diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c index de2ea589aa49b..939e628b49a85 100644 --- a/drivers/bluetooth/btbcm.c +++ b/drivers/bluetooth/btbcm.c @@ -32,6 +32,17 @@ #define BCM_FW_NAME_LEN 64 #define BCM_FW_NAME_COUNT_MAX 4 +#define BCM_ROMFW_BASELINE_NUM 0xFFFF + +struct bcm_chip_version_table { + u8 chip_id; + u16 baseline; +}; + +static const struct bcm_chip_version_table disable_broken_read_transmit_power_by_chip_ver[] = { + {0x87, BCM_ROMFW_BASELINE_NUM} /* CYW4373/4373E */ +}; + /* For kmalloc-ing the fw-name array instead of putting it on the stack */ typedef char bcm_fw_name[BCM_FW_NAME_LEN]; @@ -432,18 +443,42 @@ static const struct dmi_system_id disable_broken_read_transmit_power[] = { { } }; +static bool btbcm_is_disable_broken_read_tx_power_by_chip_ver(u8 chip_id, u16 baseline) +{ + int i; + int table_size = sizeof(disable_broken_read_transmit_power_by_chip_ver)/sizeof(disable_broken_read_transmit_power_by_chip_ver[0]); + const struct bcm_chip_version_table *entry = &disable_broken_read_transmit_power_by_chip_ver[0]; + + for( i=0 ; ichip_id) && (baseline == entry->baseline) ) + return true; + } + + return false; +} + static int btbcm_read_info(struct hci_dev *hdev) { struct sk_buff *skb; + u8 chip_id; + u16 baseline; /* Read Verbose Config Version Info */ skb = btbcm_read_verbose_config(hdev); if (IS_ERR(skb)) return PTR_ERR(skb); + chip_id = skb->data[1]; + baseline = skb->data[3] | (skb->data[4] << 8); bt_dev_info(hdev, "BCM: chip id %u", skb->data[1]); kfree_skb(skb); + /* Read DMI and disable broken Read LE Min/Max Tx Power */ + if ((dmi_first_match(disable_broken_read_transmit_power)) || + (btbcm_is_disable_broken_read_tx_power_by_chip_ver(chip_id, baseline))) + set_bit(HCI_QUIRK_BROKEN_READ_TRANSMIT_POWER, &hdev->quirks); + return 0; } diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index faad19b396d50..ef442b6b1e950 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -125,6 +125,10 @@ static const struct usb_device_id btusb_table[] = { /* Broadcom BCM20702B0 (Dynex/Insignia) */ { USB_DEVICE(0x19ff, 0x0239), .driver_info = BTUSB_BCM_PATCHRAM }, + /* Cypress devices with vendor specific id */ + { USB_VENDOR_AND_INTERFACE_INFO(0x04b4, 0xff, 0x01, 0x01), + .driver_info = BTUSB_BCM_PATCHRAM }, + /* Broadcom BCM43142A0 (Foxconn/Lenovo) */ { USB_VENDOR_AND_INTERFACE_INFO(0x105b, 0xff, 0x01, 0x01), .driver_info = BTUSB_BCM_PATCHRAM }, diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index cb1c15012dd02..51b032f6fca2d 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -25,6 +25,7 @@ source "drivers/net/wireless/broadcom/Kconfig" source "drivers/net/wireless/cisco/Kconfig" source "drivers/net/wireless/intel/Kconfig" source "drivers/net/wireless/intersil/Kconfig" +source "drivers/net/wireless/laird/Kconfig" source "drivers/net/wireless/marvell/Kconfig" source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/microchip/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index a61cf6c903431..2cb49430f6da4 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_WLAN_VENDOR_BROADCOM) += broadcom/ obj-$(CONFIG_WLAN_VENDOR_CISCO) += cisco/ obj-$(CONFIG_WLAN_VENDOR_INTEL) += intel/ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/ +obj-$(CONFIG_WLAN_VENDOR_LAIRD) += laird/ obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig index 32794c1eca231..5f7c862be6886 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig @@ -48,3 +48,10 @@ config BRCMFMAC_PCIE IEEE802.11ac embedded FullMAC WLAN driver. Say Y if you want to use the driver for an PCIE wireless card. +config BRCMFMAC_BT_SHARED_SDIO + bool "FMAC shares SDIO bus to Bluetooth" + depends on BRCMFMAC_SDIO + default n + help + This option enables the feature of sharing the SDIO bus interface + between Cypress BT and WiFi host drivers. diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile index 13c13504a6e81..2fa8e6d1926ef 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile @@ -24,7 +24,9 @@ brcmfmac-objs += \ btcoex.o \ vendor.o \ pno.o \ - xtlv.o + xtlv.o \ + vendor_ifx.o \ + twt.o brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \ bcdc.o \ fwsignal.o @@ -47,3 +49,5 @@ brcmfmac-$(CONFIG_OF) += \ of.o brcmfmac-$(CONFIG_DMI) += \ dmi.o +brcmfmac-${CONFIG_BRCMFMAC_BT_SHARED_SDIO} += \ + bt_shared_sdio.o diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c index e300278ea38c6..1145c7a6fbfdf 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c @@ -36,6 +36,7 @@ #include "sdio.h" #include "core.h" #include "common.h" +#include "cfg80211.h" #define SDIOH_API_ACCESS_RETRY_LIMIT 2 @@ -43,9 +44,12 @@ #define SDIO_FUNC1_BLOCKSIZE 64 #define SDIO_FUNC2_BLOCKSIZE 512 -#define SDIO_4373_FUNC2_BLOCKSIZE 256 +#define SDIO_4373_FUNC2_BLOCKSIZE 128 #define SDIO_435X_FUNC2_BLOCKSIZE 256 #define SDIO_4329_FUNC2_BLOCKSIZE 128 +#define SDIO_89459_FUNC2_BLOCKSIZE 256 +#define SDIO_CYW55572_FUNC2_BLOCKSIZE 256 + /* Maximum milliseconds to wait for F2 to come up */ #define SDIO_WAIT_F2RDY 3000 @@ -917,6 +921,16 @@ int brcmf_sdiod_probe(struct brcmf_sdio_dev *sdiodev) case SDIO_DEVICE_ID_BROADCOM_4329: f2_blksz = SDIO_4329_FUNC2_BLOCKSIZE; break; + case SDIO_DEVICE_ID_BROADCOM_CYPRESS_89459: + case SDIO_DEVICE_ID_CYPRESS_54590: + case SDIO_DEVICE_ID_CYPRESS_54591: + case SDIO_DEVICE_ID_CYPRESS_54594: + f2_blksz = SDIO_89459_FUNC2_BLOCKSIZE; + break; + case SDIO_DEVICE_ID_CYPRESS_55572: + case SDIO_DEVICE_ID_CYPRESS_55500: + f2_blksz = SDIO_CYW55572_FUNC2_BLOCKSIZE; + break; default: break; } @@ -962,6 +976,9 @@ int brcmf_sdiod_probe(struct brcmf_sdio_dev *sdiodev) #define BRCMF_SDIO_DEVICE(dev_id) \ {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, dev_id)} +#define CYF_SDIO_DEVICE(dev_id) \ + {SDIO_DEVICE(SDIO_VENDOR_ID_CYPRESS, dev_id)} + /* devices we support, null terminated */ static const struct sdio_device_id brcmf_sdmmc_ids[] = { BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43143), @@ -985,7 +1002,13 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = { BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43439), BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_43752), - BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_89359), + BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_CYPRESS_89459), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_43439), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_54590), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_54591), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_54594), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_55572), + CYF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_55500), { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.c new file mode 100644 index 0000000000000..40bb59fbfb5c2 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.c @@ -0,0 +1,324 @@ +/* Copyright 2019, Cypress Semiconductor Corporation or a subsidiary of + * Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related + * materials ("Software"), is owned by Cypress Semiconductor + * Corporation or one of its subsidiaries ("Cypress") and is protected by + * and subject to worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license + * agreement accompanying the software package from which you + * obtained this Software ("EULA"). If no EULA applies, Cypress hereby grants + * you a personal, nonexclusive, non-transferable license to copy, modify, + * and compile the Software source code solely for use in connection with + * Cypress's integrated circuit products. Any reproduction, modification, + * translation, compilation, or representation of this Software except as + * specified above is prohibited without the express written permission of + * Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress + * reserves the right to make changes to the Software without notice. Cypress + * does not assume any liability arising out of the application or use of the + * Software or any product or circuit described in the Software. Cypress does + * not authorize its products for use in any products where a malfunction or + * failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). By + * including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing + * so agrees to indemnify Cypress against all liability. + */ + +#include +#include +#include +#include +#include +#include "bus.h" +#include "chipcommon.h" +#include "core.h" +#include "sdio.h" +#include "soc.h" +#include "fwil.h" + +#define SDIOD_ADDR_BOUND 0x1000 +#define SDIOD_ADDR_BOUND_MASK 0xfff + +struct brcmf_bus *g_bus_if; + +enum bus_owner { + WLAN_MODULE = 0, + BT_MODULE +}; + +struct btsdio_info { + u32 bt_buf_reg_addr; + u32 host_ctrl_reg_addr; + u32 bt_ctrl_reg_addr; + u32 bt_buf_addr; + u32 wlan_buf_addr; +}; + +void brcmf_btsdio_int_handler(struct brcmf_bus *bus_if) +{ + struct brcmf_bt_dev *btdev = bus_if->bt_dev; + + if (btdev && btdev->bt_sdio_int_cb) + btdev->bt_sdio_int_cb(btdev->bt_data); +} + +int brcmf_btsdio_init(struct brcmf_bus *bus_if) +{ + if (!bus_if) + return -EINVAL; + + g_bus_if = bus_if; + return 0; +} + +int brcmf_btsdio_attach(struct brcmf_bus *bus_if, void *btdata, + void (*bt_int_fun)(void *data)) +{ + struct brcmf_bt_dev *btdev; + + /* Allocate bt dev */ + btdev = kzalloc(sizeof(*btdev), GFP_ATOMIC); + if (!btdev) + return -ENOMEM; + + btdev->bt_data = btdata; + btdev->bt_sdio_int_cb = bt_int_fun; + bus_if->bt_dev = btdev; + + return 0; +} + +void brcmf_btsdio_detach(struct brcmf_bus *bus_if) +{ + struct brcmf_bt_dev *btdev = bus_if->bt_dev; + + if (!btdev) + return; + + if (btdev->bt_data) + btdev->bt_data = NULL; + if (btdev->bt_sdio_int_cb) + btdev->bt_sdio_int_cb = NULL; + if (bus_if->bt_dev) { + bus_if->bt_dev = NULL; + kfree(btdev); + } +} + +u8 brcmf_btsdio_bus_count(struct brcmf_bus *bus_if) +{ + struct brcmf_bt_dev *btdev = bus_if->bt_dev; + + if (!btdev) + return 0; + + return btdev->use_count; +} + +void *brcmf_bt_sdio_attach(void *btdata, void (*bt_int_fun)(void *data)) +{ + int err; + + if (!g_bus_if) { + brcmf_err("BTSDIO is not initialized\n"); + return NULL; + } + + err = brcmf_btsdio_attach(g_bus_if, btdata, bt_int_fun); + if (err) { + brcmf_err("BTSDIO attach failed, err=%d\n", err); + return NULL; + } + + return (void *)g_bus_if; +} +EXPORT_SYMBOL(brcmf_bt_sdio_attach); + +int brcmf_get_wlan_info(struct brcmf_bus *bus_if, struct btsdio_info *bs_info) +{ + struct brcmf_if *ifp; + + if (!bus_if || !bs_info) + return -EINVAL; + + ifp = bus_if->drvr->iflist[0]; + + bs_info->bt_buf_reg_addr = SI_ENUM_BASE_DEFAULT + 0xC00 + + CHIPGCIREGOFFS(gci_input[6]); + bs_info->host_ctrl_reg_addr = SI_ENUM_BASE_DEFAULT + 0xC00 + + CHIPGCIREGOFFS(gci_output[3]); + bs_info->bt_ctrl_reg_addr = SI_ENUM_BASE_DEFAULT + 0xC00 + + CHIPGCIREGOFFS(gci_input[7]); + brcmf_dbg(INFO, "BT buf reg addr: 0x%x\n", + bs_info->bt_buf_reg_addr); + brcmf_dbg(INFO, "HOST ctrl reg addr: 0x%x\n", + bs_info->host_ctrl_reg_addr); + brcmf_dbg(INFO, "BT ctrl reg addr: 0x%x\n", + bs_info->bt_ctrl_reg_addr); + return 0; +} +EXPORT_SYMBOL(brcmf_get_wlan_info); + +u32 brcmf_bus_reg_read(struct brcmf_bus *bus_if, u32 addr) +{ + struct brcmf_sdio_dev *sdiodev; + int err = 0; + u32 val; + + if (!bus_if) + return -EINVAL; + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + val = brcmf_sdiod_readl(sdiodev, addr, &err); + if (err) { + brcmf_err("sdio reg read failed, err=%d\n", err); + sdio_release_host(sdiodev->func1); + return err; + } + sdio_release_host(sdiodev->func1); + + return val; +} +EXPORT_SYMBOL(brcmf_bus_reg_read); + +void brcmf_bus_reg_write(struct brcmf_bus *bus_if, u32 addr, u32 val) +{ + struct brcmf_sdio_dev *sdiodev; + int err = 0; + + if (!bus_if) + return; + + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + brcmf_sdiod_writel(sdiodev, addr, val, &err); + if (err) + brcmf_err("sdio reg write failed, err=%d\n", err); + sdio_release_host(sdiodev->func1); +} +EXPORT_SYMBOL(brcmf_bus_reg_write); + +int brcmf_membytes(struct brcmf_bus *bus_if, bool set, u32 address, u8 *data, + unsigned int size) +{ + struct brcmf_sdio_dev *sdiodev; + int err = 0; + u32 block1_offset; + u32 block2_addr; + u16 block1_size; + u16 block2_size; + u8 *block2_data; + + if (!bus_if || !data) + return -EINVAL; + + sdiodev = bus_if->bus_priv.sdio; + /* To avoid SDIO access crosses AXI 4k address boundaries crossing */ + if (((address & SDIOD_ADDR_BOUND_MASK) + size) > SDIOD_ADDR_BOUND) { + brcmf_dbg(SDIO, "data cross 4K boundary\n"); + /* The 1st 4k packet */ + block1_offset = address & SDIOD_ADDR_BOUND_MASK; + block1_size = (SDIOD_ADDR_BOUND - block1_offset); + sdio_claim_host(sdiodev->func1); + err = brcmf_sdiod_ramrw(sdiodev, set, address, + data, block1_size); + if (err) { + brcmf_err("sdio memory access failed, err=%d\n", err); + sdio_release_host(sdiodev->func1); + return err; + } + /* The 2nd 4k packet */ + block2_addr = address + block1_size; + block2_size = size - block1_size; + block2_data = data + block1_size; + err = brcmf_sdiod_ramrw(sdiodev, set, block2_addr, + block2_data, block2_size); + if (err) + brcmf_err("sdio memory access failed, err=%d\n", err); + sdio_release_host(sdiodev->func1); + } else { + sdio_claim_host(sdiodev->func1); + err = brcmf_sdiod_ramrw(sdiodev, set, address, data, size); + if (err) + brcmf_err("sdio memory access failed, err=%d\n", err); + sdio_release_host(sdiodev->func1); + } + return err; +} +EXPORT_SYMBOL(brcmf_membytes); + +/* Function to enable the Bus Clock + * This function is not callable from non-sleepable context + */ +int brcmf_bus_clk_enable(struct brcmf_bus *bus_if, enum bus_owner owner) +{ + struct brcmf_sdio_dev *sdiodev; + struct brcmf_bt_dev *btdev; + int err = 0; + + if (!bus_if) + return -EINVAL; + + btdev = bus_if->bt_dev; + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + btdev->use_count++; + sdio_release_host(sdiodev->func1); + err = brcmf_sdio_sleep(sdiodev->bus, false); + + return err; +} +EXPORT_SYMBOL(brcmf_bus_clk_enable); + +/* Function to disable the Bus Clock + * This function is not callable from non-sleepable context + */ +int brcmf_bus_clk_disable(struct brcmf_bus *bus_if, enum bus_owner owner) +{ + struct brcmf_sdio_dev *sdiodev; + struct brcmf_bt_dev *btdev; + int err = 0; + + if (!bus_if) + return -EINVAL; + + btdev = bus_if->bt_dev; + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + if (btdev->use_count != 0) + btdev->use_count--; + sdio_release_host(sdiodev->func1); + err = brcmf_sdio_sleep(sdiodev->bus, true); + + return err; +} +EXPORT_SYMBOL(brcmf_bus_clk_disable); + +/* Function to reset bt_use_count counter to zero. + * This function is not callable from non-sleepable context + */ +void brcmf_bus_reset_bt_use_count(struct brcmf_bus *bus_if) +{ + struct brcmf_sdio_dev *sdiodev; + struct brcmf_bt_dev *btdev; + + if (!bus_if) + return; + + btdev = bus_if->bt_dev; + sdiodev = bus_if->bus_priv.sdio; + + sdio_claim_host(sdiodev->func1); + btdev->use_count = 0; + sdio_release_host(sdiodev->func1); +} +EXPORT_SYMBOL(brcmf_bus_reset_bt_use_count); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.h new file mode 100644 index 0000000000000..28910878ead5b --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bt_shared_sdio.h @@ -0,0 +1,42 @@ +/* Copyright 2019, Cypress Semiconductor Corporation or a subsidiary of + * Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related + * materials ("Software"), is owned by Cypress Semiconductor + * Corporation or one of its subsidiaries ("Cypress") and is protected by + * and subject to worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license + * agreement accompanying the software package from which you + * obtained this Software ("EULA"). If no EULA applies, Cypress hereby grants + * you a personal, nonexclusive, non-transferable license to copy, modify, + * and compile the Software source code solely for use in connection with + * Cypress's integrated circuit products. Any reproduction, modification, + * translation, compilation, or representation of this Software except as + * specified above is prohibited without the express written permission of + * Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress + * reserves the right to make changes to the Software without notice. Cypress + * does not assume any liability arising out of the application or use of the + * Software or any product or circuit described in the Software. Cypress does + * not authorize its products for use in any products where a malfunction or + * failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). By + * including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing + * so agrees to indemnify Cypress against all liability. + */ + +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO +int brcmf_btsdio_init(struct brcmf_bus *bus_if); +void brcmf_btsdio_detach(struct brcmf_bus *bus_if); +void brcmf_btsdio_int_handler(struct brcmf_bus *bus_if); +u8 brcmf_btsdio_bus_count(struct brcmf_bus *bus_if); +#else +static inline +u8 brcmf_btsdio_bus_count(struct brcmf_bus *bus_if) +{ + return 0; +} +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h index 2208ab3aa7959..abefbd3ec95e0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h @@ -6,9 +6,8 @@ #ifndef BRCMFMAC_BUS_H #define BRCMFMAC_BUS_H -#include -#include #include "debug.h" +#include /* IDs of the 6 default common rings of msgbuf protocol */ #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT 0 @@ -24,6 +23,12 @@ #define BRCMF_NROF_COMMON_MSGRINGS (BRCMF_NROF_H2D_COMMON_MSGRINGS + \ BRCMF_NROF_D2H_COMMON_MSGRINGS) +/* The interval to poll console */ +#define BRCMF_CONSOLE 10 + +/* The maximum console interval value (5 mins) */ +#define MAX_CONSOLE_INTERVAL (5 * 60) + /* The level of bus communication with the dongle */ enum brcmf_bus_state { BRCMF_BUS_DOWN, /* Not ready for frame transfers */ @@ -36,11 +41,6 @@ enum brcmf_bus_protocol_type { BRCMF_PROTO_MSGBUF }; -/* Firmware blobs that may be available */ -enum brcmf_blob_type { - BRCMF_BLOB_CLM, -}; - struct brcmf_mp_device; struct brcmf_bus_dcmd { @@ -67,7 +67,7 @@ struct brcmf_bus_dcmd { * @wowl_config: specify if dongle is configured for wowl when going to suspend * @get_ramsize: obtain size of device memory. * @get_memdump: obtain device memory dump in provided buffer. - * @get_blob: obtain a firmware blob. + * @get_fwname: obtain firmware name. * * This structure provides an abstract interface towards the * bus specific driver. For control messages to common driver @@ -84,8 +84,8 @@ struct brcmf_bus_ops { void (*wowl_config)(struct device *dev, bool enabled); size_t (*get_ramsize)(struct device *dev); int (*get_memdump)(struct device *dev, void *data, size_t len); - int (*get_blob)(struct device *dev, const struct firmware **fw, - enum brcmf_blob_type type); + int (*get_fwname)(struct device *dev, const char *ext, + unsigned char *fw_name); void (*debugfs_create)(struct device *dev); int (*reset)(struct device *dev); }; @@ -124,6 +124,19 @@ struct brcmf_bus_stats { atomic_t pktcow_failed; }; +/** + * struct brcmf_bt_dev - bt shared SDIO device. + * + * @ bt_data: bt internal structure data + * @ bt_sdio_int_cb: bt registered interrupt callback function + * @ bt_use_count: Counter that tracks whether BT is using the bus + */ +struct brcmf_bt_dev { + void *bt_data; + void (*bt_sdio_int_cb)(void *data); + u32 use_count; /* Counter for tracking if BT is using the bus */ +}; + /** * struct brcmf_bus - interface structure between common and bus layer * @@ -139,6 +152,7 @@ struct brcmf_bus_stats { * @wowl_supported: is wowl supported by bus driver. * @chiprev: revision of the dongle chip. * @msgbuf: msgbuf protocol parameters provided by bus layer. + * @bt_dev: bt shared SDIO device */ struct brcmf_bus { union { @@ -159,6 +173,9 @@ struct brcmf_bus { const struct brcmf_bus_ops *ops; struct brcmf_bus_msgbuf *msgbuf; +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + struct brcmf_bt_dev *bt_dev; +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ }; /* @@ -227,10 +244,10 @@ int brcmf_bus_get_memdump(struct brcmf_bus *bus, void *data, size_t len) } static inline -int brcmf_bus_get_blob(struct brcmf_bus *bus, const struct firmware **fw, - enum brcmf_blob_type type) +int brcmf_bus_get_fwname(struct brcmf_bus *bus, const char *ext, + unsigned char *fw_name) { - return bus->ops->get_blob(bus->dev, fw, type); + return bus->ops->get_fwname(bus->dev, ext, fw_name); } static inline @@ -256,14 +273,14 @@ int brcmf_bus_reset(struct brcmf_bus *bus) */ /* Receive frame for delivery to OS. Callee disposes of rxp. */ -void brcmf_rx_frame(struct device *dev, struct sk_buff *rxp, bool handle_event, - bool inirq); +struct sk_buff *brcmf_rx_frame(struct device *dev, struct sk_buff *rxp, bool handle_event, + bool inirq); /* Receive async event packet from firmware. Callee disposes of rxp. */ void brcmf_rx_event(struct device *dev, struct sk_buff *rxp); int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings); /* Indication from bus module regarding presence/insertion of dongle. */ -int brcmf_attach(struct device *dev); +int brcmf_attach(struct device *dev, bool start_bus); /* Indication from bus module regarding removal/absence of dongle */ void brcmf_detach(struct device *dev); void brcmf_free(struct device *dev); @@ -279,6 +296,7 @@ void brcmf_bus_change_state(struct brcmf_bus *bus, enum brcmf_bus_state state); s32 brcmf_iovar_data_set(struct device *dev, char *name, void *data, u32 len); void brcmf_bus_add_txhdrlen(struct device *dev, uint len); +int brcmf_fwlog_attach(struct device *dev); #ifdef CONFIG_BRCMFMAC_SDIO void brcmf_sdio_exit(void); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index ad5a8d61d9385..2e27f4b32de8b 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -30,8 +31,10 @@ #include "fwil.h" #include "proto.h" #include "vendor.h" +#include "vendor_ifx.h" #include "bus.h" #include "common.h" +#include "twt.h" #define BRCMF_SCAN_IE_LEN_MAX 2048 @@ -40,6 +43,7 @@ #define RSN_OUI "\x00\x0F\xAC" /* RSN OUI */ #define WME_OUI_TYPE 2 #define WPS_OUI_TYPE 4 +#define WFA_OUI_TYPE_MBO_OCE 0x16 #define VS_IE_FIXED_HDR_LEN 6 #define WPA_IE_VERSION_LEN 2 @@ -64,6 +68,9 @@ #define RSN_CAP_MFPC_MASK BIT(7) #define RSN_PMKID_COUNT_LEN 2 +#define DPP_AKM_SUITE_TYPE 2 +#define WLAN_AKM_SUITE_DPP SUITE(WLAN_OUI_WFA, DPP_AKM_SUITE_TYPE) + #define VNDR_IE_CMD_LEN 4 /* length of the set command * string :"add", "del" (+ NUL) */ @@ -76,8 +83,10 @@ #define DOT11_MGMT_HDR_LEN 24 /* d11 management header len */ #define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */ -#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 -#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 +#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 +#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 +#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS_6E 80 +#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS_6E 130 #define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20 #define BRCMF_SCAN_CHANNEL_TIME 40 @@ -86,13 +95,55 @@ #define BRCMF_ND_INFO_TIMEOUT msecs_to_jiffies(2000) -#define BRCMF_PS_MAX_TIMEOUT_MS 2000 +#define MGMT_AUTH_FRAME_DWELL_TIME 4000 +#define MGMT_AUTH_FRAME_WAIT_TIME (MGMT_AUTH_FRAME_DWELL_TIME + 100) + +/* Dump obss definitions */ +#define ACS_MSRMNT_DELAY 80 +#define CHAN_NOISE_DUMMY (-80) +#define OBSS_TOKEN_IDX 15 +#define IBSS_TOKEN_IDX 15 +#define TX_TOKEN_IDX 14 +#define CTG_TOKEN_IDX 13 +#define PKT_TOKEN_IDX 15 +#define IDLE_TOKEN_IDX 12 #define BRCMF_ASSOC_PARAMS_FIXED_SIZE \ (sizeof(struct brcmf_assoc_params_le) - sizeof(u16)) -#define BRCMF_MAX_CHANSPEC_LIST \ - (BRCMF_DCMD_MEDLEN / sizeof(__le32) - 1) +#define BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY 123 +#define BSS_MEMBERSHIP_SELECTOR_SET 0x80 +#define SAE_H2E_ONLY_ENABLE (BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY | \ + BSS_MEMBERSHIP_SELECTOR_SET) + +struct brcmf_dump_survey { + u32 obss; + u32 ibss; + u32 no_ctg; + u32 no_pckt; + u32 tx; + u32 idle; +}; + +struct cca_stats_n_flags { + u32 msrmnt_time; /* Time for Measurement (msec) */ + u32 msrmnt_done; /* flag set when measurement complete */ + char buf[1]; +}; + +struct cca_msrmnt_query { + u32 msrmnt_query; + u32 time_req; +}; + +/* algo bit vector */ +#define KEY_ALGO_MASK(_algo) (1 << (_algo)) +/* version of the wl_wsec_info structure */ +#define WL_WSEC_INFO_VERSION 0x01 + +/* start enum value for BSS properties */ +#define WL_WSEC_INFO_BSS_BASE 0x0100 +#define WL_WSEC_INFO_BSS_ALGOS (WL_WSEC_INFO_BSS_BASE + 6) static bool check_vif_up(struct brcmf_cfg80211_vif *vif) { @@ -148,6 +199,14 @@ static struct ieee80211_rate __wl_rates[] = { .max_power = 30, \ } +#define CHAN6G(_channel) { \ + .band = NL80211_BAND_6GHZ, \ + .center_freq = 5950 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + static struct ieee80211_channel __wl_2ghz_channels[] = { CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427), CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447), @@ -164,6 +223,23 @@ static struct ieee80211_channel __wl_5ghz_channels[] = { CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165) }; +static struct ieee80211_channel __wl_6ghz_channels[] = { + CHAN6G(1), CHAN6G(5), CHAN6G(9), CHAN6G(13), CHAN6G(17), + CHAN6G(21), CHAN6G(25), CHAN6G(29), CHAN6G(33), CHAN6G(37), + CHAN6G(41), CHAN6G(45), CHAN6G(49), CHAN6G(53), CHAN6G(57), + CHAN6G(61), CHAN6G(65), CHAN6G(69), CHAN6G(73), CHAN6G(77), + CHAN6G(81), CHAN6G(85), CHAN6G(89), CHAN6G(93), CHAN6G(97), + CHAN6G(101), CHAN6G(105), CHAN6G(109), CHAN6G(113), CHAN6G(117), + CHAN6G(121), CHAN6G(125), CHAN6G(129), CHAN6G(133), CHAN6G(137), + CHAN6G(141), CHAN6G(145), CHAN6G(149), CHAN6G(153), CHAN6G(157), + CHAN6G(161), CHAN6G(165), CHAN6G(169), CHAN6G(173), CHAN6G(177), + CHAN6G(181), CHAN6G(185), CHAN6G(189), CHAN6G(193), CHAN6G(197), + CHAN6G(201), CHAN6G(205), CHAN6G(209), CHAN6G(213), CHAN6G(217), + CHAN6G(221), CHAN6G(225), CHAN6G(229), CHAN6G(233) +}; + +struct ieee80211_sband_iftype_data sdata[NUM_NL80211_BANDS]; + /* Band templates duplicated per wiphy. The channel info * above is added to the band during setup. */ @@ -179,6 +255,12 @@ static const struct ieee80211_supported_band __wl_band_5ghz = { .n_bitrates = wl_a_rates_size, }; +static struct ieee80211_supported_band __wl_band_6ghz = { + .band = NL80211_BAND_6GHZ, + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + /* This is to override regulatory domains defined in cfg80211 module (reg.c) * By default world regulatory domain defined in reg.c puts the flags * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165). @@ -187,7 +269,7 @@ static const struct ieee80211_supported_band __wl_band_5ghz = { * domain are to be done here. */ static const struct ieee80211_regdomain brcmf_regdom = { - .n_reg_rules = 4, + .n_reg_rules = 5, .alpha2 = "99", .reg_rules = { /* IEEE 802.11b/g, channels 1..11 */ @@ -200,22 +282,31 @@ static const struct ieee80211_regdomain brcmf_regdom = { /* IEEE 802.11a, channel 36..64 */ REG_RULE(5150-10, 5350+10, 160, 6, 20, 0), /* IEEE 802.11a, channel 100..165 */ - REG_RULE(5470-10, 5850+10, 160, 6, 20, 0), } + REG_RULE(5470-10, 5850+10, 160, 6, 20, 0), + /* IEEE 802.11ax, 6E */ + REG_RULE(5935-10, 7115+10, 160, 6, 20, 0), + } }; /* Note: brcmf_cipher_suites is an array of int defining which cipher suites * are supported. A pointer to this array and the number of entries is passed * on to upper layers. AES_CMAC defines whether or not the driver supports MFP. * So the cipher suite AES_CMAC has to be the last one in the array, and when - * device does not support MFP then the number of suites will be decreased by 1 + * device does not support MFP then the number of suites will be decreased by 4 */ static const u32 brcmf_cipher_suites[] = { WLAN_CIPHER_SUITE_WEP40, WLAN_CIPHER_SUITE_WEP104, WLAN_CIPHER_SUITE_TKIP, WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_CCMP_256, + WLAN_CIPHER_SUITE_GCMP, + WLAN_CIPHER_SUITE_GCMP_256, /* Keep as last entry: */ - WLAN_CIPHER_SUITE_AES_CMAC + WLAN_CIPHER_SUITE_AES_CMAC, + WLAN_CIPHER_SUITE_BIP_CMAC_256, + WLAN_CIPHER_SUITE_BIP_GMAC_128, + WLAN_CIPHER_SUITE_BIP_GMAC_256 }; /* Vendor specific ie. id = 221, oui and type defines exact ie */ @@ -237,6 +328,169 @@ struct parsed_vndr_ies { struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT]; }; +struct brcmf_ext_tlv { + u8 id; + u8 len; + u8 ext_id; +}; + +struct parsed_ext_ie_info { + u8 *ie_ptr; + u32 ie_len; /* total length including id & length field */ + struct brcmf_ext_tlv ie_data; +}; + +struct parsed_extension_ies { + u32 count; + struct parsed_ext_ie_info ie_info[VNDR_IE_PARSE_LIMIT]; +}; + +struct dot11_assoc_resp { + u16 capability; /* capability information */ + u16 status; /* status code */ + u16 aid; /* association ID */ +}; + +#define WLC_E_IF_ROLE_STA 0 /* Infra STA */ +#define WLC_E_IF_ROLE_AP 1 /* Access Point */ + +#define WL_INTERFACE_CREATE_VER_1 1 +#define WL_INTERFACE_CREATE_VER_2 2 +#define WL_INTERFACE_CREATE_VER_3 3 +#define WL_INTERFACE_CREATE_VER_MAX WL_INTERFACE_CREATE_VER_3 + +#define WL_INTERFACE_MAC_DONT_USE 0x0 +#define WL_INTERFACE_MAC_USE 0x2 + +#define WL_INTERFACE_CREATE_STA 0x0 +#define WL_INTERFACE_CREATE_AP 0x1 + +struct wl_interface_create_v1 { + u16 ver; /* structure version */ + u32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u32 wlc_index; /* optional for wlc index */ +}; + +struct wl_interface_create_v2 { + u16 ver; /* structure version */ + u8 pad1[2]; + u32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u8 iftype; /* type of interface created */ + u8 pad2; + u32 wlc_index; /* optional for wlc index */ +}; + +struct wl_interface_create_v3 { + u16 ver; /* structure version */ + u16 len; /* length of structure + data */ + u16 fixed_len; /* length of structure */ + u8 iftype; /* type of interface created */ + u8 wlc_index; /* optional for wlc index */ + u32 flags; /* flags for operation */ + u8 mac_addr[ETH_ALEN]; /* MAC address */ + u8 bssid[ETH_ALEN]; /* optional for BSSID */ + u8 if_index; /* interface index request */ + u8 pad[3]; + u8 data[]; /* Optional for specific data */ +}; + +/* tlv used to return wl_wsec_info properties */ +struct wl_wsec_info_tlv { + u16 type; + u16 len; /* data length */ + u8 data[1]; /* data follows */ +}; + +/* input/output data type for wsec_info iovar */ +struct wl_wsec_info { + u8 version; /* structure version */ + u8 pad[2]; + u8 num_tlvs; + struct wl_wsec_info_tlv tlvs[1]; /* tlv data follows */ +}; + +static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg); +static bool +wl_cfgoce_has_ie(const u8 *ie, const u8 **tlvs, u32 *tlvs_len, + const u8 *oui, u32 oui_len, u8 type); + +/* Check whether the given IE looks like WFA OCE IE. */ +#define wl_cfgoce_is_oce_ie(ie, tlvs, len) \ + wl_cfgoce_has_ie(ie, tlvs, len, \ + (const u8 *)WFA_OUI, TLV_OUI_LEN, WFA_OUI_TYPE_MBO_OCE) + +static s32 +wl_set_wsec_info_algos(struct brcmf_if *ifp, u32 algos, u32 mask) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err = 0; + struct wl_wsec_info *wsec_info; + struct bcm_xtlv *wsec_info_tlv; + u16 tlv_data_len; + u8 tlv_data[8]; + u32 param_len; + u8 *buf; + + brcmf_dbg(TRACE, "Enter\n"); + + buf = kzalloc(sizeof(struct wl_wsec_info) + sizeof(tlv_data), GFP_KERNEL); + if (!buf) { + bphy_err(drvr, "unable to allocate.\n"); + return -ENOMEM; + } + + wsec_info = (struct wl_wsec_info *)buf; + wsec_info->version = WL_WSEC_INFO_VERSION; + wsec_info_tlv = (struct bcm_xtlv *)(buf + offsetof(struct wl_wsec_info, tlvs)); + + wsec_info->num_tlvs++; + tlv_data_len = sizeof(tlv_data); + memcpy(tlv_data, &algos, sizeof(algos)); + memcpy(tlv_data + sizeof(algos), &mask, sizeof(mask)); + + wsec_info_tlv->id = cpu_to_le16(WL_WSEC_INFO_BSS_ALGOS); + wsec_info_tlv->len = cpu_to_le16(tlv_data_len); + memcpy(wsec_info_tlv->data, tlv_data, tlv_data_len); + + param_len = offsetof(struct wl_wsec_info, tlvs) + + offsetof(struct wl_wsec_info_tlv, data) + tlv_data_len; + + err = brcmf_fil_bsscfg_data_set(ifp, "wsec_info", buf, param_len); + if (err) + brcmf_err("set wsec_info_error:%d\n", err); + + kfree(buf); + return err; +} + +/* Is any of the tlvs the expected entry? If + * not update the tlvs buffer pointer/length. + */ +static bool +wl_cfgoce_has_ie(const u8 *ie, const u8 **tlvs, u32 *tlvs_len, + const u8 *oui, u32 oui_len, u8 type) +{ + /* If the contents match the OUI and the type */ + if (ie[TLV_LEN_OFF] >= oui_len + 1 && + !memcmp(&ie[TLV_BODY_OFF], oui, oui_len) && + type == ie[TLV_BODY_OFF + oui_len]) { + return true; + } + + if (!tlvs) + return false; + /* point to the next ie */ + ie += ie[TLV_LEN_OFF] + TLV_HDR_LEN; + /* calculate the length of the rest of the buffer */ + *tlvs_len -= (int)(ie - *tlvs); + /* update the pointer to the start of the buffer */ + *tlvs = ie; + + return false; +} + static u8 nl80211_band_to_fwil(enum nl80211_band band) { switch (band) { @@ -244,6 +498,8 @@ static u8 nl80211_band_to_fwil(enum nl80211_band band) return WLC_BAND_2G; case NL80211_BAND_5GHZ: return WLC_BAND_5G; + case NL80211_BAND_6GHZ: + return WLC_BAND_6G; default: WARN_ON(1); break; @@ -317,6 +573,9 @@ static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf, case NL80211_BAND_5GHZ: ch_inf.band = BRCMU_CHAN_BAND_5G; break; + case NL80211_BAND_6GHZ: + ch_inf.band = BRCMU_CHAN_BAND_6G; + break; case NL80211_BAND_60GHZ: default: WARN_ON_ONCE(1); @@ -332,6 +591,20 @@ u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, { struct brcmu_chan ch_inf; + switch (ch->band) { + case NL80211_BAND_2GHZ: + ch_inf.band = BRCMU_CHAN_BAND_2G; + break; + case NL80211_BAND_5GHZ: + ch_inf.band = BRCMU_CHAN_BAND_5G; + break; + case NL80211_BAND_6GHZ: + ch_inf.band = BRCMU_CHAN_BAND_6G; + break; + case NL80211_BAND_60GHZ: + default: + WARN_ON_ONCE(1); + } ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq); ch_inf.bw = BRCMU_CHAN_BW_20; d11inf->encchspec(&ch_inf); @@ -490,7 +763,7 @@ send_key_to_dongle(struct brcmf_if *ifp, struct brcmf_wsec_key *key) return err; } -static void +void brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev) { struct brcmf_cfg80211_vif *vif; @@ -524,40 +797,227 @@ static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr) return -ENOMEM; } +static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr) +{ + u8 mac_idx = ifp->drvr->sta_mac_idx; + + /* set difference MAC address with locally administered bit */ + memcpy(mac_addr, ifp->mac_addr, ETH_ALEN); + mac_addr[0] |= 0x02; + mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0; + mac_idx++; + mac_idx = mac_idx % 2; + ifp->drvr->sta_mac_idx = mac_idx; +} + +static int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr) +{ + struct wl_interface_create_v1 iface_v1; + struct wl_interface_create_v2 iface_v2; + struct wl_interface_create_v3 iface_v3; + u32 iface_create_ver; + int err; + + /* interface_create version 1 */ + memset(&iface_v1, 0, sizeof(iface_v1)); + iface_v1.ver = WL_INTERFACE_CREATE_VER_1; + iface_v1.flags = WL_INTERFACE_CREATE_STA | + WL_INTERFACE_MAC_USE; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); + + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v1, + sizeof(iface_v1)); + if (err) { + brcmf_dbg(INFO, "failed to create interface(v1), err=%d\n", + err); + } else { + brcmf_dbg(INFO, "interface created(v1)\n"); + return 0; + } + + /* interface_create version 2 */ + memset(&iface_v2, 0, sizeof(iface_v2)); + iface_v2.ver = WL_INTERFACE_CREATE_VER_2; + iface_v2.flags = WL_INTERFACE_MAC_USE; + iface_v2.iftype = WL_INTERFACE_CREATE_STA; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); + + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v2, + sizeof(iface_v2)); + if (err) { + brcmf_dbg(INFO, "failed to create interface(v2), err=%d\n", + err); + } else { + brcmf_dbg(INFO, "interface created(v2)\n"); + return 0; + } + + /* interface_create version 3+ */ + /* get supported version from firmware side */ + iface_create_ver = 0; + err = brcmf_fil_bsscfg_int_get(ifp, "interface_create", + &iface_create_ver); + if (err) { + brcmf_err("fail to get supported version, err=%d\n", err); + return -EOPNOTSUPP; + } + + switch (iface_create_ver) { + case WL_INTERFACE_CREATE_VER_3: + memset(&iface_v3, 0, sizeof(iface_v3)); + iface_v3.ver = WL_INTERFACE_CREATE_VER_3; + iface_v3.flags = WL_INTERFACE_MAC_USE; + iface_v3.iftype = WL_INTERFACE_CREATE_STA; + if (!is_zero_ether_addr(macaddr)) + memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN); + else + brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); + + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v3, + sizeof(iface_v3)); + + if (!err) + brcmf_dbg(INFO, "interface created(v3)\n"); + break; + default: + brcmf_err("not support interface create(v%d)\n", + iface_create_ver); + err = -EOPNOTSUPP; + break; + } + + if (err) { + brcmf_info("station interface creation failed (%d)\n", + err); + return -EIO; + } + + return 0; +} + static int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp) { + struct wl_interface_create_v1 iface_v1; + struct wl_interface_create_v2 iface_v2; + struct wl_interface_create_v3 iface_v3; + u32 iface_create_ver; struct brcmf_pub *drvr = ifp->drvr; struct brcmf_mbss_ssid_le mbss_ssid_le; int bsscfgidx; int err; - memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le)); - bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr); - if (bsscfgidx < 0) - return bsscfgidx; + /* interface_create version 1 */ + memset(&iface_v1, 0, sizeof(iface_v1)); + iface_v1.ver = WL_INTERFACE_CREATE_VER_1; + iface_v1.flags = WL_INTERFACE_CREATE_AP | + WL_INTERFACE_MAC_USE; - mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx); - mbss_ssid_le.SSID_len = cpu_to_le32(5); - sprintf(mbss_ssid_le.SSID, "ssid%d" , bsscfgidx); + brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr); - err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le, - sizeof(mbss_ssid_le)); - if (err < 0) - bphy_err(drvr, "setting ssid failed %d\n", err); + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v1, + sizeof(iface_v1)); + if (err) { + brcmf_dbg(INFO, "failed to create interface(v1), err=%d\n", + err); + } else { + brcmf_dbg(INFO, "interface created(v1)\n"); + return 0; + } + + /* interface_create version 2 */ + memset(&iface_v2, 0, sizeof(iface_v2)); + iface_v2.ver = WL_INTERFACE_CREATE_VER_2; + iface_v2.flags = WL_INTERFACE_MAC_USE; + iface_v2.iftype = WL_INTERFACE_CREATE_AP; + + brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr); + + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v2, + sizeof(iface_v2)); + if (err) { + brcmf_dbg(INFO, "failed to create interface(v2), err=%d\n", + err); + } else { + brcmf_dbg(INFO, "interface created(v2)\n"); + return 0; + } + + /* interface_create version 3+ */ + /* get supported version from firmware side */ + iface_create_ver = 0; + err = brcmf_fil_bsscfg_int_get(ifp, "interface_create", + &iface_create_ver); + if (err) { + brcmf_err("fail to get supported version, err=%d\n", err); + return -EOPNOTSUPP; + } + + switch (iface_create_ver) { + case WL_INTERFACE_CREATE_VER_3: + memset(&iface_v3, 0, sizeof(iface_v3)); + iface_v3.ver = WL_INTERFACE_CREATE_VER_3; + iface_v3.flags = WL_INTERFACE_MAC_USE; + iface_v3.iftype = WL_INTERFACE_CREATE_AP; + brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr); + + err = brcmf_fil_iovar_data_get(ifp, "interface_create", + &iface_v3, + sizeof(iface_v3)); + + if (!err) + brcmf_dbg(INFO, "interface created(v3)\n"); + break; + default: + brcmf_err("not support interface create(v%d)\n", + iface_create_ver); + err = -EOPNOTSUPP; + break; + } + + if (err) { + brcmf_info("Does not support interface_create (%d)\n", + err); + memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le)); + bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr); + if (bsscfgidx < 0) + return bsscfgidx; + + mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx); + mbss_ssid_le.SSID_len = cpu_to_le32(5); + sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx); + + err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le, + sizeof(mbss_ssid_le)); + + if (err < 0) + bphy_err(drvr, "setting ssid failed %d\n", err); + } return err; } /** - * brcmf_ap_add_vif() - create a new AP virtual interface for multiple BSS + * brcmf_ap_add_vif() - create a new AP or STA virtual interface * * @wiphy: wiphy device of new interface. * @name: name of the new interface. - * @params: contains mac address for AP device. + * @params: contains mac address for AP or STA device. */ static -struct wireless_dev *brcmf_ap_add_vif(struct wiphy *wiphy, const char *name, - struct vif_params *params) +struct wireless_dev *brcmf_apsta_add_vif(struct wiphy *wiphy, const char *name, + struct vif_params *params, + enum nl80211_iftype type) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); @@ -565,18 +1025,24 @@ struct wireless_dev *brcmf_ap_add_vif(struct wiphy *wiphy, const char *name, struct brcmf_cfg80211_vif *vif; int err; + if (type != NL80211_IFTYPE_STATION && type != NL80211_IFTYPE_AP) + return ERR_PTR(-EINVAL); + if (brcmf_cfg80211_vif_event_armed(cfg)) return ERR_PTR(-EBUSY); brcmf_dbg(INFO, "Adding vif \"%s\"\n", name); - vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_AP); + vif = brcmf_alloc_vif(cfg, type); if (IS_ERR(vif)) return (struct wireless_dev *)vif; brcmf_cfg80211_arm_vif_event(cfg, vif); - err = brcmf_cfg80211_request_ap_if(ifp); + if (type == NL80211_IFTYPE_STATION) + err = brcmf_cfg80211_request_sta_if(ifp, params->macaddr); + else + err = brcmf_cfg80211_request_ap_if(ifp); if (err) { brcmf_cfg80211_arm_vif_event(cfg, NULL); goto fail; @@ -723,15 +1189,15 @@ static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy, } switch (type) { case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_MESH_POINT: return ERR_PTR(-EOPNOTSUPP); case NL80211_IFTYPE_MONITOR: return brcmf_mon_add_vif(wiphy, name); + case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: - wdev = brcmf_ap_add_vif(wiphy, name, params); + wdev = brcmf_apsta_add_vif(wiphy, name, params, type); break; case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: @@ -763,8 +1229,11 @@ void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) struct brcmf_pub *drvr = ifp->drvr; s32 err = 0; + ifp->drvr->req_mpc = mpc; if (check_vif_up(ifp->vif)) { - err = brcmf_fil_iovar_int_set(ifp, "mpc", mpc); + err = brcmf_fil_iovar_int_set(ifp, + "mpc", + ifp->drvr->req_mpc); if (err) { bphy_err(drvr, "fail to set mpc\n"); return; @@ -773,6 +1242,21 @@ void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) } } +bool brcmf_is_apmode_operating(struct wiphy *wiphy) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_cfg80211_vif *vif; + bool ret = false; + + list_for_each_entry(vif, &cfg->vif_list, list) { + if (brcmf_is_apmode(vif) && + test_bit(BRCMF_VIF_STATUS_AP_CREATED, &vif->sme_state)) + ret = true; + } + + return ret; +} + s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, bool aborted, bool fw_abort) @@ -851,8 +1335,8 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, return err; } -static int brcmf_cfg80211_del_ap_iface(struct wiphy *wiphy, - struct wireless_dev *wdev) +static int brcmf_cfg80211_del_apsta_iface(struct wiphy *wiphy, + struct wireless_dev *wdev) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = wdev->netdev; @@ -909,15 +1393,15 @@ int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) switch (wdev->iftype) { case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_MESH_POINT: return -EOPNOTSUPP; case NL80211_IFTYPE_MONITOR: return brcmf_mon_del_vif(wiphy, wdev); + case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_AP: - return brcmf_cfg80211_del_ap_iface(wiphy, wdev); + return brcmf_cfg80211_del_apsta_iface(wiphy, wdev); case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_P2P_DEVICE: @@ -1022,6 +1506,7 @@ brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, ndev->ieee80211_ptr->iftype = type; brcmf_cfg80211_update_proto_addr_mode(&vif->wdev); + brcmf_setup_wiphybands(cfg); done: brcmf_dbg(TRACE, "Exit\n"); @@ -1215,11 +1700,6 @@ brcmf_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) if (err) goto scan_out; - err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBREQ_FLAG, - request->ie, request->ie_len); - if (err) - goto scan_out; - err = brcmf_do_escan(vif->ifp, request); if (err) goto scan_out; @@ -1351,14 +1831,13 @@ static int brcmf_set_pmk(struct brcmf_if *ifp, const u8 *pmk_data, u16 pmk_len) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_wsec_pmk_le pmk; - int err; + int i, err; - memset(&pmk, 0, sizeof(pmk)); - - /* pass pmk directly */ - pmk.key_len = cpu_to_le16(pmk_len); - pmk.flags = cpu_to_le16(0); - memcpy(pmk.key, pmk_data, pmk_len); + /* convert to firmware key format */ + pmk.key_len = cpu_to_le16(pmk_len << 1); + pmk.flags = cpu_to_le16(BRCMF_WSEC_PASSPHRASE); + for (i = 0; i < pmk_len; i++) + snprintf(&pmk.key[2 * i], 3, "%02x", pmk_data[i]); /* store psk in firmware */ err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_WSEC_PMK, @@ -1421,6 +1900,8 @@ static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason, locally_generated, GFP_KERNEL); } clear_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state); clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0); if (vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { @@ -1428,6 +1909,7 @@ static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason, brcmf_set_pmk(vif->ifp, NULL, 0); vif->profile.use_fwsup = BRCMF_PROFILE_FWSUP_NONE; } + brcmf_dbg(TRACE, "Exit\n"); } @@ -1615,14 +2097,20 @@ static s32 brcmf_set_wpa_version(struct net_device *ndev, s32 val = 0; s32 err = 0; - if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) { val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; - else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) - val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; - else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) { + if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE) + val = WPA3_AUTH_SAE_PSK; + else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_OWE) + val = WPA3_AUTH_OWE; + else + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + } else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) { val = WPA3_AUTH_SAE_PSK; - else + } else { val = WPA_AUTH_DISABLED; + } brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", val); if (err) { @@ -1685,6 +2173,8 @@ brcmf_set_wsec_mode(struct net_device *ndev, s32 gval = 0; s32 wsec; s32 err = 0; + u32 algos = 0, mask = 0; + if (sme->crypto.n_ciphers_pairwise) { switch (sme->crypto.ciphers_pairwise[0]) { @@ -1701,6 +2191,15 @@ brcmf_set_wsec_mode(struct net_device *ndev, case WLAN_CIPHER_SUITE_AES_CMAC: pval = AES_ENABLED; break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + return -EOPNOTSUPP; + } + pval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; default: bphy_err(drvr, "invalid cipher pairwise (%d)\n", sme->crypto.ciphers_pairwise[0]); @@ -1722,6 +2221,15 @@ brcmf_set_wsec_mode(struct net_device *ndev, case WLAN_CIPHER_SUITE_AES_CMAC: gval = AES_ENABLED; break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + return -EOPNOTSUPP; + } + gval = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; default: bphy_err(drvr, "invalid cipher group (%d)\n", sme->crypto.cipher_group); @@ -1730,6 +2238,7 @@ brcmf_set_wsec_mode(struct net_device *ndev, } brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval); + brcmf_dbg(CONN, "algos (0x%x) mask (0x%x)\n", algos, mask); /* In case of privacy, but no security and WPS then simulate */ /* setting AES. WPS-2.0 allows no security */ if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval && @@ -1743,6 +2252,17 @@ brcmf_set_wsec_mode(struct net_device *ndev, return err; } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_dbg(CONN, + "set_wsec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = wl_set_wsec_info_algos(ifp, algos, mask); + if (err) { + brcmf_err("set wsec_info error (%d)\n", err); + return err; + } + } + sec = &profile->sec; sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; sec->cipher_group = sme->crypto.cipher_group; @@ -1758,6 +2278,7 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) struct brcmf_pub *drvr = ifp->drvr; s32 val; s32 err; + s32 okc_enable; const struct brcmf_tlv *rsn_ie; const u8 *ie; u32 ie_len; @@ -1765,9 +2286,12 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) u16 rsn_cap; u32 mfp; u16 count; + u16 pmkid_count; + const u8 *group_mgmt_cs = NULL; profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; profile->is_ft = false; + profile->is_okc = false; if (!sme->crypto.n_akm_suites) return 0; @@ -1783,6 +2307,8 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK: val = WPA_AUTH_PSK; @@ -1798,11 +2324,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA2_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_8021X_SHA256: val = WPA2_AUTH_1X_SHA256; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK_SHA256: val = WPA2_AUTH_PSK_SHA256; @@ -1815,10 +2345,31 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) profile->is_ft = true; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_FT_PSK: val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_DPP: + val = WFA_AUTH_DPP; + profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; + break; + case WLAN_AKM_SUITE_OWE: + val = WPA3_AUTH_OWE; + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; + break; + case WLAN_AKM_SUITE_8021X_SUITE_B_192: + val = WPA3_AUTH_1X_SUITE_B_SHA384; + if (sme->want_1x) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; default: bphy_err(drvr, "invalid akm suite (%d)\n", @@ -1832,6 +2383,10 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) if (sme->crypto.sae_pwd) { brcmf_dbg(INFO, "using SAE offload\n"); profile->use_fwsup = BRCMF_PROFILE_FWSUP_SAE; + } else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP) && + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) { + brcmf_dbg(INFO, "using EXTSAE with PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } break; case WLAN_AKM_SUITE_FT_OVER_SAE: @@ -1849,8 +2404,29 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) } } - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) || + (profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM)) { brcmf_dbg(INFO, "using 1X offload\n"); + err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + bphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } else if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE && + (val == WPA3_AUTH_SAE_PSK)) { + brcmf_dbg(INFO, "not using SAE offload\n"); + err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable", + &okc_enable); + if (err) { + bphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) goto skip_mfp_config; @@ -1884,18 +2460,50 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) mfp = BRCMF_MFP_REQUIRED; else if (rsn_cap & RSN_CAP_MFPC_MASK) mfp = BRCMF_MFP_CAPABLE; + + /* In case of dpp, very low tput is observed if MFPC is set in + * firmmare. Firmware needs to ensure that MFPC is not set when + * MFPR was requested from fmac. However since this change being + * specific to DPP, fmac needs to set wpa_auth prior to mfp, so + * that firmware can use this info to prevent MFPC being set in + * case of dpp. + */ + if (val == WFA_AUTH_DPP) { + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); + if (err) { + bphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } + } brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp); -skip_mfp_config: - brcmf_dbg(CONN, "setting wpa_auth to %d\n", val); - err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); - if (err) { - bphy_err(drvr, "could not set wpa_auth (%d)\n", err); - return err; + offset += RSN_CAP_LEN; + if (mfp && (ie_len - offset >= RSN_PMKID_COUNT_LEN)) { + pmkid_count = ie[offset] + (ie[offset + 1] << 8); + offset += RSN_PMKID_COUNT_LEN + (pmkid_count * WLAN_PMKID_LEN); + if (ie_len - offset >= WPA_IE_MIN_OUI_LEN) { + group_mgmt_cs = &ie[offset]; + if (memcmp(group_mgmt_cs, RSN_OUI, TLV_OUI_LEN) == 0) { + brcmf_fil_bsscfg_data_set(ifp, "bip", + (void *)group_mgmt_cs, + WPA_IE_MIN_OUI_LEN); + } + } } - return err; -} +skip_mfp_config: + if (val != WFA_AUTH_DPP) { + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); + if (err) { + bphy_err(drvr, "could not set wpa_auth (%d)\n", err); + return err; + } + } + + return err; +} static s32 brcmf_set_sharedkey(struct net_device *ndev, @@ -2022,6 +2630,43 @@ static void brcmf_set_join_pref(struct brcmf_if *ifp, bphy_err(drvr, "Set join_pref error (%d)\n", err); } +static bool +wl_cfg80211_is_oce_ap(struct brcmf_if *ifp, + struct wiphy *wiphy, const u8 *bssid_hint) +{ + struct brcmf_pub *drvr = ifp->drvr; + const struct brcmf_tlv *ie; + const struct cfg80211_bss_ies *ies; + struct cfg80211_bss *bss; + const u8 *parse = NULL; + u32 len; + + bss = cfg80211_get_bss(wiphy, NULL, bssid_hint, 0, 0, + IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); + if (!bss) { + bphy_err(drvr, "Unable to find AP in the cache"); + return false; + } + + if (rcu_access_pointer(bss->ies)) { + ies = rcu_access_pointer(bss->ies); + parse = ies->data; + len = ies->len; + } else { + bphy_err(drvr, "ies is NULL"); + return false; + } + + while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { + if (wl_cfgoce_is_oce_ie((const u8 *)ie, + (u8 const **)&parse, &len) == true) { + return true; + } + } + brcmf_dbg(TRACE, "OCE IE NOT found"); + return false; +} + static s32 brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_connect_params *sme) @@ -2041,6 +2686,7 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, u16 chanspec; s32 err = 0; u32 ssid_len; + bool skip_hints = ifp->drvr->settings->fw_ap_select; brcmf_dbg(TRACE, "Enter\n"); if (!check_vif_up(ifp->vif)) @@ -2051,6 +2697,20 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, return -EOPNOTSUPP; } + /* override bssid_hint for oce networks */ + skip_hints = (skip_hints && + wl_cfg80211_is_oce_ap(ifp, wiphy, sme->bssid_hint)); + if (skip_hints) { + /* Let fw choose the best AP */ + brcmf_dbg(TRACE, "Skipping bssid & channel hint\n"); + } else { + if (sme->channel_hint) + chan = sme->channel_hint; + + if (sme->bssid_hint) + sme->bssid = sme->bssid_hint; + } + if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) { /* A normal (non P2P) connection request setup. */ ie = NULL; @@ -2126,44 +2786,50 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, goto done; } - if (sme->crypto.psk && - profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) { - err = -EINVAL; - goto done; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + if (sme->crypto.psk) { + if ((profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) && + (profile->use_fwsup != BRCMF_PROFILE_FWSUP_PSK)) { + if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_NONE)) { + err = -EINVAL; + goto done; + } + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + } } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; - } - if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { - /* enable firmware supplicant for this interface */ - err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); - if (err < 0) { - bphy_err(drvr, "failed to enable fw supplicant\n"); - goto done; + if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { + /* enable firmware supplicant for this interface */ + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1); + if (err < 0) { + bphy_err(drvr, "failed to enable fw supplicant\n"); + goto done; + } + } else { + err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0); } - } - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) - err = brcmf_set_pmk(ifp, sme->crypto.psk, - BRCMF_WSEC_MAX_PSK_LEN); - else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { - /* clean up user-space RSNE */ - err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0); - if (err) { - bphy_err(drvr, "failed to clean up user-space RSNE\n"); - goto done; - } - err = brcmf_set_sae_password(ifp, sme->crypto.sae_pwd, - sme->crypto.sae_pwd_len); - if (!err && sme->crypto.psk) + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) && + sme->crypto.psk) err = brcmf_set_pmk(ifp, sme->crypto.psk, BRCMF_WSEC_MAX_PSK_LEN); + else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) { + /* clean up user-space RSNE */ + if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) { + bphy_err(drvr, "failed to clean up user-space RSNE\n"); + goto done; + } + err = brcmf_set_sae_password(ifp, sme->crypto.sae_pwd, + sme->crypto.sae_pwd_len); + if (!err && sme->crypto.psk) + err = brcmf_set_pmk(ifp, sme->crypto.psk, + BRCMF_WSEC_MAX_PSK_LEN); + } + if (err) + goto done; } - if (err) - goto done; - /* Join with specific BSSID and cached SSID * If SSID is zero join based on BSSID only */ @@ -2194,17 +2860,25 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, if (cfg->channel) { ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1); - ext_join_params->assoc_le.chanspec_list[0] = cpu_to_le16(chanspec); + /* Increase dwell time to receive probe response or detect * beacon from target AP at a noisy air only during connect * command. */ - ext_join_params->scan_le.active_time = - cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); - ext_join_params->scan_le.passive_time = - cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); + if (BRCMU_CHSPEC_IS6G(chanspec)) { + ext_join_params->scan_le.active_time = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS_6E); + ext_join_params->scan_le.passive_time = + cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS_6E); + } else { + ext_join_params->scan_le.active_time = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); + ext_join_params->scan_le.passive_time = + cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); + } + /* To sync with presence period of VSDB GO send probe request * more frequently. Probe request will be stopped when it gets * probe response from target AP/GO. @@ -2220,8 +2894,18 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, brcmf_set_join_pref(ifp, &sme->bss_select); - err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params, - join_params_size); + /* The internal supplicant judges to use assoc or reassoc itself. + * it is not necessary to specify REASSOC + */ + if ((sme->prev_bssid) && !brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { + brcmf_dbg(CONN, "Trying to REASSOC\n"); + join_params_size = sizeof(ext_join_params->assoc_le); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_REASSOC, + &ext_join_params->assoc_le, join_params_size); + } else { + err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params, + join_params_size); + } kfree(ext_join_params); if (!err) /* This is it. join command worked, we are done */ @@ -2273,14 +2957,22 @@ brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, clear_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, &ifp->vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &ifp->vif->sme_state); cfg80211_disconnected(ndev, reason_code, NULL, 0, true, GFP_KERNEL); memcpy(&scbval.ea, &profile->bssid, ETH_ALEN); scbval.val = cpu_to_le32(reason_code); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_DISASSOC, &scbval, sizeof(scbval)); - if (err) + if (err) { bphy_err(drvr, "error (%d)\n", err); + } else { + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TWT)) { + /* Cleanup TWT Session list */ + brcmf_twt_cleanup_sessions(ifp); + } + } brcmf_dbg(TRACE, "Exit\n"); return err; @@ -2450,6 +3142,7 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, s32 err; u8 keybuf[8]; bool ext_key; + u32 algos = 0, mask = 0; brcmf_dbg(TRACE, "Enter\n"); brcmf_dbg(CONN, "key index (%d)\n", key_idx); @@ -2531,6 +3224,30 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, val = AES_ENABLED; brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); break; + case WLAN_CIPHER_SUITE_GCMP_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_AES_GCM256; + val = AES_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_GCMP_256\n"); + algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + break; + case WLAN_CIPHER_SUITE_BIP_GMAC_256: + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_err("the low layer not support GCMP\n"); + err = -EOPNOTSUPP; + goto done; + } + key->algo = CRYPTO_ALGO_BIP_GMAC256; + val = AES_ENABLED; + algos = KEY_ALGO_MASK(CRYPTO_ALGO_BIP_GMAC256); + mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM); + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_BIP_GMAC_256\n"); + break; default: bphy_err(drvr, "Invalid cipher (0x%x)\n", params->cipher); err = -EINVAL; @@ -2553,6 +3270,17 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, goto done; } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) { + brcmf_dbg(CONN, + "set_wsdec_info algos (0x%x) mask (0x%x)\n", + algos, mask); + err = wl_set_wsec_info_algos(ifp, algos, mask); + if (err) { + brcmf_err("set wsec_info error (%d)\n", err); + return err; + } + } + done: brcmf_dbg(TRACE, "Exit\n"); return err; @@ -2725,7 +3453,8 @@ static void brcmf_fill_bss_param(struct brcmf_if *ifp, struct station_info *si) static s32 brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp, - struct station_info *sinfo) + struct station_info *sinfo, + const u8 *mac) { struct brcmf_pub *drvr = ifp->drvr; struct brcmf_scb_val_le scbval; @@ -2744,6 +3473,7 @@ brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp, sinfo->txrate.legacy = rate * 5; memset(&scbval, 0, sizeof(scbval)); + memcpy(&scbval.ea[0], mac, ETH_ALEN); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, &scbval, sizeof(scbval)); if (err) { @@ -2795,7 +3525,7 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, return -EIO; if (brcmf_is_ibssmode(ifp->vif)) - return brcmf_cfg80211_get_station_ibss(ifp, sinfo); + return brcmf_cfg80211_get_station_ibss(ifp, sinfo, mac); memset(&sta_info_le, 0, sizeof(sta_info_le)); memcpy(&sta_info_le, mac, ETH_ALEN); @@ -2876,6 +3606,7 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, } else if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) { memset(&scb_val, 0, sizeof(scb_val)); + memcpy(&scb_val.ea[0], mac, ETH_ALEN); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, &scb_val, sizeof(scb_val)); if (err) { @@ -2956,12 +3687,13 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, goto done; } - pm = enabled ? PM_FAST : PM_OFF; + pm = enabled ? ifp->drvr->settings->default_pm : PM_OFF; /* Do not enable the power save after assoc if it is a p2p interface */ if (ifp->vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) { brcmf_dbg(INFO, "Do not enable power save for P2P clients\n"); pm = PM_OFF; } + brcmf_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled")); err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, pm); @@ -2972,11 +3704,6 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, bphy_err(drvr, "error (%d)\n", err); } - err = brcmf_fil_iovar_int_set(ifp, "pm2_sleep_ret", - min_t(u32, timeout, BRCMF_PS_MAX_TIMEOUT_MS)); - if (err) - bphy_err(drvr, "Unable to set pm timeout, (%d)\n", err); - done: brcmf_dbg(TRACE, "Exit\n"); return err; @@ -2997,26 +3724,28 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, u8 *notify_ie; size_t notify_ielen; struct cfg80211_inform_bss bss_data = {}; + const struct brcmf_tlv *ssid = NULL; if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) { bphy_err(drvr, "Bss info is larger than buffer. Discarding\n"); return -EINVAL; } - if (!bi->ctl_ch) { - ch.chspec = le16_to_cpu(bi->chanspec); - cfg->d11inf.decchspec(&ch); + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + if (!bi->ctl_ch) bi->ctl_ch = ch.control_ch_num; - } - channel = bi->ctl_ch; - - if (channel <= CH_MAX_2G_CHANNEL) - band = NL80211_BAND_2GHZ; - else - band = NL80211_BAND_5GHZ; + channel = bi->ctl_ch; + band = BRCMU_CHAN_BAND_TO_NL80211(ch.band); freq = ieee80211_channel_to_frequency(channel, band); + if (!freq) + return -EINVAL; + bss_data.chan = ieee80211_get_channel(wiphy, freq); + if (!bss_data.chan) + return -EINVAL; + bss_data.scan_width = NL80211_BSS_CHAN_WIDTH_20; bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime()); @@ -3026,6 +3755,12 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, notify_ielen = le32_to_cpu(bi->ie_length); bss_data.signal = (s16)le16_to_cpu(bi->RSSI) * 100; + ssid = brcmf_parse_tlvs(notify_ie, notify_ielen, WLAN_EID_SSID); + if (ssid && ssid->data[0] == '\0' && ssid->len == bi->SSID_len) { + /* Update SSID for hidden AP */ + memcpy((u8 *)ssid->data, bi->SSID, bi->SSID_len); + } + brcmf_dbg(CONN, "bssid: %pM\n", bi->BSSID); brcmf_dbg(CONN, "Channel: %d(%d)\n", channel, freq); brcmf_dbg(CONN, "Capability: %X\n", notify_capability); @@ -3105,7 +3840,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); if (buf == NULL) { err = -ENOMEM; - goto CleanUp; + goto cleanup; } *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); @@ -3114,7 +3849,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, buf, WL_BSS_INFO_MAX); if (err) { bphy_err(drvr, "WLC_GET_BSS_INFO failed: %d\n", err); - goto CleanUp; + goto cleanup; } bi = (struct brcmf_bss_info_le *)(buf + 4); @@ -3122,14 +3857,18 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); - if (ch.band == BRCMU_CHAN_BAND_2G) - band = wiphy->bands[NL80211_BAND_2GHZ]; - else - band = wiphy->bands[NL80211_BAND_5GHZ]; - + band = wiphy->bands[BRCMU_CHAN_BAND_TO_NL80211(ch.band)]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (!freq) { + err = -EINVAL; + goto cleanup; + } cfg->channel = freq; notify_channel = ieee80211_get_channel(wiphy, freq); + if (!notify_channel) { + err = -EINVAL; + goto cleanup; + } notify_capability = le16_to_cpu(bi->capability); notify_interval = le16_to_cpu(bi->beacon_period); @@ -3150,12 +3889,12 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg, if (!bss) { err = -ENOMEM; - goto CleanUp; + goto cleanup; } cfg80211_put_bss(wiphy, bss); -CleanUp: +cleanup: kfree(buf); @@ -3392,6 +4131,13 @@ static struct cfg80211_scan_request * brcmf_alloc_internal_escan_request(struct wiphy *wiphy, u32 n_netinfo) { struct cfg80211_scan_request *req; size_t req_size; + size_t size_sanity = ~0; + + if (n_netinfo > ((size_sanity - sizeof(*req)) / + (sizeof(req->channels[0]) + sizeof(*req->ssids)))) { + brcmf_err("requesting a huge count:%d\n", n_netinfo); + return NULL; + } req_size = sizeof(*req) + n_netinfo * sizeof(req->channels[0]) + @@ -3542,9 +4288,18 @@ brcmf_notify_sched_scan_results(struct brcmf_if *ifp, } netinfo_start = brcmf_get_netinfo_array(pfn_result); - datalen = e->datalen - ((void *)netinfo_start - (void *)pfn_result); - if (datalen < result_count * sizeof(*netinfo)) { - bphy_err(drvr, "insufficient event data\n"); + /* To make sure e->datalen is big enough */ + if (e->datalen >= ((void *)netinfo_start - (void *)pfn_result)) { + u32 cnt_sanity = ~0; + + datalen = e->datalen - ((void *)netinfo_start - (void *)pfn_result); + if (datalen < result_count * sizeof(*netinfo) || + (result_count > cnt_sanity / sizeof(*netinfo))) { + brcmf_err("insufficient event data\n"); + goto out_err; + } + } else { + brcmf_err("insufficient event data\n"); goto out_err; } @@ -3807,17 +4562,34 @@ static s32 brcmf_cfg80211_resume(struct wiphy *wiphy) struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_bus *bus_if = drvr->bus_if; + struct brcmf_cfg80211_info *config = drvr->config; + int retry = BRCMF_PM_WAIT_MAXRETRY; + s32 power_mode; + + power_mode = cfg->pwr_save ? ifp->drvr->settings->default_pm : PM_OFF; brcmf_dbg(TRACE, "Enter\n"); + config->pm_state = BRCMF_CFG80211_PM_STATE_RESUMING; + if (cfg->wowl.active) { + /* wait for bus resumed */ + while (retry && bus_if->state != BRCMF_BUS_UP) { + usleep_range(10000, 20000); + retry--; + } + if (!retry && bus_if->state != BRCMF_BUS_UP) + brcmf_err("timed out wait for bus resume\n"); + brcmf_report_wowl_wakeind(wiphy, ifp); brcmf_fil_iovar_int_set(ifp, "wowl_clear", 0); brcmf_config_wowl_pattern(ifp, "clr", NULL, 0, NULL, 0); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_ARP_ND)) brcmf_configure_arp_nd_offload(ifp, true); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, - cfg->wowl.pre_pmmode); + power_mode); cfg->wowl.active = false; if (cfg->wowl.nd_enabled) { brcmf_cfg80211_sched_scan_stop(cfg->wiphy, ifp->ndev, 0); @@ -3826,7 +4598,12 @@ static s32 brcmf_cfg80211_resume(struct wiphy *wiphy) brcmf_notify_sched_scan_results); cfg->wowl.nd_enabled = false; } + + /* disable packet filters */ + brcmf_pktfilter_enable(ifp->ndev, false); + } + config->pm_state = BRCMF_CFG80211_PM_STATE_RESUMED; return 0; } @@ -3842,7 +4619,6 @@ static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg, if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_ARP_ND)) brcmf_configure_arp_nd_offload(ifp, false); - brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PM, &cfg->wowl.pre_pmmode); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, PM_MAX); wowl_config = 0; @@ -3884,6 +4660,9 @@ static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg, brcmf_fil_iovar_int_set(ifp, "wowl_activate", 1); brcmf_bus_wowl_config(cfg->pub->bus_if, true); cfg->wowl.active = true; + + /* enable packet filters */ + brcmf_pktfilter_enable(ifp->ndev, true); } static int brcmf_keepalive_start(struct brcmf_if *ifp, unsigned int interval) @@ -3911,9 +4690,12 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, struct net_device *ndev = cfg_to_ndev(cfg); struct brcmf_if *ifp = netdev_priv(ndev); struct brcmf_cfg80211_vif *vif; + struct brcmf_cfg80211_info *config = ifp->drvr->config; brcmf_dbg(TRACE, "Enter\n"); + config->pm_state = BRCMF_CFG80211_PM_STATE_SUSPENDING; + /* if the primary net_device is not READY there is nothing * we can do but pray resume goes smoothly. */ @@ -3928,7 +4710,8 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) brcmf_abort_scanning(cfg); - if (wowl == NULL) { + if (!wowl || !test_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state)) { brcmf_bus_wowl_config(cfg->pub->bus_if, false); list_for_each_entry(vif, &cfg->vif_list, list) { if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) @@ -3948,17 +4731,22 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, brcmf_set_mpc(ifp, 1); } else { - /* Configure WOWL paramaters */ - brcmf_configure_wowl(cfg, ifp, wowl); + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL)) + /* Configure WOWL parameters */ + brcmf_configure_wowl(cfg, ifp, wowl); /* Prevent disassociation due to inactivity with keep-alive */ brcmf_keepalive_start(ifp, 30); } exit: - brcmf_dbg(TRACE, "Exit\n"); + /* set cfg80211 pm state to cfg80211 suspended state */ + config->pm_state = BRCMF_CFG80211_PM_STATE_SUSPENDED; + /* clear any scanning activity */ cfg->scan_status = 0; + + brcmf_dbg(TRACE, "Exit\n"); return 0; } @@ -4011,11 +4799,23 @@ brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, return -EINVAL; } - brcmf_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmk[npmk].bssid); - brcmf_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmk[npmk].pmkid); + brcmf_dbg(CONN, "set_pmksa - PMK bssid: %pM =\n", pmk[i].bssid); + brcmf_dbg(CONN, "%*ph\n", WLAN_PMKID_LEN, pmk[i].pmkid); err = brcmf_update_pmklist(cfg, ifp); + if (pmksa->pmk_len && pmksa->pmk_len < BRCMF_WSEC_PMK_LEN_SUITEB_192) { + /* external supplicant stores SUITEB-192 PMK */ + if (ifp->vif->profile.is_okc) { + err = brcmf_fil_iovar_data_set(ifp, "okc_info_pmk", pmksa->pmk, + pmksa->pmk_len); + if (err < 0) + bphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", err); + } else { + brcmf_set_pmk(ifp, pmksa->pmk, pmksa->pmk_len); + } + } + brcmf_dbg(TRACE, "Exit\n"); return err; } @@ -4121,6 +4921,12 @@ static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie) return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0); } +static bool brcmf_valid_dpp_suite(u8 *oui) +{ + return (memcmp(oui, WFA_OUI, TLV_OUI_LEN) == 0 && + *(oui + TLV_OUI_LEN) == DPP_AKM_SUITE_TYPE); +} + static s32 brcmf_configure_wpaie(struct brcmf_if *ifp, const struct brcmf_vs_tlv *wpa_ie, @@ -4234,42 +5040,47 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, goto exit; } for (i = 0; i < count; i++) { - if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + if (brcmf_valid_dpp_suite(&data[offset])) { + wpa_auth |= WFA_AUTH_DPP; + offset += TLV_OUI_LEN; + } else if (brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + offset += TLV_OUI_LEN; + switch (data[offset]) { + case RSN_AKM_NONE: + brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); + wpa_auth |= WPA_AUTH_NONE; + break; + case RSN_AKM_UNSPECIFIED: + brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); + is_rsn_ie ? + (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : + (wpa_auth |= WPA_AUTH_UNSPECIFIED); + break; + case RSN_AKM_PSK: + brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); + is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : + (wpa_auth |= WPA_AUTH_PSK); + break; + case RSN_AKM_SHA256_PSK: + brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); + wpa_auth |= WPA2_AUTH_PSK_SHA256; + break; + case RSN_AKM_SHA256_1X: + brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); + wpa_auth |= WPA2_AUTH_1X_SHA256; + break; + case RSN_AKM_SAE: + brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); + wpa_auth |= WPA3_AUTH_SAE_PSK; + break; + default: + bphy_err(drvr, "Invalid key mgmt info\n"); + } + } else { err = -EINVAL; bphy_err(drvr, "ivalid OUI\n"); goto exit; } - offset += TLV_OUI_LEN; - switch (data[offset]) { - case RSN_AKM_NONE: - brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); - wpa_auth |= WPA_AUTH_NONE; - break; - case RSN_AKM_UNSPECIFIED: - brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : - (wpa_auth |= WPA_AUTH_UNSPECIFIED); - break; - case RSN_AKM_PSK: - brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); - is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : - (wpa_auth |= WPA_AUTH_PSK); - break; - case RSN_AKM_SHA256_PSK: - brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n"); - wpa_auth |= WPA2_AUTH_PSK_SHA256; - break; - case RSN_AKM_SHA256_1X: - brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n"); - wpa_auth |= WPA2_AUTH_1X_SHA256; - break; - case RSN_AKM_SAE: - brcmf_dbg(TRACE, "RSN_AKM_SAE\n"); - wpa_auth |= WPA3_AUTH_SAE_PSK; - break; - default: - bphy_err(drvr, "Invalid key mgmt info\n"); - } offset++; } @@ -4289,10 +5100,12 @@ brcmf_configure_wpaie(struct brcmf_if *ifp, */ if (!(wpa_auth & (WPA2_AUTH_PSK_SHA256 | WPA2_AUTH_1X_SHA256 | + WFA_AUTH_DPP | WPA3_AUTH_SAE_PSK))) { err = -EINVAL; goto exit; } + /* Firmware has requirement that WPA2_AUTH_PSK/ * WPA2_AUTH_UNSPECIFIED be set, if SHA256 OUI * is to be included in the rsn ie. @@ -4437,6 +5250,68 @@ brcmf_vndr_ie(u8 *iebuf, s32 pktflag, u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd) return ie_len + VNDR_IE_HDR_SIZE; } +static s32 +brcmf_parse_extension_ies(const u8 *extension_ie_buf, u32 extension_ie_len, + struct parsed_extension_ies *extension_ies) +{ + struct brcmf_ext_tlv *ext_ie; + struct brcmf_tlv *ie; + struct parsed_ext_ie_info *parsed_info; + s32 remaining_len; + + remaining_len = (s32)extension_ie_len; + memset(extension_ies, 0, sizeof(*extension_ies)); + + ie = (struct brcmf_tlv *)extension_ie_buf; + while (ie) { + if (ie->id != WLAN_EID_EXTENSION) + goto next; + ext_ie = (struct brcmf_ext_tlv *)ie; + + /* len should be bigger than ext_id + one data */ + if (ext_ie->len < 2) { + brcmf_err("invalid ext_ie ie. length is too small %d\n", + ext_ie->len); + goto next; + } + + /* skip parsing the HE capab, HE_6G_capa & oper IE from upper layer + * to avoid sending it to the FW, as these IEs will be + * added by the FW based on the MAC & PHY capab if HE + * is enabled. + */ + if (ext_ie->ext_id == WLAN_EID_EXT_HE_CAPABILITY || + ext_ie->ext_id == WLAN_EID_EXT_HE_OPERATION || + ext_ie->ext_id == WLAN_EID_EXT_HE_6GHZ_CAPA) + goto next; + + parsed_info = &extension_ies->ie_info[extension_ies->count]; + + parsed_info->ie_ptr = (char *)ext_ie; + parsed_info->ie_len = ext_ie->len + TLV_HDR_LEN; + memcpy(&parsed_info->ie_data, ext_ie, sizeof(*ext_ie)); + + extension_ies->count++; + + brcmf_dbg(TRACE, "** EXT_IE %d, len 0x%02x EXT_ID: %d\n", + parsed_info->ie_data.id, + parsed_info->ie_data.len, + parsed_info->ie_data.ext_id); + + /* temperory parsing at most 5 EXT_ID, will review it.*/ + if (extension_ies->count >= VNDR_IE_PARSE_LIMIT) + break; +next: + remaining_len -= (ie->len + TLV_HDR_LEN); + if (remaining_len <= TLV_HDR_LEN) + ie = NULL; + else + ie = (struct brcmf_tlv *)(((u8 *)ie) + ie->len + + TLV_HDR_LEN); + } + return 0; +} + s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, const u8 *vndr_ie_buf, u32 vndr_ie_len) { @@ -4458,6 +5333,9 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, s32 i; u8 *ptr; int remained_buf_len; + struct parsed_extension_ies new_ext_ies; + struct parsed_extension_ies old_ext_ies; + struct parsed_ext_ie_info *extie_info; if (!vif) return -ENODEV; @@ -4519,6 +5397,13 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, vndrie_info->ie_len); parsed_ie_buf_len += vndrie_info->ie_len; } + brcmf_parse_extension_ies(vndr_ie_buf, vndr_ie_len, &new_ext_ies); + for (i = 0; i < new_ext_ies.count; i++) { + extie_info = &new_ext_ies.ie_info[i]; + memcpy(ptr + parsed_ie_buf_len, extie_info->ie_ptr, + extie_info->ie_len); + parsed_ie_buf_len += extie_info->ie_len; + } } if (mgmt_ie_buf && *mgmt_ie_len) { @@ -4531,6 +5416,8 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, /* parse old vndr_ie */ brcmf_parse_vndr_ies(mgmt_ie_buf, *mgmt_ie_len, &old_vndr_ies); + /* parse old ext_ie */ + brcmf_parse_extension_ies(mgmt_ie_buf, *mgmt_ie_len, &old_ext_ies); /* make a command to delete old ie */ for (i = 0; i < old_vndr_ies.count; i++) { @@ -4548,6 +5435,23 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, curr_ie_buf += del_add_ie_buf_len; total_ie_buf_len += del_add_ie_buf_len; } + /* make a command to delete old extension ie */ + for (i = 0; i < old_ext_ies.count; i++) { + extie_info = &old_ext_ies.ie_info[i]; + + brcmf_dbg(TRACE, "DEL EXT_IE : %d, Len: %d , ext_id:%d\n", + extie_info->ie_data.id, + extie_info->ie_data.len, + extie_info->ie_data.ext_id); + + del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, + pktflag | BRCMF_VNDR_IE_CUSTOM_FLAG, + extie_info->ie_ptr, + extie_info->ie_len, + "del"); + curr_ie_buf += del_add_ie_buf_len; + total_ie_buf_len += del_add_ie_buf_len; + } } *mgmt_ie_len = 0; @@ -4586,6 +5490,39 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, vndrie_info->ie_len); *mgmt_ie_len += vndrie_info->ie_len; + curr_ie_buf += del_add_ie_buf_len; + total_ie_buf_len += del_add_ie_buf_len; + } + /* make a command to add new EXT ie */ + for (i = 0; i < new_ext_ies.count; i++) { + extie_info = &new_ext_ies.ie_info[i]; + + /* verify remained buf size before copy data */ + if (remained_buf_len < (extie_info->ie_data.len + + VNDR_IE_VSIE_OFFSET)) { + bphy_err(drvr, "no space in mgmt_ie_buf: len left %d", + remained_buf_len); + break; + } + remained_buf_len -= (extie_info->ie_len + + VNDR_IE_VSIE_OFFSET); + + brcmf_dbg(TRACE, "ADDED EXT ID : %d, Len: %d, OUI:%d\n", + extie_info->ie_data.id, + extie_info->ie_data.len, + extie_info->ie_data.ext_id); + + del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, + pktflag | BRCMF_VNDR_IE_CUSTOM_FLAG, + extie_info->ie_ptr, + extie_info->ie_len, + "add"); + + /* save the parsed IE in wl struct */ + memcpy(ptr + (*mgmt_ie_len), extie_info->ie_ptr, + extie_info->ie_len); + *mgmt_ie_len += extie_info->ie_len; + curr_ie_buf += del_add_ie_buf_len; total_ie_buf_len += del_add_ie_buf_len; } @@ -4655,6 +5592,90 @@ brcmf_config_ap_mgmt_ie(struct brcmf_cfg80211_vif *vif, return err; } +static s32 +brcmf_parse_configure_sae_pwe(struct brcmf_if *ifp, + struct cfg80211_ap_settings *settings) +{ + s32 err = 0; + const struct brcmf_tlv *rsnx_ie; + const struct brcmf_tlv *ext_rate_ie; + const struct brcmf_tlv *supp_rate_ie; + u8 ie_len, i; + bool support_sae_h2e = false, must_sae_h2e = false; + u32 wpa_auth = 0; + + /* get configured wpa_auth */ + err = brcmf_fil_bsscfg_int_get(ifp, "wpa_auth", &wpa_auth); + if ((wpa_auth & WPA3_AUTH_SAE_PSK) == 0) { + /* wpa_auth is not SAE, ignore sae_pwe. */ + brcmf_dbg(INFO, "wpa_auth is not SAE:0x%x\n", wpa_auth); + return 0; + } + + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) { + err = brcmf_fil_iovar_int_set(ifp, "extsae_pwe", 0); + if (err) { + brcmf_err("extsae_pwe iovar is not supported\n"); + return -EOPNOTSUPP; + } + + rsnx_ie = brcmf_parse_tlvs((u8 *)settings->beacon.tail, + settings->beacon.tail_len, + WLAN_EID_RSNX); + if (rsnx_ie) { + ie_len = rsnx_ie->len; + if (ie_len) { + if (rsnx_ie->data[0] & WLAN_RSNX_CAPA_SAE_H2E) + support_sae_h2e = true; + } + brcmf_dbg(INFO, "found RSNX IE, support_sae_h2e:%d\n", + support_sae_h2e); + } + + /* found rsnx_ie with SAE_H2E, check the bss selector to know if it is a H2E only */ + if (support_sae_h2e) { + supp_rate_ie = brcmf_parse_tlvs((u8 *)settings->beacon.head, + settings->beacon.head_len, + WLAN_EID_SUPP_RATES); + ext_rate_ie = brcmf_parse_tlvs((u8 *)settings->beacon.tail, + settings->beacon.tail_len, + WLAN_EID_EXT_SUPP_RATES); + if (ext_rate_ie) { + ie_len = ext_rate_ie->len; + for (i = 0; i < ie_len; i++) { + if (ext_rate_ie->data[i] == SAE_H2E_ONLY_ENABLE) { + must_sae_h2e = true; + break; + } + } + } + + /* if we cannot found H2E only selector in ext_supp_rate ie. + * traversal supp_rate ie to make sure it really doesn't exist. + */ + if (!must_sae_h2e && supp_rate_ie) { + ie_len = supp_rate_ie->len; + for (i = 0; i < ie_len; i++) { + if (supp_rate_ie->data[i] == SAE_H2E_ONLY_ENABLE) { + must_sae_h2e = true; + break; + } + } + } + brcmf_dbg(INFO, "must_sae_h2e:%d\n", must_sae_h2e); + } + + if (must_sae_h2e) /* support SAE H2E only */ + err = brcmf_fil_iovar_int_set(ifp, "extsae_pwe", 1); + else if (support_sae_h2e) /* support SAE P&H and H2E both */ + err = brcmf_fil_iovar_int_set(ifp, "extsae_pwe", 2); + else /* support SAE P&H only */ + err = brcmf_fil_iovar_int_set(ifp, "extsae_pwe", 0); + } + + return err; +} + static s32 brcmf_parse_configure_security(struct brcmf_if *ifp, struct cfg80211_ap_settings *settings, @@ -4688,6 +5709,10 @@ brcmf_parse_configure_security(struct brcmf_if *ifp, err = brcmf_configure_wpaie(ifp, tmp_ie, true); if (err < 0) return err; + + err = brcmf_parse_configure_sae_pwe(ifp, settings); + if (err < 0) + return err; } } else { brcmf_dbg(TRACE, "No WPA(2) IEs found\n"); @@ -4718,6 +5743,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, bool mbss; int is_11d; bool supports_11d; + struct bcm_xtlv *he_tlv; brcmf_dbg(TRACE, "ctrlchn=%d, center=%d, bw=%d, beacon_interval=%d, dtim_period=%d,\n", settings->chandef.chan->hw_value, @@ -4728,6 +5754,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, settings->inactivity_timeout); dev_role = ifp->vif->wdev.iftype; mbss = ifp->vif->mbss; + brcmf_dbg(TRACE, "mbss %s\n", mbss ? "enabled" : "disabled"); /* store current 11d setting */ if (brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_REGULATORY, @@ -4817,7 +5844,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, err = -EINVAL; goto exit; } - + ifp->isap = false; /* Interface specific setup */ if (dev_role == NL80211_IFTYPE_AP) { if ((brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) && (!mbss)) @@ -4829,22 +5856,22 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, err); goto exit; } - if (!mbss) { - /* Firmware 10.x requires setting channel after enabling - * AP and before bringing interface up. - */ - err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); - if (err < 0) { - bphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", - chanspec, err); - goto exit; - } - } - err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); - if (err < 0) { - bphy_err(drvr, "BRCMF_C_UP error (%d)\n", err); - goto exit; - } + + /* Firmware 10.x requires setting channel after enabling + * AP and before bringing interface up. + */ + err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); + if (err < 0) { + bphy_err(drvr, "Set Channel failed: chspec=%d, %d\n", + chanspec, err); + goto exit; + } + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); + if (err < 0) { + bphy_err(drvr, "BRCMF_C_UP error (%d)\n", err); + goto exit; + } if (crypto->psk) { brcmf_dbg(INFO, "using PSK offload\n"); @@ -4897,7 +5924,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, err); goto exit; } - + ifp->isap = true; brcmf_dbg(TRACE, "AP mode configuration complete\n"); } else if (dev_role == NL80211_IFTYPE_P2P_GO) { err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); @@ -4929,10 +5956,24 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, goto exit; } + ifp->isap = true; brcmf_dbg(TRACE, "GO mode configuration complete\n"); } else { WARN_ON(1); } + /* Set he_bss_color in hostapd */ + if (settings->beacon.he_bss_color.enabled) { + u8 param[8] = {0}; + + he_tlv = (struct bcm_xtlv *)param; + he_tlv->id = cpu_to_le16(IFX_HE_CMD_BSSCOLOR); + he_tlv->len = cpu_to_le16(1); + memcpy(he_tlv->data, &settings->beacon.he_bss_color.color, sizeof(u8)); + err = brcmf_fil_iovar_data_set(ifp, "he", param, sizeof(param)); + + if (err) + brcmf_err("set he bss_color error:%d\n", err); + } brcmf_config_ap_mgmt_ie(ifp->vif, &settings->beacon); set_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); @@ -4942,6 +5983,9 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, if ((err) && (!mbss)) { brcmf_set_mpc(ifp, 1); brcmf_configure_arp_nd_offload(ifp, true); + } else { + cfg->num_softap++; + brcmf_dbg(TRACE, "Num of SoftAP %u\n", cfg->num_softap); } return err; } @@ -4956,6 +6000,7 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, s32 err; struct brcmf_fil_bss_enable_le bss_enable; struct brcmf_join_params join_params; + s32 apsta = 0; brcmf_dbg(TRACE, "Enter\n"); @@ -4972,26 +6017,43 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, profile->use_fwauth = BIT(BRCMF_PROFILE_FWAUTH_NONE); } - if (ifp->vif->mbss) { - err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); - return err; - } + cfg->num_softap--; - /* First BSS doesn't get a full reset */ - if (ifp->bsscfgidx == 0) - brcmf_fil_iovar_int_set(ifp, "closednet", 0); + /* Clear bss configuration and SSID */ + bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx); + bss_enable.enable = cpu_to_le32(0); + err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) + brcmf_err("bss_enable config failed %d\n", err); memset(&join_params, 0, sizeof(join_params)); err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, &join_params, sizeof(join_params)); if (err < 0) bphy_err(drvr, "SET SSID error (%d)\n", err); - err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); - if (err < 0) - bphy_err(drvr, "BRCMF_C_DOWN error %d\n", err); - err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 0); + + if (cfg->num_softap) { + brcmf_dbg(TRACE, "Num of SoftAP %u\n", cfg->num_softap); + return 0; + } + + /* First BSS doesn't get a full reset */ + if (ifp->bsscfgidx == 0) + brcmf_fil_iovar_int_set(ifp, "closednet", 0); + + err = brcmf_fil_iovar_int_get(ifp, "apsta", &apsta); if (err < 0) - bphy_err(drvr, "setting AP mode failed %d\n", err); + brcmf_err("wl apsta failed (%d)\n", err); + + if (!apsta) { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); + if (err < 0) + bphy_err(drvr, "BRCMF_C_DOWN error %d\n", err); + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 0); + if (err < 0) + bphy_err(drvr, "Set AP mode error %d\n", err); + } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MBSS)) brcmf_fil_iovar_int_set(ifp, "mbss", 0); brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_REGULATORY, @@ -5011,8 +6073,8 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev, bphy_err(drvr, "bss_enable config failed %d\n", err); } brcmf_set_mpc(ifp, 1); - brcmf_configure_arp_nd_offload(ifp, true); clear_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); + brcmf_configure_arp_nd_offload(ifp, true); brcmf_net_setcarrier(ifp, false); return err; @@ -5121,9 +6183,13 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, s32 ie_len; struct brcmf_fil_action_frame_le *action_frame; struct brcmf_fil_af_params_le *af_params; - bool ack; + bool ack = false; s32 chan_nr; u32 freq; + struct brcmf_mf_params_le *mf_params; + u32 mf_params_len; + s32 timeout; + u32 hw_channel; brcmf_dbg(TRACE, "Enter\n"); @@ -5184,26 +6250,97 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, /* Add the channel. Use the one specified as parameter if any or * the current one (got from the firmware) otherwise */ - if (chan) + if (chan) { freq = chan->center_freq; - else + chan_nr = ieee80211_frequency_to_channel(freq); + af_params->channel = cpu_to_le32(chan_nr); + } else { brcmf_fil_cmd_int_get(vif->ifp, BRCMF_C_GET_CHANNEL, - &freq); - chan_nr = ieee80211_frequency_to_channel(freq); - af_params->channel = cpu_to_le32(chan_nr); + &hw_channel); + af_params->channel = hw_channel; + } + af_params->dwell_time = cpu_to_le32(params->wait); memcpy(action_frame->data, &buf[DOT11_MGMT_HDR_LEN], le16_to_cpu(action_frame->len)); - brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, freq=%d\n", - *cookie, le16_to_cpu(action_frame->len), freq); + brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, ch=%d\n", + *cookie, le16_to_cpu(action_frame->len), af_params->channel); ack = brcmf_p2p_send_action_frame(cfg, cfg_to_ndev(cfg), - af_params); + af_params, vif, chan); cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, GFP_KERNEL); kfree(af_params); + } else if (ieee80211_is_auth(mgmt->frame_control)) { + reinit_completion(&vif->mgmt_tx); + clear_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status); + clear_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status); + clear_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, + &vif->mgmt_tx_status); + + mf_params_len = offsetof(struct brcmf_mf_params_le, data) + + (len - DOT11_MGMT_HDR_LEN); + mf_params = kzalloc(mf_params_len, GFP_KERNEL); + if (!mf_params) { + err = -ENOMEM; + goto exit; + } + + mf_params->dwell_time = cpu_to_le32(MGMT_AUTH_FRAME_DWELL_TIME); + mf_params->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); + mf_params->frame_control = mgmt->frame_control; + + if (chan) { + freq = chan->center_freq; + chan_nr = ieee80211_frequency_to_channel(freq); + mf_params->channel = cpu_to_le32(chan_nr); + } else { + brcmf_fil_cmd_int_get(vif->ifp, BRCMF_C_GET_CHANNEL, + &hw_channel); + mf_params->channel = hw_channel; + } + + memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN); + memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); + mf_params->packet_id = cpu_to_le32(*cookie); + memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN], + le16_to_cpu(mf_params->len)); + + brcmf_dbg(TRACE, "Auth frame, cookie=%d, fc=%04x, len=%d, channel=%d\n", + le32_to_cpu(mf_params->packet_id), + le16_to_cpu(mf_params->frame_control), + le16_to_cpu(mf_params->len), + le32_to_cpu(mf_params->channel)); + + vif->mgmt_tx_id = le32_to_cpu(mf_params->packet_id); + set_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status); + + err = brcmf_fil_bsscfg_data_set(vif->ifp, "mgmt_frame", + mf_params, mf_params_len); + if (err) { + bphy_err(drvr, "Failed to send Auth frame: err=%d\n", + err); + goto tx_status; + } + + timeout = + wait_for_completion_timeout(&vif->mgmt_tx, + MGMT_AUTH_FRAME_WAIT_TIME); + if (test_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status)) { + brcmf_dbg(TRACE, "TX Auth frame operation is success\n"); + ack = true; + } else { + bphy_err(drvr, "TX Auth frame operation is failed: status=%ld)\n", + vif->mgmt_tx_status); + } + +tx_status: + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + GFP_KERNEL); + kfree(mf_params); + } else { brcmf_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control); brcmf_dbg_hex_dump(true, buf, len, "payload, len=%zu\n", len); @@ -5304,15 +6441,7 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy, ch.chspec = chanspec; cfg->d11inf.decchspec(&ch); - - switch (ch.band) { - case BRCMU_CHAN_BAND_2G: - band = NL80211_BAND_2GHZ; - break; - case BRCMU_CHAN_BAND_5G: - band = NL80211_BAND_5GHZ; - break; - } + band = BRCMU_CHAN_BAND_TO_NL80211(ch.band); switch (ch.bw) { case BRCMU_CHAN_BW_80: @@ -5333,7 +6462,11 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy, } freq = ieee80211_channel_to_frequency(ch.control_ch_num, band); + if (!freq) + return -EINVAL; chandef->chan = ieee80211_get_channel(wiphy, freq); + if (!chandef->chan) + return -EINVAL; chandef->width = width; chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band); chandef->center_freq2 = 0; @@ -5500,17 +6633,30 @@ static int brcmf_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev, const struct cfg80211_pmk_conf *conf) { struct brcmf_if *ifp; + struct brcmf_pub *drvr; + int ret; brcmf_dbg(TRACE, "enter\n"); /* expect using firmware supplicant for 1X */ ifp = netdev_priv(dev); - if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X)) + drvr = ifp->drvr; + if (WARN_ON((ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X) && + (ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_ROAM) && + (ifp->vif->profile.is_ft != true) && + (ifp->vif->profile.is_okc != true))) return -EINVAL; if (conf->pmk_len > BRCMF_WSEC_MAX_PSK_LEN) return -ERANGE; + if (ifp->vif->profile.is_okc) { + ret = brcmf_fil_iovar_data_set(ifp, "okc_info_pmk", conf->pmk, + conf->pmk_len); + if (ret < 0) + bphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", ret); + } + return brcmf_set_pmk(ifp, conf->pmk, conf->pmk_len); } @@ -5527,6 +6673,185 @@ static int brcmf_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev, return brcmf_set_pmk(ifp, NULL, 0); } +static int +brcmf_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev, + struct bss_parameters *params) +{ + struct brcmf_if *ifp; + int ret = 0; + u32 ap_isolate, val; + + brcmf_dbg(TRACE, "Enter\n"); + ifp = netdev_priv(dev); + if (params->ap_isolate >= 0) { + ap_isolate = (u32)params->ap_isolate; + ret = brcmf_fil_iovar_int_set(ifp, "ap_isolate", ap_isolate); + if (ret < 0) + brcmf_err("ap_isolate iovar failed: ret=%d\n", ret); + } + + /* Get ap_isolate value from firmware to detemine whether fmac */ + /* driver supports packet forwarding. */ + if (brcmf_fil_iovar_int_get(ifp, "ap_isolate", &val) == 0) { + ifp->fmac_pkt_fwd_en = + ((params->ap_isolate == 0) && (val == 1)) ? + true : false; + } else { + brcmf_err("get ap_isolate iovar failed: ret=%d\n", ret); + ifp->fmac_pkt_fwd_en = false; + } + + return ret; +} + +static int +brcmf_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_external_auth_params *params) +{ + struct brcmf_if *ifp; + struct brcmf_pub *drvr; + struct brcmf_auth_req_status_le auth_status; + int ret = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + ifp = netdev_priv(dev); + drvr = ifp->drvr; + if (params->status == WLAN_STATUS_SUCCESS) { + auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS); + } else { + bphy_err(drvr, "External authentication failed: status=%d\n", + params->status); + auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_FAIL); + } + + memcpy(auth_status.peer_mac, params->bssid, ETH_ALEN); + auth_status.ssid_len = cpu_to_le32(min_t(u8, params->ssid.ssid_len, + IEEE80211_MAX_SSID_LEN)); + memcpy(auth_status.ssid, params->ssid.ssid, auth_status.ssid_len); + memset(auth_status.pmkid, 0, WLAN_PMKID_LEN); + if (params->pmkid) + memcpy(auth_status.pmkid, params->pmkid, WLAN_PMKID_LEN); + + ret = brcmf_fil_iovar_data_set(ifp, "auth_status", &auth_status, + sizeof(auth_status)); + if (ret < 0) + bphy_err(drvr, "auth_status iovar failed: ret=%d\n", ret); + + return ret; +} + +static int +brcmf_cfg80211_set_bitrate(struct wiphy *wiphy, struct net_device *ndev, + unsigned int link_id, const u8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct brcmf_if *ifp; + u32 he[2] = {0, 0}; + u32 rspec = 0; + s32 ret = TIME_OK; + uint hegi; + u16 mcs_mask; + u8 band, mcs = 0; + + ifp = netdev_priv(ndev); + ret = brcmf_fil_iovar_data_get(ifp, "he", he, sizeof(he)); + if (unlikely(ret)) { + brcmf_dbg(INFO, "error reading he (%d)\n", ret); + return -EOPNOTSUPP; + } + + if (!he[0]) { + brcmf_dbg(INFO, "Only HE supported\n"); + return -EOPNOTSUPP; + } + + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (band != NL80211_BAND_2GHZ && band != NL80211_BAND_5GHZ && + band != NL80211_BAND_6GHZ) { + continue; + } + + /* Skip setting HE rates if legacy rate set is called from userspace. + * Also if any one of 2.4, 5 or 6GHz is being called then other two will have + * an invalid he mask of 0xFFF so skip setting he rates for other two bands. + */ + if (!mask->control[band].he_mcs[0] || mask->control[band].he_mcs[0] == 0xFFF) + continue; + + mcs_mask = mask->control[band].he_mcs[0]; + mcs_mask = (mcs_mask ^ ((mcs_mask - 1) & mcs_mask)); + if (mcs_mask != mask->control[band].he_mcs[0]) + continue; + + while (mcs_mask) { + mcs++; + mcs_mask >>= 1; + } + + rspec = WL_RSPEC_ENCODE_HE; /* 11ax HE */ + rspec |= (WL_RSPEC_HE_NSS_UNSPECIFIED << WL_RSPEC_HE_NSS_SHIFT) | (mcs - 1); + /* set the other rspec fields */ + hegi = mask->control[band].he_gi + 1; + rspec |= ((hegi != 0xFF) ? HE_GI_TO_RSPEC(hegi) : 0); + + if (band == NL80211_BAND_2GHZ) + ret = brcmf_fil_iovar_data_set(ifp, "2g_rate", (char *)&rspec, 4); + + if (band == NL80211_BAND_5GHZ) + ret = brcmf_fil_iovar_data_set(ifp, "5g_rate", (char *)&rspec, 4); + + if (band == NL80211_BAND_6GHZ) + ret = brcmf_fil_iovar_data_set(ifp, "6g_rate", (char *)&rspec, 4); + + if (unlikely(ret)) { + brcmf_dbg(INFO, "%s: set rate failed, retcode = %d\n", + __func__, ret); + return ret; + } + } + return ret; +} + +static int +brcmf_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp; + struct wl_rssi_event rssi; + int err = 0; + + ifp = netdev_priv(dev); + if (rssi_thold == cfg->cqm_info.rssi_threshold) + return err; + + if (rssi_thold == 0) { + rssi.rate_limit_msec = cpu_to_le32(0); + rssi.num_rssi_levels = 0; + rssi.version = WL_RSSI_EVENT_IFX_VERSION; + } else { + rssi.rate_limit_msec = cpu_to_le32(0); + rssi.num_rssi_levels = 3; + rssi.rssi_levels[0] = S8_MIN; + rssi.rssi_levels[1] = rssi_thold; + rssi.rssi_levels[2] = S8_MAX; + rssi.version = WL_RSSI_EVENT_IFX_VERSION; + } + + err = brcmf_fil_iovar_data_set(ifp, "rssi_event", &rssi, sizeof(rssi)); + if (err < 0) { + brcmf_err("set rssi_event iovar failed (%d)\n", err); + } else { + cfg->cqm_info.enable = rssi_thold ? 1 : 0; + cfg->cqm_info.rssi_threshold = rssi_thold; + } + + brcmf_dbg(TRACE, "enable = %d, rssi_threshold = %d\n", + cfg->cqm_info.enable, cfg->cqm_info.rssi_threshold); + return err; +} + static struct cfg80211_ops brcmf_cfg80211_ops = { .add_virtual_intf = brcmf_cfg80211_add_iface, .del_virtual_intf = brcmf_cfg80211_del_iface, @@ -5555,6 +6880,7 @@ static struct cfg80211_ops brcmf_cfg80211_ops = { .start_ap = brcmf_cfg80211_start_ap, .stop_ap = brcmf_cfg80211_stop_ap, .change_beacon = brcmf_cfg80211_change_beacon, + .set_bitrate_mask = brcmf_cfg80211_set_bitrate, .del_station = brcmf_cfg80211_del_station, .change_station = brcmf_cfg80211_change_station, .sched_scan_start = brcmf_cfg80211_sched_scan_start, @@ -5574,6 +6900,9 @@ static struct cfg80211_ops brcmf_cfg80211_ops = { .update_connect_params = brcmf_cfg80211_update_conn_params, .set_pmk = brcmf_cfg80211_set_pmk, .del_pmk = brcmf_cfg80211_del_pmk, + .change_bss = brcmf_cfg80211_change_bss, + .external_auth = brcmf_cfg80211_external_auth, + .set_cqm_rssi_config = brcmf_cfg80211_set_cqm_rssi_config, }; struct cfg80211_ops *brcmf_cfg80211_get_ops(struct brcmf_mp_device *settings) @@ -5620,6 +6949,7 @@ struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg, vif->mbss = mbss; } + init_completion(&vif->mgmt_tx); list_add_tail(&vif->list, &cfg->vif_list); return vif; } @@ -5638,8 +6968,10 @@ void brcmf_cfg80211_free_netdev(struct net_device *ndev) ifp = netdev_priv(ndev); vif = ifp->vif; - if (vif) + if (vif) { brcmf_free_vif(vif); + ifp->vif = NULL; + } } static bool brcmf_is_linkup(struct brcmf_cfg80211_vif *vif, @@ -5889,11 +7221,6 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, (struct brcmf_cfg80211_assoc_ielen_le *)cfg->extra_buf; req_len = le32_to_cpu(assoc_info->req_len); resp_len = le32_to_cpu(assoc_info->resp_len); - if (req_len > WL_EXTRA_BUF_MAX || resp_len > WL_EXTRA_BUF_MAX) { - bphy_err(drvr, "invalid lengths in assoc info: req %u resp %u\n", - req_len, resp_len); - return -EINVAL; - } if (req_len) { err = brcmf_fil_iovar_data_get(ifp, "assoc_req_ies", cfg->extra_buf, @@ -5912,6 +7239,11 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, conn_info->req_ie_len = 0; conn_info->req_ie = NULL; } + + /* resp_len is the total length of assoc resp + * which includes 6 bytes of aid/status code/capabilities. + * the assoc_resp_ie length should minus the 6 bytes which starts from rate_ie. + */ if (resp_len) { err = brcmf_fil_iovar_data_get(ifp, "assoc_resp_ies", cfg->extra_buf, @@ -5920,7 +7252,7 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, bphy_err(drvr, "could not get assoc resp (%d)\n", err); return err; } - conn_info->resp_ie_len = resp_len; + conn_info->resp_ie_len = resp_len - sizeof(struct dot11_assoc_resp); conn_info->resp_ie = kmemdup(cfg->extra_buf, conn_info->resp_ie_len, GFP_KERNEL); @@ -5947,6 +7279,47 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, return err; } +static bool +brcmf_has_pmkid(const u8 *parse, u32 len) +{ + const struct brcmf_tlv *rsn_ie; + const u8 *ie; + u32 ie_len; + u32 offset; + u16 count; + + rsn_ie = brcmf_parse_tlvs(parse, len, WLAN_EID_RSN); + if (!rsn_ie) + goto done; + ie = (const u8 *)rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + /* Skip group data cipher suite */ + offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN; + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto done; + /* Skip pairwise cipher suite(s) */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + goto done; + /* Skip auth key management suite(s) */ + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN); + if (offset + RSN_CAP_LEN >= ie_len) + goto done; + /* Skip rsn capabilities */ + offset += RSN_CAP_LEN; + if (offset + RSN_PMKID_COUNT_LEN > ie_len) + goto done; + /* Extract PMKID count */ + count = ie[offset] + (ie[offset + 1] << 8); + if (count) + return true; + +done: + return false; +} + static s32 brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, @@ -5989,13 +7362,13 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, ch.chspec = le16_to_cpu(bi->chanspec); cfg->d11inf.decchspec(&ch); - if (ch.band == BRCMU_CHAN_BAND_2G) - band = wiphy->bands[NL80211_BAND_2GHZ]; - else - band = wiphy->bands[NL80211_BAND_5GHZ]; - + band = wiphy->bands[BRCMU_CHAN_BAND_TO_NL80211(ch.band)]; freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band); + if (!freq) + err = -EINVAL; notify_channel = ieee80211_get_channel(wiphy, freq); + if (!notify_channel) + err = -EINVAL; done: kfree(buf); @@ -6007,14 +7380,16 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, roam_info.resp_ie = conn_info->resp_ie; roam_info.resp_ie_len = conn_info->resp_ie_len; + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X || + profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) && + (brcmf_has_pmkid(roam_info.req_ie, roam_info.req_ie_len) || + profile->is_ft || profile->is_okc)) + roam_info.authorized = true; + cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); brcmf_dbg(CONN, "Report roaming result\n"); - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && profile->is_ft) { - cfg80211_port_authorized(ndev, profile->bssid, GFP_KERNEL); - brcmf_dbg(CONN, "Report port authorized\n"); - } - + clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); return err; @@ -6042,6 +7417,10 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, &ifp->vif->sme_state); conn_params.status = WLAN_STATUS_SUCCESS; } else { + clear_bit(BRCMF_VIF_STATUS_EAP_SUCCESS, + &ifp->vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, + &ifp->vif->sme_state); conn_params.status = WLAN_STATUS_AUTH_TIMEOUT; } conn_params.links[0].bssid = profile->bssid; @@ -6049,6 +7428,11 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, conn_params.req_ie_len = conn_info->req_ie_len; conn_params.resp_ie = conn_info->resp_ie; conn_params.resp_ie_len = conn_info->resp_ie_len; + + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && + brcmf_has_pmkid(conn_params.req_ie, conn_params.req_ie_len)) + conn_params.authorized = true; + cfg80211_connect_done(ndev, &conn_params, GFP_KERNEL); brcmf_dbg(CONN, "Report connect result - connection %s\n", completed ? "succeeded" : "failed"); @@ -6121,6 +7505,14 @@ brcmf_notify_connect_status(struct brcmf_if *ifp, } if (brcmf_is_apmode(ifp->vif)) { + if (e->event_code == BRCMF_E_ASSOC_IND || + e->event_code == BRCMF_E_REASSOC_IND) { + brcmf_findadd_sta(ifp, e->addr); + } else if ((e->event_code == BRCMF_E_DISASSOC_IND) || + (e->event_code == BRCMF_E_DEAUTH_IND) || + (e->event_code == BRCMF_E_DEAUTH)) { + brcmf_del_sta(ifp, e->addr); + } err = brcmf_notify_connect_status_ap(cfg, ndev, e, data); } else if (brcmf_is_linkup(ifp->vif, e)) { brcmf_dbg(CONN, "Linkup\n"); @@ -6139,9 +7531,13 @@ brcmf_notify_connect_status(struct brcmf_if *ifp, } else if (brcmf_is_linkdown(ifp->vif, e)) { brcmf_dbg(CONN, "Linkdown\n"); if (!brcmf_is_ibssmode(ifp->vif) && - test_bit(BRCMF_VIF_STATUS_CONNECTED, - &ifp->vif->sme_state)) { - if (memcmp(profile->bssid, e->addr, ETH_ALEN)) + (test_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state) || + test_bit(BRCMF_VIF_STATUS_CONNECTING, + &ifp->vif->sme_state))) { + if (test_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state) && + memcmp(profile->bssid, e->addr, ETH_ALEN)) return err; brcmf_bss_connect_done(cfg, ndev, e, false); @@ -6211,20 +7607,18 @@ static s32 brcmf_notify_rssi(struct brcmf_if *ifp, { struct brcmf_cfg80211_vif *vif = ifp->vif; struct brcmf_rssi_be *info = data; - s32 rssi, snr = 0, noise = 0; + s32 rssi, snr, noise; s32 low, high, last; - if (e->datalen >= sizeof(*info)) { - rssi = be32_to_cpu(info->rssi); - snr = be32_to_cpu(info->snr); - noise = be32_to_cpu(info->noise); - } else if (e->datalen >= sizeof(rssi)) { - rssi = be32_to_cpu(*(__be32 *)data); - } else { + if (e->datalen < sizeof(*info)) { brcmf_err("insufficient RSSI event data\n"); return 0; } + rssi = be32_to_cpu(info->rssi); + snr = be32_to_cpu(info->snr); + noise = be32_to_cpu(info->noise); + low = vif->cqm_rssi_low; high = vif->cqm_rssi_high; last = vif->cqm_rssi_last; @@ -6256,6 +7650,9 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, struct brcmf_if_event *ifevent = (struct brcmf_if_event *)data; struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; struct brcmf_cfg80211_vif *vif; + enum nl80211_iftype iftype = NL80211_IFTYPE_UNSPECIFIED; + bool vif_pend = false; + int err; brcmf_dbg(TRACE, "Enter: action %u flags %u ifidx %u bsscfgidx %u\n", ifevent->action, ifevent->flags, ifevent->ifidx, @@ -6268,9 +7665,28 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, switch (ifevent->action) { case BRCMF_E_IF_ADD: /* waiting process may have timed out */ - if (!cfg->vif_event.vif) { + if (!vif) { + /* handle IF_ADD event from firmware */ spin_unlock(&event->vif_event_lock); - return -EBADF; + vif_pend = true; + if (ifevent->role == WLC_E_IF_ROLE_STA) + iftype = NL80211_IFTYPE_STATION; + else if (ifevent->role == WLC_E_IF_ROLE_AP) + iftype = NL80211_IFTYPE_AP; + else + vif_pend = false; + + if (vif_pend) { + vif = brcmf_alloc_vif(cfg, iftype); + if (IS_ERR(vif)) { + brcmf_err("Role:%d failed to alloc vif\n", + ifevent->role); + return PTR_ERR(vif); + } + } else { + brcmf_err("Invalid Role:%d\n", ifevent->role); + return -EBADF; + } } ifp->vif = vif; @@ -6280,6 +7696,18 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, ifp->ndev->ieee80211_ptr = &vif->wdev; SET_NETDEV_DEV(ifp->ndev, wiphy_dev(cfg->wiphy)); } + + if (vif_pend) { + err = brcmf_net_attach(ifp, false); + if (err) { + brcmf_err("netdevice register failed with err:%d\n", + err); + brcmf_free_vif(vif); + free_netdev(ifp->ndev); + } + return err; + } + spin_unlock(&event->vif_event_lock); wake_up(&event->vif_wq); return 0; @@ -6303,17 +7731,203 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, return -EINVAL; } -static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) +static s32 +brcmf_notify_ext_auth_request(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) { - conf->frag_threshold = (u32)-1; - conf->rts_threshold = (u32)-1; - conf->retry_short = (u32)-1; - conf->retry_long = (u32)-1; + struct brcmf_pub *drvr = ifp->drvr; + struct cfg80211_external_auth_params params; + struct brcmf_auth_req_status_le *auth_req = + (struct brcmf_auth_req_status_le *)data; + s32 err = 0; + + brcmf_dbg(INFO, "Enter: event %s (%d) received\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + + if (e->datalen < sizeof(*auth_req)) { + bphy_err(drvr, "Event %s (%d) data too small. Ignore\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + return -EINVAL; + } + + memset(¶ms, 0, sizeof(params)); + params.action = NL80211_EXTERNAL_AUTH_START; + params.key_mgmt_suite = ntohl(WLAN_AKM_SUITE_SAE); + params.status = WLAN_STATUS_SUCCESS; + params.ssid.ssid_len = min_t(u32, 32, le32_to_cpu(auth_req->ssid_len)); + memcpy(params.ssid.ssid, auth_req->ssid, params.ssid.ssid_len); + memcpy(params.bssid, auth_req->peer_mac, ETH_ALEN); + + err = cfg80211_external_auth_request(ifp->ndev, ¶ms, GFP_ATOMIC); + if (err) + bphy_err(drvr, "Ext Auth request to supplicant failed (%d)\n", + err); + + return err; } -static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) +static s32 +brcmf_notify_auth_frame_rx(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) { - brcmf_fweh_register(cfg->pub, BRCMF_E_LINK, + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_cfg80211_info *cfg = drvr->config; + struct wireless_dev *wdev; + u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data); + struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; + u8 *frame = (u8 *)(rxframe + 1); + struct brcmu_chan ch; + struct ieee80211_mgmt *mgmt_frame; + s32 freq; + + brcmf_dbg(INFO, "Enter: event %s (%d) received\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + + if (e->datalen < sizeof(*rxframe)) { + bphy_err(drvr, "Event %s (%d) data too small. Ignore\n", + brcmf_fweh_event_name(e->event_code), e->event_code); + return -EINVAL; + } + + wdev = &ifp->vif->wdev; + WARN_ON(!wdev); + + ch.chspec = be16_to_cpu(rxframe->chanspec); + cfg->d11inf.decchspec(&ch); + + mgmt_frame = kzalloc(mgmt_frame_len, GFP_KERNEL); + if (!mgmt_frame) + return -ENOMEM; + + mgmt_frame->frame_control = cpu_to_le16(IEEE80211_STYPE_AUTH); + memcpy(mgmt_frame->da, ifp->mac_addr, ETH_ALEN); + memcpy(mgmt_frame->sa, e->addr, ETH_ALEN); + brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mgmt_frame->bssid, + ETH_ALEN); + frame += offsetof(struct ieee80211_mgmt, u); + memcpy(&mgmt_frame->u, frame, + mgmt_frame_len - offsetof(struct ieee80211_mgmt, u)); + + freq = ieee80211_channel_to_frequency(ch.control_ch_num, + BRCMU_CHAN_BAND_TO_NL80211(ch.band)); + + cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len, + NL80211_RXMGMT_FLAG_EXTERNAL_AUTH); + kfree(mgmt_frame); + return 0; +} + +static s32 +brcmf_notify_mgmt_tx_status(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_vif *vif = ifp->vif; + u32 *packet_id = (u32 *)data; + + brcmf_dbg(INFO, "Enter: event %s (%d), status=%d\n", + brcmf_fweh_event_name(e->event_code), e->event_code, + e->status); + + if (!test_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status) || + (*packet_id != vif->mgmt_tx_id)) + return 0; + + if (e->event_code == BRCMF_E_MGMT_FRAME_TXSTATUS) { + if (e->status == BRCMF_E_STATUS_SUCCESS) + set_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status); + else + set_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status); + } else { + set_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, &vif->mgmt_tx_status); + } + + complete(&vif->mgmt_tx); + return 0; +} + +static s32 +brcmf_notify_rssi_change_ind(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct wl_event_data_rssi *value = (struct wl_event_data_rssi *)data; + s32 rssi = 0; + + brcmf_dbg(INFO, "Enter: event %s (%d), status=%d\n", + brcmf_fweh_event_name(e->event_code), e->event_code, + e->status); + + if (!cfg->cqm_info.enable) + return 0; + + rssi = ntohl(value->rssi); + brcmf_dbg(TRACE, "rssi: %d, threshold: %d, send event(%s)\n", + rssi, cfg->cqm_info.rssi_threshold, + rssi > cfg->cqm_info.rssi_threshold ? "HIGH" : "LOW"); + cfg80211_cqm_rssi_notify(cfg_to_ndev(cfg), + (rssi > cfg->cqm_info.rssi_threshold ? + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH : + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW), + rssi, GFP_KERNEL); + + return 0; +} + +static s32 +brcmf_notify_beacon_loss(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct cfg80211_bss *bss; + + brcmf_dbg(INFO, "Enter: event %s (%d), status=%d\n", + brcmf_fweh_event_name(e->event_code), e->event_code, + e->status); + + if (!ifp->drvr->settings->roamoff) + return 0; + + /* On beacon loss event, Supplicant triggers new scan request + * with NL80211_SCAN_FLAG_FLUSH Flag set, but lost AP bss entry + * still remained as it is held by cfg as associated. Unlinking this + * current BSS from cfg cached bss list on beacon loss event here, + * would allow supplicant to receive new scanned entries + * without current bss and select new bss to trigger roam. + */ + bss = cfg80211_get_bss(cfg->wiphy, NULL, profile->bssid, 0, 0, + IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); + if (bss) { + cfg80211_unlink_bss(cfg->wiphy, bss); + cfg80211_put_bss(cfg->wiphy, bss); + } + + cfg80211_cqm_beacon_loss_notify(cfg_to_ndev(cfg), GFP_KERNEL); + + return 0; +} + +static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) +{ + conf->frag_threshold = (u32)-1; + conf->rts_threshold = (u32)-1; + conf->retry_short = (u32)-1; + conf->retry_long = (u32)-1; +} + +static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + struct wl_rssi_event rssi_event = {}; + int err = 0; + + /* get supported version from firmware side */ + err = brcmf_fil_iovar_data_get(ifp, "rssi_event", &rssi_event, + sizeof(rssi_event)); + if (err) + brcmf_err("fail to get supported rssi_event version, err=%d\n", err); + + brcmf_fweh_register(cfg->pub, BRCMF_E_LINK, brcmf_notify_connect_status); brcmf_fweh_register(cfg->pub, BRCMF_E_DEAUTH_IND, brcmf_notify_connect_status); @@ -6347,7 +7961,29 @@ static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) brcmf_p2p_notify_action_tx_complete); brcmf_fweh_register(cfg->pub, BRCMF_E_PSK_SUP, brcmf_notify_connect_status); - brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, brcmf_notify_rssi); + if (rssi_event.version == WL_RSSI_EVENT_IFX_VERSION) + brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, + brcmf_notify_rssi_change_ind); + else + brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, + brcmf_notify_rssi); + brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_REQ, + brcmf_notify_ext_auth_request); + brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_FRAME_RX, + brcmf_notify_auth_frame_rx); + brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_TXSTATUS, + brcmf_notify_mgmt_tx_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_OFF_CHAN_COMPLETE, + brcmf_notify_mgmt_tx_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_BCNLOST_MSG, + brcmf_notify_beacon_loss); + + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TWT)) { + brcmf_fweh_register(cfg->pub, BRCMF_E_TWT_SETUP, + brcmf_notify_twt_event); + brcmf_fweh_register(cfg->pub, BRCMF_E_TWT_TEARDOWN, + brcmf_notify_twt_event); + } } static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg) @@ -6534,15 +8170,14 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, struct brcmf_pub *drvr = cfg->pub; struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0); struct ieee80211_supported_band *band; - struct ieee80211_channel *channel; + struct ieee80211_channel *channel, *cur, *next; struct brcmf_chanspec_list *list; struct brcmu_chan ch; int err; u8 *pbuf; u32 i, j; u32 total; - u32 chaninfo; - + u32 chaninfo, n_2g = 0, n_5g = 0, n_6g = 0; pbuf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL); if (pbuf == NULL) @@ -6558,22 +8193,31 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, } band = wiphy->bands[NL80211_BAND_2GHZ]; - if (band) + if (band) { + /* restore channels info */ + memcpy(band->channels, &__wl_2ghz_channels, sizeof(__wl_2ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_2ghz_channels); for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } band = wiphy->bands[NL80211_BAND_5GHZ]; - if (band) + if (band) { + /* restore channels info */ + memcpy(band->channels, &__wl_5ghz_channels, sizeof(__wl_5ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); + for (i = 0; i < band->n_channels; i++) + band->channels[i].flags = IEEE80211_CHAN_DISABLED; + } + band = wiphy->bands[NL80211_BAND_6GHZ]; + if (band) { + /* restore channels info */ + memcpy(band->channels, &__wl_6ghz_channels, sizeof(__wl_6ghz_channels)); + band->n_channels = ARRAY_SIZE(__wl_6ghz_channels); for (i = 0; i < band->n_channels; i++) band->channels[i].flags = IEEE80211_CHAN_DISABLED; - - total = le32_to_cpu(list->count); - if (total > BRCMF_MAX_CHANSPEC_LIST) { - bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", - total); - err = -EINVAL; - goto fail_pbuf; } + total = le32_to_cpu(list->count); for (i = 0; i < total; i++) { ch.chspec = (u16)le32_to_cpu(list->element[i]); cfg->d11inf.decchspec(&ch); @@ -6582,6 +8226,14 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, band = wiphy->bands[NL80211_BAND_2GHZ]; } else if (ch.band == BRCMU_CHAN_BAND_5G) { band = wiphy->bands[NL80211_BAND_5GHZ]; + } else if ((ch.band == BRCMU_CHAN_BAND_6G)) { + if (brcmf_feat_is_6ghz_enabled(ifp)) { + band = wiphy->bands[NL80211_BAND_6GHZ]; + } else { + brcmf_dbg(INFO, "Disabled channel Spec. 0x%x.\n", + ch.chspec); + continue; + } } else { bphy_err(drvr, "Invalid channel Spec. 0x%x.\n", ch.chspec); @@ -6657,6 +8309,59 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg, } } + /* Remove disabled channels to avoid unexpected restore. */ + band = wiphy->bands[NL80211_BAND_2GHZ]; + if (band) { + n_2g = band->n_channels; + for (i = 0; i < n_2g;) { + cur = &band->channels[i]; + if (cur->flags == IEEE80211_CHAN_DISABLED) { + for (j = i; j < n_2g - 1; j++) { + cur = &band->channels[j]; + next = &band->channels[j + 1]; + memcpy(cur, next, sizeof(*cur)); + } + n_2g--; + } else + i++; + } + wiphy->bands[NL80211_BAND_2GHZ]->n_channels = n_2g; + } + band = wiphy->bands[NL80211_BAND_5GHZ]; + if (band) { + n_5g = band->n_channels; + for (i = 0; i < n_5g;) { + cur = &band->channels[i]; + if (cur->flags == IEEE80211_CHAN_DISABLED) { + for (j = i; j < n_5g - 1; j++) { + cur = &band->channels[j]; + next = &band->channels[j + 1]; + memcpy(cur, next, sizeof(*cur)); + } + n_5g--; + } else + i++; + } + wiphy->bands[NL80211_BAND_5GHZ]->n_channels = n_5g; + } + band = wiphy->bands[NL80211_BAND_6GHZ]; + if (band) { + n_6g = band->n_channels; + for (i = 0; i < n_6g;) { + cur = &band->channels[i]; + if (cur->flags == IEEE80211_CHAN_DISABLED) { + for (j = i; j < n_6g - 1; j++) { + cur = &band->channels[j]; + next = &band->channels[j + 1]; + memcpy(cur, next, sizeof(*cur)); + } + n_6g--; + } else + i++; + } + wiphy->bands[NL80211_BAND_6GHZ]->n_channels = n_6g; + } + fail_pbuf: kfree(pbuf); return err; @@ -6719,13 +8424,6 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg) band = cfg_to_wiphy(cfg)->bands[NL80211_BAND_2GHZ]; list = (struct brcmf_chanspec_list *)pbuf; num_chan = le32_to_cpu(list->count); - if (num_chan > BRCMF_MAX_CHANSPEC_LIST) { - bphy_err(drvr, "Invalid count of channel Spec. (%u)\n", - num_chan); - kfree(pbuf); - return -EINVAL; - } - for (i = 0; i < num_chan; i++) { ch.chspec = (u16)le32_to_cpu(list->element[i]); cfg->d11inf.decchspec(&ch); @@ -6761,6 +8459,16 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) err = brcmf_fil_iovar_int_get(ifp, "bw_cap", &band); if (!err) { bw_cap[NL80211_BAND_5GHZ] = band; + + if (!brcmf_feat_is_6ghz_enabled(ifp)) + return; + + band = WLC_BAND_6G; + err = brcmf_fil_iovar_int_get(ifp, "bw_cap", &band); + if (!err) { + bw_cap[NL80211_BAND_6GHZ] = band; + return; + } return; } WARN_ON(1); @@ -6792,6 +8500,10 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[]) static void brcmf_update_ht_cap(struct ieee80211_supported_band *band, u32 bw_cap[2], u32 nchain) { + /* not allowed in 6G band */ + if (band->band == NL80211_BAND_6GHZ) + return; + band->ht_cap.ht_supported = true; if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) { band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; @@ -6822,8 +8534,8 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, { __le16 mcs_map; - /* not allowed in 2.4G band */ - if (band->band == NL80211_BAND_2GHZ) + /* not allowed in 2.4G & 6G band */ + if (band->band == NL80211_BAND_2GHZ || band->band == NL80211_BAND_6GHZ) return; band->vht_cap.vht_supported = true; @@ -6858,6 +8570,98 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band, } } +static void brcmf_update_he_cap(struct ieee80211_supported_band *band, + struct ieee80211_sband_iftype_data *data) +{ + int idx = 1; + struct ieee80211_sta_he_cap *he_cap = &data->he_cap; + struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem; + struct ieee80211_he_mcs_nss_supp *he_mcs = &he_cap->he_mcs_nss_supp; + struct ieee80211_he_6ghz_capa *he_6ghz_capa = &data->he_6ghz_capa; + + if (data == NULL) { + brcmf_dbg(INFO, "failed to allco mem\n"); + return; + } + + data->types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + he_cap->has_he = true; + + /* HE MAC Capabilities Information */ + he_cap_elem->mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE | + IEEE80211_HE_MAC_CAP0_TWT_REQ | + IEEE80211_HE_MAC_CAP0_TWT_RES; + + he_cap_elem->mac_cap_info[1] = IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_8US | + IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US; + + he_cap_elem->mac_cap_info[2] = IEEE80211_HE_MAC_CAP2_BSR | + IEEE80211_HE_MAC_CAP2_BCAST_TWT; + + he_cap_elem->mac_cap_info[3] = IEEE80211_HE_MAC_CAP3_OMI_CONTROL | + IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 | + IEEE80211_HE_MAC_CAP3_FLEX_TWT_SCHED; + + he_cap_elem->mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU; + + + /* HE PHY Capabilities Information */ + he_cap_elem->phy_cap_info[0] = IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; + + he_cap_elem->phy_cap_info[1] = IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD; + + he_cap_elem->phy_cap_info[2] = IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US | + IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO | + IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO; + + he_cap_elem->phy_cap_info[3] = IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_QPSK | + IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_2 | + IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM; + + he_cap_elem->phy_cap_info[4] = IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE | + IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8; + + he_cap_elem->phy_cap_info[5] = IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK | + IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK; + + he_cap_elem->phy_cap_info[6] = IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU | + IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU | + IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB | + IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB | + IEEE80211_HE_PHY_CAP6_TRIG_CQI_FB | + IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE | + IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT; + + he_cap_elem->phy_cap_info[7] = IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP7_MAX_NC_1; + + he_cap_elem->phy_cap_info[8] = IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI | + IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G; + + he_cap_elem->phy_cap_info[9] = IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB | + IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB; + + /* HE Supported MCS and NSS Set */ + he_mcs->rx_mcs_80 = cpu_to_le16(0xfffa); + he_mcs->tx_mcs_80 = cpu_to_le16(0xfffa); + /* HE 6 GHz band capabilities */ + if (band->band == NL80211_BAND_6GHZ) { + u16 capa = 0; + + capa = FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START, + IEEE80211_HT_MPDU_DENSITY_8) | + FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP, + IEEE80211_VHT_MAX_AMPDU_1024K) | + FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN, + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454); + he_6ghz_capa->capa = cpu_to_le16(capa); + } + band->n_iftype_data = idx; + band->iftype_data = data; +} + static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) { struct brcmf_pub *drvr = cfg->pub; @@ -6865,7 +8669,10 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) struct wiphy *wiphy = cfg_to_wiphy(cfg); u32 nmode = 0; u32 vhtmode = 0; - u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT }; + u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, /* 2GHz */ + WLC_BW_20MHZ_BIT, /* 5GHz */ + 0, /* 60GHz */ + 0 }; /* 6GHz */ u32 rxchain; u32 nchain; int err; @@ -6874,6 +8681,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) u32 txstreams = 0; u32 txbf_bfe_cap = 0; u32 txbf_bfr_cap = 0; + u32 he[2] = {0, 0}; (void)brcmf_fil_iovar_int_get(ifp, "vhtmode", &vhtmode); err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode); @@ -6882,9 +8690,11 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) } else { brcmf_get_bwcap(ifp, bw_cap); } - brcmf_dbg(INFO, "nmode=%d, vhtmode=%d, bw_cap=(%d, %d)\n", - nmode, vhtmode, bw_cap[NL80211_BAND_2GHZ], - bw_cap[NL80211_BAND_5GHZ]); + (void)brcmf_fil_iovar_data_get(ifp, "he", he, sizeof(he)); + + brcmf_dbg(INFO, "nmode=%d, vhtmode=%d, he=%d, bw_cap=(%d, %d, %d)\n", + nmode, vhtmode, he[0], bw_cap[NL80211_BAND_2GHZ], + bw_cap[NL80211_BAND_5GHZ], bw_cap[NL80211_BAND_6GHZ]); err = brcmf_fil_iovar_int_get(ifp, "rxchain", &rxchain); if (err) { @@ -6920,11 +8730,17 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg) if (band == NULL) continue; + if ((band->band == NL80211_BAND_6GHZ) && + !brcmf_feat_is_6ghz_enabled(ifp)) + continue; + if (nmode) brcmf_update_ht_cap(band, bw_cap, nchain); if (vhtmode) brcmf_update_vht_cap(band, bw_cap, nchain, txstreams, txbf_bfe_cap, txbf_bfr_cap); + if (he[0]) + brcmf_update_he_cap(band, &sdata[band->band]); } return 0; @@ -6935,6 +8751,7 @@ brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = { [NL80211_IFTYPE_STATION] = { .tx = 0xffff, .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, [NL80211_IFTYPE_P2P_CLIENT] = { @@ -6998,7 +8815,7 @@ brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = { * * p2p, mchan, and mbss: * - * #STA <= 1, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 2, 3 total + * #STA <= 2, #P2P-DEV <= 1, #{P2P-CL, P2P-GO} <= 1, channels = 2, 3 total * #STA <= 1, #P2P-DEV <= 1, #AP <= 1, #P2P-CL <= 1, channels = 1, 4 total * #AP <= 4, matching BI, channels = 1, 4 total * @@ -7044,7 +8861,7 @@ static int brcmf_setup_ifmodes(struct wiphy *wiphy, struct brcmf_if *ifp) goto err; combo[c].num_different_channels = 1 + (rsdb || (p2p && mchan)); - c0_limits[i].max = 1; + c0_limits[i].max = 1 + (p2p && mchan); c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION); if (mon_flag) { c0_limits[i].max = 1; @@ -7144,6 +8961,7 @@ static void brcmf_wiphy_wowl_params(struct wiphy *wiphy, struct brcmf_if *ifp) struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_pub *drvr = cfg->pub; struct wiphy_wowlan_support *wowl; + struct cfg80211_wowlan *brcmf_wowlan_config = NULL; wowl = kmemdup(&brcmf_wowlan_support, sizeof(brcmf_wowlan_support), GFP_KERNEL); @@ -7166,17 +8984,39 @@ static void brcmf_wiphy_wowl_params(struct wiphy *wiphy, struct brcmf_if *ifp) } wiphy->wowlan = wowl; + + /* wowlan_config structure report for kernels */ + brcmf_wowlan_config = kzalloc(sizeof(*brcmf_wowlan_config), + GFP_KERNEL); + if (brcmf_wowlan_config) { + brcmf_wowlan_config->any = false; + brcmf_wowlan_config->disconnect = true; + brcmf_wowlan_config->eap_identity_req = true; + brcmf_wowlan_config->four_way_handshake = true; + brcmf_wowlan_config->rfkill_release = false; + brcmf_wowlan_config->patterns = NULL; + brcmf_wowlan_config->n_patterns = 0; + brcmf_wowlan_config->tcp = NULL; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_GTK)) + brcmf_wowlan_config->gtk_rekey_failure = true; + else + brcmf_wowlan_config->gtk_rekey_failure = false; + } else { + brcmf_err("Can not allocate memory for brcm_wowlan_config\n"); + } + wiphy->wowlan_config = brcmf_wowlan_config; #endif } static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); const struct ieee80211_iface_combination *combo; struct ieee80211_supported_band *band; u16 max_interfaces = 0; bool gscan; - __le32 bandlist[3]; + __le32 bandlist[4]; u32 n_bands; int err, i; @@ -7210,7 +9050,7 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) wiphy->cipher_suites = brcmf_cipher_suites; wiphy->n_cipher_suites = ARRAY_SIZE(brcmf_cipher_suites); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) - wiphy->n_cipher_suites--; + wiphy->n_cipher_suites -= 4; wiphy->bss_select_support = BIT(NL80211_BSS_SELECT_ATTR_RSSI) | BIT(NL80211_BSS_SELECT_ATTR_BAND_PREF) | BIT(NL80211_BSS_SELECT_ATTR_RSSI_ADJUST); @@ -7224,6 +9064,8 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; if (!ifp->drvr->settings->roamoff) wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM; + if (brcmf_feat_is_6ghz_enabled(ifp)) + wiphy->flags |= WIPHY_FLAG_SPLIT_SCAN_6GHZ; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) { wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK); @@ -7240,6 +9082,17 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SAE_OFFLOAD_AP); } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) { + wiphy->features |= NL80211_FEATURE_SAE; + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_AP_PMKSA_CACHING); + } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FBT) || + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_OKC)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_ROAM_OFFLOAD); + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); + wiphy->mgmt_stypes = brcmf_txrx_stypes; wiphy->max_remain_on_channel_duration = 5000; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { @@ -7248,10 +9101,13 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) } /* vendor commands/events support */ wiphy->vendor_commands = brcmf_vendor_cmds; - wiphy->n_vendor_commands = BRCMF_VNDR_CMDS_LAST - 1; + wiphy->n_vendor_commands = get_brcmf_num_vndr_cmds(); + wiphy->vendor_events = brcmf_vendor_events; + wiphy->n_vendor_events = BRCMF_VNDR_EVTS_LAST; + brcmf_fweh_register(cfg->pub, BRCMF_E_PHY_TEMP, + brcmf_wiphy_phy_temp_evt_handler); - if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL)) - brcmf_wiphy_wowl_params(wiphy, ifp); + brcmf_wiphy_wowl_params(wiphy, ifp); err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BANDLIST, &bandlist, sizeof(bandlist)); if (err) { @@ -7295,6 +9151,24 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) band->n_channels = ARRAY_SIZE(__wl_5ghz_channels); wiphy->bands[NL80211_BAND_5GHZ] = band; } + if (bandlist[i] == cpu_to_le32(WLC_BAND_6G) && + brcmf_feat_is_6ghz_enabled(ifp)) { + band = kmemdup(&__wl_band_6ghz, sizeof(__wl_band_6ghz), + GFP_KERNEL); + if (!band) + return -ENOMEM; + + band->channels = kmemdup(&__wl_6ghz_channels, + sizeof(__wl_6ghz_channels), + GFP_KERNEL); + if (!band->channels) { + kfree(band); + return -ENOMEM; + } + + band->n_channels = ARRAY_SIZE(__wl_6ghz_channels); + wiphy->bands[NL80211_BAND_6GHZ] = band; + } } if (wiphy->bands[NL80211_BAND_5GHZ] && @@ -7305,7 +9179,6 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST); wiphy_read_of_freq_limits(wiphy); - return 0; } @@ -7316,6 +9189,7 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) struct wireless_dev *wdev; struct brcmf_if *ifp; s32 power_mode; + s32 eap_restrict; s32 err = 0; if (cfg->dongle_up) @@ -7330,7 +9204,7 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) brcmf_dongle_scantime(ifp); - power_mode = cfg->pwr_save ? PM_FAST : PM_OFF; + power_mode = cfg->pwr_save ? ifp->drvr->settings->default_pm : PM_OFF; err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, power_mode); if (err) goto default_conf_out; @@ -7340,6 +9214,14 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) err = brcmf_dongle_roam(ifp); if (err) goto default_conf_out; + + eap_restrict = ifp->drvr->settings->eap_restrict; + if (eap_restrict) { + err = brcmf_fil_iovar_int_set(ifp, "eap_restrict", + eap_restrict); + if (err) + brcmf_info("eap_restrict error (%d)\n", err); + } err = brcmf_cfg80211_change_iface(wdev->wiphy, ndev, wdev->iftype, NULL); if (err) @@ -7383,6 +9265,7 @@ static s32 __brcmf_cfg80211_down(struct brcmf_if *ifp) the state fw and WPA_Supplicant state consistent */ brcmf_delay(500); + cfg->dongle_up = false; } brcmf_abort_scanning(cfg); @@ -7550,6 +9433,229 @@ static s32 brcmf_translate_country_code(struct brcmf_pub *drvr, char alpha2[2], return 0; } +static int +brcmf_parse_dump_obss(char *buf, struct brcmf_dump_survey *survey) +{ + int i; + char *token; + char delim[] = "\n "; + unsigned long val; + int err = 0; + + token = strsep(&buf, delim); + while (token) { + if (!strcmp(token, "OBSS")) { + for (i = 0; i < OBSS_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->obss = val; + } + + if (!strcmp(token, "IBSS")) { + for (i = 0; i < IBSS_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->ibss = val; + } + + if (!strcmp(token, "TXDur")) { + for (i = 0; i < TX_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->tx = val; + } + + if (!strcmp(token, "Category")) { + for (i = 0; i < CTG_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->no_ctg = val; + } + + if (!strcmp(token, "Packet")) { + for (i = 0; i < PKT_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->no_pckt = val; + } + + if (!strcmp(token, "Opp(time):")) { + for (i = 0; i < IDLE_TOKEN_IDX; i++) + token = strsep(&buf, delim); + err = kstrtoul(token, 10, &val); + survey->idle = val; + } + + token = strsep(&buf, delim); + + if (err) + return err; + } + + return 0; +} + +static int +brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req, + struct brcmf_dump_survey *survey) +{ + struct cca_stats_n_flags *results; + char *buf; + int err; + + buf = kzalloc(sizeof(char) * BRCMF_DCMD_MEDLEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, &req, sizeof(struct cca_msrmnt_query)); + err = brcmf_fil_iovar_data_get(ifp, "dump_obss", + buf, BRCMF_DCMD_MEDLEN); + if (err) { + brcmf_err("dump_obss error (%d)\n", err); + err = -EINVAL; + goto exit; + } + results = (struct cca_stats_n_flags *)(buf); + + if (req.msrmnt_query) + brcmf_parse_dump_obss(results->buf, survey); + +exit: + kfree(buf); + return err; +} + +static s32 +cfg80211_set_channel(struct wiphy *wiphy, struct net_device *dev, + struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type) +{ + u16 chspec = 0; + int err = 0; + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + + /* set_channel */ + chspec = channel_to_chanspec(&cfg->d11inf, chan); + if (chspec != INVCHANSPEC) { + err = brcmf_fil_iovar_int_set(ifp, "chanspec", chspec); + if (err) + err = -EINVAL; + } else { + brcmf_err("failed to convert host chanspec to fw chanspec\n"); + err = -EINVAL; + } + + return err; +} + +static int +brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev, + int idx, struct survey_info *info) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + struct brcmf_dump_survey *survey; + struct ieee80211_supported_band *band; + struct ieee80211_channel *chan; + struct cca_msrmnt_query req; + u32 noise; + int err; + + brcmf_dbg(TRACE, "Enter: channel idx=%d\n", idx); + + if (!brcmf_is_apmode(ifp->vif)) + return -ENOENT; + + /* Do not run survey when VIF in CONNECTING / CONNECTED states */ + if ((test_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state)) || + (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state))) { + return -EBUSY; + } + + band = wiphy->bands[NL80211_BAND_2GHZ]; + if (band && idx >= band->n_channels) { + idx -= band->n_channels; + band = NULL; + } + + if (!band || idx >= band->n_channels) { + band = wiphy->bands[NL80211_BAND_5GHZ]; + if (idx >= band->n_channels) + return -ENOENT; + } + + /* Setting current channel to the requested channel */ + chan = &band->channels[idx]; + err = cfg80211_set_channel(wiphy, ndev, chan, NL80211_CHAN_HT20); + if (err) { + info->channel = chan; + info->filled = 0; + return 0; + } + + survey = kzalloc(sizeof(*survey), GFP_KERNEL); + if (!survey) + return -ENOMEM; + + /* Disable mpc */ + brcmf_set_mpc(ifp, 0); + + /* Set interface up, explicitly. */ + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); + if (err) { + brcmf_err("set interface up failed, err = %d\n", err); + goto exit; + } + + /* Get noise value */ + err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PHY_NOISE, &noise); + if (err) { + brcmf_err("Get Phy Noise failed, use dummy value\n"); + noise = CHAN_NOISE_DUMMY; + } + + + /* Start Measurement for obss stats on current channel */ + req.msrmnt_query = 0; + req.time_req = ACS_MSRMNT_DELAY; + err = brcmf_dump_obss(ifp, req, survey); + if (err) + goto exit; + + /* Add 10 ms for IOVAR completion */ + msleep(ACS_MSRMNT_DELAY + 10); + + /* Issue IOVAR to collect measurement results */ + req.msrmnt_query = 1; + err = brcmf_dump_obss(ifp, req, survey); + if (err) + goto exit; + + info->channel = chan; + info->noise = noise; + info->time = ACS_MSRMNT_DELAY; + info->time_busy = ACS_MSRMNT_DELAY - survey->idle; + info->time_rx = survey->obss + survey->ibss + survey->no_ctg + + survey->no_pckt; + info->time_tx = survey->tx; + info->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX; + + brcmf_dbg(INFO, "OBSS dump: channel %d: survey duration %d\n", + ieee80211_frequency_to_channel(chan->center_freq), + ACS_MSRMNT_DELAY); + brcmf_dbg(INFO, "noise(%d) busy(%llu) rx(%llu) tx(%llu)\n", + info->noise, info->time_busy, info->time_rx, info->time_tx); + +exit: + if (!brcmf_is_apmode(ifp->vif)) + brcmf_set_mpc(ifp, 1); + kfree(survey); + return err; +} + static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy, struct regulatory_request *req) { @@ -7613,6 +9719,11 @@ static void brcmf_free_wiphy(struct wiphy *wiphy) kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels); kfree(wiphy->bands[NL80211_BAND_5GHZ]); } + if (wiphy->bands[NL80211_BAND_6GHZ]) { + kfree(wiphy->bands[NL80211_BAND_6GHZ]->channels); + kfree(wiphy->bands[NL80211_BAND_6GHZ]); + } + #if IS_ENABLED(CONFIG_PM) if (wiphy->wowlan != &brcmf_wowlan_support) kfree(wiphy->wowlan); @@ -7645,6 +9756,8 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, cfg->wiphy = wiphy; cfg->pub = drvr; + cfg->pm_state = BRCMF_CFG80211_PM_STATE_RESUMED; + cfg->num_softap = 0; init_vif_event(&cfg->vif_event); INIT_LIST_HEAD(&cfg->vif_list); @@ -7701,11 +9814,10 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL_GTK)) ops->set_rekey_data = brcmf_cfg80211_set_rekey_data; #endif - err = wiphy_register(wiphy); - if (err < 0) { - bphy_err(drvr, "Could not register wiphy device (%d)\n", err); - goto priv_out; - } + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DUMP_OBSS)) + ops->dump_survey = brcmf_cfg80211_dump_survey; + else + ops->dump_survey = NULL; err = brcmf_setup_wiphybands(cfg); if (err) { @@ -7713,6 +9825,12 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, goto wiphy_unreg_out; } + err = wiphy_register(wiphy); + if (err < 0) { + bphy_err(drvr, "Could not register wiphy device (%d)\n", err); + goto priv_out; + } + /* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(), * setup 40MHz in 2GHz band and enable OBSS scanning. */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index e90a30808c220..d9fd3cf9c3f2a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -92,6 +92,23 @@ #define BRCMF_VIF_EVENT_TIMEOUT msecs_to_jiffies(1500) +#define BRCMF_PM_WAIT_MAXRETRY 100 + +/* cfg80211 wowlan definitions */ +#define WL_WOWLAN_MAX_PATTERNS 8 +#define WL_WOWLAN_MIN_PATTERN_LEN 1 +#define WL_WOWLAN_MAX_PATTERN_LEN 255 +#define WL_WOWLAN_PKT_FILTER_ID_FIRST 201 +#define WL_WOWLAN_PKT_FILTER_ID_LAST (WL_WOWLAN_PKT_FILTER_ID_FIRST + \ + WL_WOWLAN_MAX_PATTERNS - 1) + +#define WL_RSPEC_ENCODE_HE 0x03000000 /* HE MCS and Nss is stored in RSPEC_RATE_MASK */ +#define WL_RSPEC_HE_NSS_UNSPECIFIED 0xF +#define WL_RSPEC_HE_NSS_SHIFT 4 /* HE Nss value shift */ +#define WL_RSPEC_HE_GI_MASK 0x00000C00 /* HE GI indices */ +#define WL_RSPEC_HE_GI_SHIFT 10 +#define HE_GI_TO_RSPEC(gi) (((gi) << WL_RSPEC_HE_GI_SHIFT) & WL_RSPEC_HE_GI_MASK) + /** * enum brcmf_scan_status - scan engine status * @@ -125,7 +142,8 @@ enum brcmf_profile_fwsup { BRCMF_PROFILE_FWSUP_NONE, BRCMF_PROFILE_FWSUP_PSK, BRCMF_PROFILE_FWSUP_1X, - BRCMF_PROFILE_FWSUP_SAE + BRCMF_PROFILE_FWSUP_SAE, + BRCMF_PROFILE_FWSUP_ROAM }; /** @@ -155,6 +173,7 @@ struct brcmf_cfg80211_profile { enum brcmf_profile_fwsup use_fwsup; u16 use_fwauth; bool is_ft; + bool is_okc; }; /** @@ -178,6 +197,28 @@ enum brcmf_vif_status { BRCMF_VIF_STATUS_ASSOC_SUCCESS, }; +enum brcmf_cfg80211_pm_state { + BRCMF_CFG80211_PM_STATE_RESUMED, + BRCMF_CFG80211_PM_STATE_RESUMING, + BRCMF_CFG80211_PM_STATE_SUSPENDED, + BRCMF_CFG80211_PM_STATE_SUSPENDING, +}; + +/** + * enum brcmf_mgmt_tx_status - mgmt frame tx status + * + * @BRCMF_MGMT_TX_ACK: mgmt frame acked + * @BRCMF_MGMT_TX_NOACK: mgmt frame not acked + * @BRCMF_MGMT_TX_OFF_CHAN_COMPLETED: off-channel complete + * @BRCMF_MGMT_TX_SEND_FRAME: mgmt frame tx is in progres + */ +enum brcmf_mgmt_tx_status { + BRCMF_MGMT_TX_ACK, + BRCMF_MGMT_TX_NOACK, + BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, + BRCMF_MGMT_TX_SEND_FRAME +}; + /** * struct vif_saved_ie - holds saved IEs for a virtual interface. * @@ -224,6 +265,9 @@ struct brcmf_cfg80211_vif { unsigned long sme_state; struct vif_saved_ie saved_ie; struct list_head list; + struct completion mgmt_tx; + unsigned long mgmt_tx_status; + u32 mgmt_tx_id; u16 mgmt_rx_reg; bool mbss; int is_11d; @@ -267,6 +311,11 @@ struct escan_info { struct cfg80211_scan_request *request); }; +struct cqm_rssi_info { + bool enable; + s32 rssi_threshold; +}; + /** * struct brcmf_cfg80211_vif_event - virtual interface event information. * @@ -363,6 +412,7 @@ struct brcmf_cfg80211_info { struct escan_info escan_info; struct timer_list escan_timeout; struct work_struct escan_timeout_work; + struct cqm_rssi_info cqm_info; struct list_head vif_list; struct brcmf_cfg80211_vif_event vif_event; struct completion vif_disabled; @@ -371,6 +421,8 @@ struct brcmf_cfg80211_info { struct brcmf_cfg80211_wowl wowl; struct brcmf_pno_info *pno; u8 ac_priority[MAX_8021D_PRIO]; + u8 pm_state; + u8 num_softap; }; /** @@ -386,6 +438,12 @@ struct brcmf_tlv { u8 data[1]; }; +struct bcm_xtlv { + u16 id; + u16 len; + u8 data[1]; +}; + static inline struct wiphy *cfg_to_wiphy(struct brcmf_cfg80211_info *cfg) { return cfg->wiphy; @@ -467,5 +525,6 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, void brcmf_set_mpc(struct brcmf_if *ndev, int mpc); void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg); void brcmf_cfg80211_free_netdev(struct net_device *ndev); - +bool brcmf_is_apmode_operating(struct wiphy *wiphy); +void brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev); #endif /* BRCMFMAC_CFG80211_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c index 121893bbaa1d7..cce1c765cc833 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c @@ -215,6 +215,58 @@ struct sbsocramregs { #define ARMCR4_BSZ_MASK 0x3f #define ARMCR4_BSZ_MULT 8192 +/* Minimum PMU resource mask for 43012C0 */ +#define CY_43012_PMU_MIN_RES_MASK 0xF8BFE77 + +/* PMU STATUS mask for 43012C0 */ +#define CY_43012_PMU_STATUS_MASK 0x1AC + +/* PMU CONTROL EXT mask for 43012C0 */ +#define CY_43012_PMU_CONTROL_EXT_MASK 0x11 + +/* PMU Watchdog Counter Tick value for 43012C0 */ +#define CY_43012_PMU_WATCHDOG_TICK_VAL 0x04 + +/* PMU Watchdog Counter Tick value for 4373 */ +#define CY_4373_PMU_WATCHDOG_TICK_VAL 0x04 + +/* Minimum PMU resource mask for 4373 */ +#define CY_4373_PMU_MIN_RES_MASK 0xFCAFF7F + +/* CYW55572 dedicated space and RAM base */ +#define CYW55572_TCAM_SIZE 0x800 +#define CYW55572_TRXHDR_SIZE 0x2b4 +#define CYW55572_RAM_BASE (0x370000 + \ + CYW55572_TCAM_SIZE + CYW55572_TRXHDR_SIZE) + +/* 55500, Dedicated sapce for TCAM_PATCH and TRX HDR area at RAMSTART */ +#define CYW55500_RAM_START (0x3a0000) +#define CYW55500_TCAM_SIZE (0x800) +#define CYW55500_TRXHDR_SIZE (0x2b4) + +#define CYW55500_RAM_BASE (CYW55500_RAM_START + CYW55500_TCAM_SIZE + \ + CYW55500_TRXHDR_SIZE) + +#define BRCMF_BLHS_POLL_INTERVAL 10 /* msec */ +#define BRCMF_BLHS_D2H_READY_TIMEOUT 100 /* msec */ +#define BRCMF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT 50 /* msec */ +#define BRCMF_BLHS_D2H_VALDN_DONE_TIMEOUT 450 /* msec */ + +/* Bootloader handshake flags - dongle to host */ +#define BRCMF_BLHS_D2H_START BIT(0) +#define BRCMF_BLHS_D2H_READY BIT(1) +#define BRCMF_BLHS_D2H_STEADY BIT(2) +#define BRCMF_BLHS_D2H_TRXHDR_PARSE_DONE BIT(3) +#define BRCMF_BLHS_D2H_VALDN_START BIT(4) +#define BRCMF_BLHS_D2H_VALDN_RESULT BIT(5) +#define BRCMF_BLHS_D2H_VALDN_DONE BIT(6) + +/* Bootloader handshake flags - host to dongle */ +#define BRCMF_BLHS_H2D_DL_FW_START BIT(0) +#define BRCMF_BLHS_H2D_DL_FW_DONE BIT(1) +#define BRCMF_BLHS_H2D_DL_NVRAM_DONE BIT(2) +#define BRCMF_BLHS_H2D_BL_RESET_ON_ERROR BIT(3) + struct brcmf_core_priv { struct brcmf_core pub; u32 wrapbase; @@ -500,6 +552,21 @@ char *brcmf_chip_name(u32 id, u32 rev, char *buf, uint len) return buf; } +static bool brcmf_chip_find_coreid(struct brcmf_chip_priv *ci, u16 coreid) +{ + struct brcmf_core_priv *core; + + list_for_each_entry(core, &ci->cores, list) { + brcmf_dbg(TRACE, " core 0x%x:%-2d base 0x%08x wrap 0x%08x\n", + core->pub.id, core->pub.rev, core->pub.base, + core->wrapbase); + if (core->pub.id == coreid) + return true; + } + + return false; +} + static struct brcmf_core *brcmf_chip_add_core(struct brcmf_chip_priv *ci, u16 coreid, u32 base, u32 wrapbase) @@ -737,6 +804,10 @@ static u32 brcmf_chip_tcm_rambase(struct brcmf_chip_priv *ci) return 0x352000; case CY_CC_89459_CHIP_ID: return ((ci->pub.chiprev < 9) ? 0x180000 : 0x160000); + case CY_CC_55500_CHIP_ID: + return CYW55500_RAM_BASE; + case CY_CC_55572_CHIP_ID: + return CYW55572_RAM_BASE; default: brcmf_err("unknown chip: %s\n", ci->pub.name); break; @@ -755,6 +826,15 @@ int brcmf_chip_get_raminfo(struct brcmf_chip *pub) if (mem) { mem_core = container_of(mem, struct brcmf_core_priv, pub); ci->pub.ramsize = brcmf_chip_tcm_ramsize(mem_core); + + if (ci->pub.chip == CY_CC_55500_CHIP_ID) + ci->pub.ramsize -= (CYW55500_TCAM_SIZE + + CYW55500_TRXHDR_SIZE); + + if (ci->pub.chip == CY_CC_55572_CHIP_ID) + ci->pub.ramsize -= (CYW55572_TCAM_SIZE + + CYW55572_TRXHDR_SIZE); + ci->pub.rambase = brcmf_chip_tcm_rambase(ci); if (ci->pub.rambase == INVALID_RAMBASE) { brcmf_err("RAM base not provided with ARM CR4 core\n"); @@ -904,7 +984,10 @@ int brcmf_chip_dmp_erom_scan(struct brcmf_chip_priv *ci) u32 base, wrap; int err; - eromaddr = ci->ops->read32(ci->ctx, + if (ci->pub.ccsec) + eromaddr = ci->pub.ccsec->erombase; + else + eromaddr = ci->ops->read32(ci->ctx, CORE_CC_REG(ci->pub.enum_base, eromptr)); while (desc_type != DMP_DESC_EOT) { @@ -934,7 +1017,8 @@ int brcmf_chip_dmp_erom_scan(struct brcmf_chip_priv *ci) /* need core with ports */ if (nmw + nsw == 0 && id != BCMA_CORE_PMU && - id != BCMA_CORE_GCI) + id != BCMA_CORE_GCI && + id != BCMA_CORE_SR) continue; /* try to obtain register address info */ @@ -958,6 +1042,144 @@ u32 brcmf_chip_enum_base(u16 devid) return SI_ENUM_BASE_DEFAULT; } +static void brcmf_blhs_init(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + + chip = container_of(pub, struct brcmf_chip_priv, pub); + addr = pub->blhs->h2d; + pub->blhs->write(chip->ctx, addr, 0); +} + +static int brcmf_blhs_is_bootloader_ready(struct brcmf_chip_priv *chip) +{ + u32 regdata; + u32 addr; + + addr = chip->pub.blhs->d2h; + SPINWAIT_MS((chip->pub.blhs->read(chip->ctx, addr) & + BRCMF_BLHS_D2H_READY) == 0, + BRCMF_BLHS_D2H_READY_TIMEOUT, BRCMF_BLHS_POLL_INTERVAL); + + regdata = chip->pub.blhs->read(chip->ctx, addr); + if (!(regdata & BRCMF_BLHS_D2H_READY)) { + brcmf_err("Timeout waiting for bootloader ready\n"); + return -EPERM; + } + + return 0; +} + +static int brcmf_blhs_prep_fw_download(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + int err; + + /* Host indication for bootloader to start the init */ + brcmf_blhs_init(pub); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + err = brcmf_blhs_is_bootloader_ready(chip); + if (err) + return err; + + /* Host notification about FW download start */ + addr = pub->blhs->h2d; + pub->blhs->write(chip->ctx, addr, BRCMF_BLHS_H2D_DL_FW_START); + + return 0; +} + +static int brcmf_blhs_post_fw_download(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + u32 regdata; + + chip = container_of(pub, struct brcmf_chip_priv, pub); + addr = pub->blhs->h2d; + pub->blhs->write(chip->ctx, addr, BRCMF_BLHS_H2D_DL_FW_DONE); + addr = pub->blhs->d2h; + SPINWAIT_MS((pub->blhs->read(chip->ctx, addr) & + BRCMF_BLHS_D2H_TRXHDR_PARSE_DONE) == 0, + BRCMF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT, + BRCMF_BLHS_POLL_INTERVAL); + + regdata = pub->blhs->read(chip->ctx, addr); + if (!(regdata & BRCMF_BLHS_D2H_TRXHDR_PARSE_DONE)) { + brcmf_err("TRX header parsing failed\n"); + + /* Host indication for bootloader to get reset on error */ + addr = pub->blhs->h2d; + regdata = pub->blhs->read(chip->ctx, addr); + regdata |= BRCMF_BLHS_H2D_BL_RESET_ON_ERROR; + pub->blhs->write(chip->ctx, addr, regdata); + + return -EPERM; + } + + return 0; +} + +static void brcmf_blhs_post_nvram_download(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + u32 regdata; + + chip = container_of(pub, struct brcmf_chip_priv, pub); + addr = pub->blhs->h2d; + regdata = pub->blhs->read(chip->ctx, addr); + regdata |= BRCMF_BLHS_H2D_DL_NVRAM_DONE; + pub->blhs->write(chip->ctx, addr, regdata); +} + +static int brcmf_blhs_chk_validation(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + u32 regdata; + + chip = container_of(pub, struct brcmf_chip_priv, pub); + addr = pub->blhs->d2h; + SPINWAIT_MS((pub->blhs->read(chip->ctx, addr) & + BRCMF_BLHS_D2H_VALDN_DONE) == 0, + BRCMF_BLHS_D2H_VALDN_DONE_TIMEOUT, + BRCMF_BLHS_POLL_INTERVAL); + + regdata = pub->blhs->read(chip->ctx, addr); + if (!(regdata & BRCMF_BLHS_D2H_VALDN_DONE) || + !(regdata & BRCMF_BLHS_D2H_VALDN_RESULT)) { + brcmf_err("TRX image validation check failed\n"); + + /* Host notification for bootloader to get reset on error */ + addr = pub->blhs->h2d; + regdata = pub->blhs->read(chip->ctx, addr); + regdata |= BRCMF_BLHS_H2D_BL_RESET_ON_ERROR; + pub->blhs->write(chip->ctx, addr, regdata); + + return -EPERM; + } + + return 0; +} + +static int brcmf_blhs_post_watchdog_reset(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + int err; + + /* Host indication for bootloader to start the init */ + brcmf_blhs_init(pub); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + err = brcmf_blhs_is_bootloader_ready(chip); + + return err; +} + static int brcmf_chip_recognition(struct brcmf_chip_priv *ci) { struct brcmf_core *core; @@ -966,11 +1188,15 @@ static int brcmf_chip_recognition(struct brcmf_chip_priv *ci) int ret; /* Get CC core rev - * Chipid is assume to be at offset 0 from SI_ENUM_BASE + * Chipid is in bus core if CC space is protected or + * it is assume to be at offset 0 from SI_ENUM_BASE * For different chiptypes or old sdio hosts w/o chipcommon, * other ways of recognition should be added here. */ - regdata = ci->ops->read32(ci->ctx, + if (ci->pub.ccsec) + regdata = ci->pub.ccsec->chipid; + else + regdata = ci->ops->read32(ci->ctx, CORE_CC_REG(ci->pub.enum_base, chipid)); ci->pub.chip = regdata & CID_ID_MASK; ci->pub.chiprev = (regdata & CID_REV_MASK) >> CID_REV_SHIFT; @@ -1109,6 +1335,8 @@ struct brcmf_chip *brcmf_chip_attach(void *ctx, u16 devid, const struct brcmf_buscore_ops *ops) { struct brcmf_chip_priv *chip; + struct brcmf_blhs *blhs; + struct brcmf_ccsec *ccsec; int err = 0; if (WARN_ON(!ops->read32)) @@ -1136,6 +1364,28 @@ struct brcmf_chip *brcmf_chip_attach(void *ctx, u16 devid, if (err < 0) goto fail; + blhs = NULL; + ccsec = NULL; + if (chip->ops->sec_attach) { + err = chip->ops->sec_attach(chip->ctx, &blhs, &ccsec, + BRCMF_BLHS_D2H_READY, + BRCMF_BLHS_D2H_READY_TIMEOUT, + BRCMF_BLHS_POLL_INTERVAL); + if (err < 0) + goto fail; + + if (blhs) { + blhs->init = brcmf_blhs_init; + blhs->prep_fwdl = brcmf_blhs_prep_fw_download; + blhs->post_fwdl = brcmf_blhs_post_fw_download; + blhs->post_nvramdl = brcmf_blhs_post_nvram_download; + blhs->chk_validation = brcmf_blhs_chk_validation; + blhs->post_wdreset = brcmf_blhs_post_watchdog_reset; + } + } + chip->pub.blhs = blhs; + chip->pub.ccsec = ccsec; + err = brcmf_chip_recognition(chip); if (err < 0) goto fail; @@ -1162,6 +1412,9 @@ void brcmf_chip_detach(struct brcmf_chip *pub) list_del(&core->list); kfree(core); } + + kfree(pub->blhs); + kfree(pub->ccsec); kfree(chip); } @@ -1222,6 +1475,14 @@ struct brcmf_core *brcmf_chip_get_pmu(struct brcmf_chip *pub) return cc; } +struct brcmf_core *brcmf_chip_get_gci(struct brcmf_chip *pub) +{ + struct brcmf_core *gci; + + gci = brcmf_chip_get_core(pub, BCMA_CORE_GCI); + return gci; +} + bool brcmf_chip_iscoreup(struct brcmf_core *pub) { struct brcmf_core_priv *core; @@ -1294,7 +1555,8 @@ brcmf_chip_cr4_set_passive(struct brcmf_chip_priv *chip) { struct brcmf_core *core; - brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CR4); + if (!chip->pub.blhs) + brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CR4); core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211); brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET | @@ -1439,6 +1701,9 @@ bool brcmf_chip_sr_capable(struct brcmf_chip *pub) reg = chip->ops->read32(chip->ctx, addr); return (reg & (PMU_RCTL_MACPHY_DISABLE_MASK | PMU_RCTL_LOGIC_DISABLE_MASK)) == 0; + case CY_CC_55500_CHIP_ID: + case CY_CC_55572_CHIP_ID: + return brcmf_chip_find_coreid(chip, BCMA_CORE_SR); default: addr = CORE_CC_REG(pmu->base, pmucapabilities_ext); reg = chip->ops->read32(chip->ctx, addr); @@ -1451,3 +1716,150 @@ bool brcmf_chip_sr_capable(struct brcmf_chip *pub) PMU_RCTL_LOGIC_DISABLE_MASK)) == 0; } } + +void brcmf_chip_reset_pmu_regs(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + u32 base; + + brcmf_dbg(TRACE, "Enter\n"); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + base = brcmf_chip_get_pmu(pub)->base; + + switch (pub->chip) { + case CY_CC_43012_CHIP_ID: + /* SW scratch */ + addr = CORE_CC_REG(base, swscratch); + chip->ops->write32(chip->ctx, addr, 0); + + /* PMU status */ + addr = CORE_CC_REG(base, pmustatus); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_STATUS_MASK); + + /* PMU control ext */ + addr = CORE_CC_REG(base, pmucontrol_ext); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_CONTROL_EXT_MASK); + break; + + default: + brcmf_err("Unsupported chip id\n"); + break; + } +} + +void brcmf_chip_set_default_min_res_mask(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 addr; + u32 base; + + brcmf_dbg(TRACE, "Enter\n"); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + base = brcmf_chip_get_pmu(pub)->base; + switch (pub->chip) { + case CY_CC_43012_CHIP_ID: + addr = CORE_CC_REG(base, min_res_mask); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_MIN_RES_MASK); + break; + + default: + brcmf_err("Unsupported chip id\n"); + break; + } +} + +void brcmf_chip_ulp_reset_lhl_regs(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 base; + u32 addr; + + brcmf_dbg(TRACE, "Enter\n"); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + base = brcmf_chip_get_gci(pub)->base; + + /* LHL Top Level Power Sequence Control */ + addr = CORE_GCI_REG(base, lhl_top_pwrseq_ctl_adr); + chip->ops->write32(chip->ctx, addr, 0); + + /* GPIO Interrupt Enable0 */ + addr = CORE_GCI_REG(base, gpio_int_en_port_adr[0]); + chip->ops->write32(chip->ctx, addr, 0); + + /* GPIO Interrupt Status0 */ + addr = CORE_GCI_REG(base, gpio_int_st_port_adr[0]); + chip->ops->write32(chip->ctx, addr, ~0); + + /* WL ARM Timer0 Interrupt Mask */ + addr = CORE_GCI_REG(base, lhl_wl_armtim0_intrp_adr); + chip->ops->write32(chip->ctx, addr, 0); + + /* WL ARM Timer0 Interrupt Status */ + addr = CORE_GCI_REG(base, lhl_wl_armtim0_st_adr); + chip->ops->write32(chip->ctx, addr, ~0); + + /* WL ARM Timer */ + addr = CORE_GCI_REG(base, lhl_wl_armtim0_adr); + chip->ops->write32(chip->ctx, addr, 0); + + /* WL MAC Timer0 Interrupt Mask */ + addr = CORE_GCI_REG(base, lhl_wl_mactim0_intrp_adr); + chip->ops->write32(chip->ctx, addr, 0); + + /* WL MAC Timer0 Interrupt Status */ + addr = CORE_GCI_REG(base, lhl_wl_mactim0_st_adr); + chip->ops->write32(chip->ctx, addr, ~0); + + /* WL MAC TimerInt0 */ + addr = CORE_GCI_REG(base, lhl_wl_mactim_int0_adr); + chip->ops->write32(chip->ctx, addr, 0x0); +} + +void brcmf_chip_reset_watchdog(struct brcmf_chip *pub) +{ + struct brcmf_chip_priv *chip; + u32 base; + u32 addr; + + brcmf_dbg(TRACE, "Enter\n"); + + chip = container_of(pub, struct brcmf_chip_priv, pub); + base = brcmf_chip_get_pmu(pub)->base; + + switch (pub->chip) { + case CY_CC_43012_CHIP_ID: + addr = CORE_CC_REG(base, min_res_mask); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_MIN_RES_MASK); + /* Watchdog res mask */ + addr = CORE_CC_REG(base, watchdog_res_mask); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_MIN_RES_MASK); + /* PMU watchdog */ + addr = CORE_CC_REG(base, pmuwatchdog); + chip->ops->write32(chip->ctx, addr, + CY_43012_PMU_WATCHDOG_TICK_VAL); + break; + case CY_CC_4373_CHIP_ID: + addr = CORE_CC_REG(base, min_res_mask); + chip->ops->write32(chip->ctx, addr, + CY_4373_PMU_MIN_RES_MASK); + addr = CORE_CC_REG(base, watchdog_res_mask); + chip->ops->write32(chip->ctx, addr, + CY_4373_PMU_MIN_RES_MASK); + addr = CORE_CC_REG(base, pmuwatchdog); + chip->ops->write32(chip->ctx, addr, + CY_4373_PMU_WATCHDOG_TICK_VAL); + mdelay(100); + break; + default: + break; + } +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.h index d69f101f58344..3d1bf44f2d148 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.h @@ -8,7 +8,12 @@ #include #define CORE_CC_REG(base, field) \ - (base + offsetof(struct chipcregs, field)) + ((base) + offsetof(struct chipcregs, field)) + +#define CORE_GCI_REG(base, field) \ + ((base) + offsetof(struct chipgciregs, field)) + +struct brcmf_blhs; /** * struct brcmf_chip - chip level information. @@ -24,6 +29,7 @@ * @ramsize: amount of RAM on chip including retention. * @srsize: amount of retention RAM on chip. * @name: string representation of the chip identifier. + * @blhs: bootlooder handshake handle. */ struct brcmf_chip { u32 chip; @@ -37,6 +43,8 @@ struct brcmf_chip { u32 ramsize; u32 srsize; char name[12]; + struct brcmf_blhs *blhs; + struct brcmf_ccsec *ccsec; }; /** @@ -61,6 +69,7 @@ struct brcmf_core { * @setup: bus-specific core setup. * @active: chip becomes active. * The callback should use the provided @rstvec when non-zero. + * @blhs_attach: attach bootloader handshake handle */ struct brcmf_buscore_ops { u32 (*read32)(void *ctx, u32 addr); @@ -69,6 +78,41 @@ struct brcmf_buscore_ops { int (*reset)(void *ctx, struct brcmf_chip *chip); int (*setup)(void *ctx, struct brcmf_chip *chip); void (*activate)(void *ctx, struct brcmf_chip *chip, u32 rstvec); + int (*sec_attach)(void *ctx, struct brcmf_blhs **blhs, struct brcmf_ccsec **ccsec, + u32 flag, uint timeout, uint interval); +}; + +/** + * struct brcmf_blhs - bootloader handshake handle related information. + * + * @d2h: offset of dongle to host register for the handshake. + * @h2d: offset of host to dongle register for the handshake. + * @init: bootloader handshake initialization. + * @prep_fwdl: handshake before firmware download. + * @post_fwdl: handshake after firmware download. + * @post_nvramdl: handshake after nvram download. + * @chk_validation: handshake for firmware validation check. + * @post_wdreset: handshake after watchdog reset. + * @read: read value with register offset for the handshake. + * @write: write value with register offset for the handshake. + */ +struct brcmf_blhs { + u32 d2h; + u32 h2d; + void (*init)(struct brcmf_chip *pub); + int (*prep_fwdl)(struct brcmf_chip *pub); + int (*post_fwdl)(struct brcmf_chip *pub); + void (*post_nvramdl)(struct brcmf_chip *pub); + int (*chk_validation)(struct brcmf_chip *pub); + int (*post_wdreset)(struct brcmf_chip *pub); + u32 (*read)(void *ctx, u32 addr); + void (*write)(void *ctx, u32 addr, u32 value); +}; + +struct brcmf_ccsec { + u32 bus_corebase; + u32 erombase; + u32 chipid; }; int brcmf_chip_get_raminfo(struct brcmf_chip *pub); @@ -88,5 +132,9 @@ bool brcmf_chip_set_active(struct brcmf_chip *ci, u32 rstvec); bool brcmf_chip_sr_capable(struct brcmf_chip *pub); char *brcmf_chip_name(u32 chipid, u32 chiprev, char *buf, uint len); u32 brcmf_chip_enum_base(u16 devid); +void brcmf_chip_reset_watchdog(struct brcmf_chip *pub); +void brcmf_chip_ulp_reset_lhl_regs(struct brcmf_chip *pub); +void brcmf_chip_reset_pmu_regs(struct brcmf_chip *pub); +void brcmf_chip_set_default_min_res_mask(struct brcmf_chip *pub); #endif /* BRCMF_AXIDMP_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c index fc5232a896535..e882628afd93c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c @@ -20,6 +20,12 @@ #include "of.h" #include "firmware.h" #include "chip.h" +#include "defs.h" +#include "fweh.h" +#include +#include +#include +#include "pcie.h" MODULE_AUTHOR("Broadcom Corporation"); MODULE_DESCRIPTION("Broadcom 802.11 wireless LAN fullmac driver."); @@ -67,6 +73,18 @@ static int brcmf_iapp_enable; module_param_named(iapp, brcmf_iapp_enable, int, 0); MODULE_PARM_DESC(iapp, "Enable partial support for the obsoleted Inter-Access Point Protocol"); +static int brcmf_eap_restrict; +module_param_named(eap_restrict, brcmf_eap_restrict, int, 0400); +MODULE_PARM_DESC(eap_restrict, "Block non-802.1X frames until auth finished"); + +static int brcmf_max_pm; +module_param_named(max_pm, brcmf_max_pm, int, 0); +MODULE_PARM_DESC(max_pm, "Use max power management mode by default"); + +int brcmf_pkt_prio_enable; +module_param_named(pkt_prio, brcmf_pkt_prio_enable, int, 0); +MODULE_PARM_DESC(pkt_prio, "Support for update the packet priority"); + #ifdef DEBUG /* always succeed brcmf_bus_started() */ static int brcmf_ignore_probe_fail; @@ -74,9 +92,31 @@ module_param_named(ignore_probe_fail, brcmf_ignore_probe_fail, int, 0); MODULE_PARM_DESC(ignore_probe_fail, "always succeed probe for debugging"); #endif +static int brcmf_fw_ap_select; +module_param_named(fw_ap_select, brcmf_fw_ap_select, int, 0400); +MODULE_PARM_DESC(fw_ap_select, "Allow FW for AP selection"); + +static int brcmf_disable_6ghz; +module_param_named(disable_6ghz, brcmf_disable_6ghz, int, 0400); +MODULE_PARM_DESC(disable_6ghz, "Disable 6GHz Operation"); + +static int brcmf_sdio_in_isr; +module_param_named(sdio_in_isr, brcmf_sdio_in_isr, int, 0400); +MODULE_PARM_DESC(sdio_in_isr, "Handle SDIO DPC in ISR"); + +static int brcmf_sdio_rxf_in_kthread; +module_param_named(sdio_rxf_thread, brcmf_sdio_rxf_in_kthread, int, 0400); +MODULE_PARM_DESC(sdio_rxf_thread, "SDIO RX Frame in Kthread"); + static struct brcmfmac_platform_data *brcmfmac_pdata; struct brcmf_mp_global_t brcmf_mp_global; +static int brcmf_reboot_callback(struct notifier_block *this, unsigned long code, void *unused); +static struct notifier_block brcmf_reboot_notifier = { + .notifier_call = brcmf_reboot_callback, + .priority = 1, +}; + void brcmf_c_set_joinpref_default(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; @@ -123,6 +163,7 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) struct brcmf_bus *bus = drvr->bus_if; struct brcmf_dload_data_le *chunk_buf; const struct firmware *clm = NULL; + u8 clm_name[BRCMF_FW_NAME_LEN]; u32 chunk_len; u32 datalen; u32 cumulative_len; @@ -132,8 +173,15 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp) brcmf_dbg(TRACE, "Enter\n"); - err = brcmf_bus_get_blob(bus, &clm, BRCMF_BLOB_CLM); - if (err || !clm) { + memset(clm_name, 0, sizeof(clm_name)); + err = brcmf_bus_get_fwname(bus, ".clm_blob", clm_name); + if (err) { + bphy_err(drvr, "get CLM blob file name failed (%d)\n", err); + return err; + } + + err = firmware_request_nowarn(&clm, clm_name, bus->dev); + if (err) { brcmf_info("no clm_blob available (err=%d), device may have limited channels available\n", err); return 0; @@ -218,6 +266,8 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) char *clmver; char *ptr; s32 err; + struct eventmsgs_ext *eventmask_msg = NULL; + u8 msglen; if (is_valid_ether_addr(ifp->mac_addr)) { /* set mac address */ @@ -225,7 +275,7 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) if (err < 0) goto done; } else { - /* retrieve mac address */ + /* retrieve mac addresses */ err = brcmf_fil_iovar_data_get(ifp, "cur_etheraddr", ifp->mac_addr, sizeof(ifp->mac_addr)); if (err < 0) { @@ -298,20 +348,16 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) err); goto done; } - buf[sizeof(buf) - 1] = '\0'; ptr = (char *)buf; strsep(&ptr, "\n"); /* Print fw version info */ + brcmf_info("Murata Customized Version: imx-langdale-godzilla_r3.0\n"); brcmf_info("Firmware: %s %s\n", ri->chipname, buf); /* locate firmware version number for ethtool */ - ptr = strrchr(buf, ' '); - if (!ptr) { - bphy_err(drvr, "Retrieving version number failed"); - goto done; - } - strscpy(ifp->drvr->fwver, ptr + 1, sizeof(ifp->drvr->fwver)); + ptr = strrchr(buf, ' ') + 1; + strscpy(ifp->drvr->fwver, ptr, sizeof(ifp->drvr->fwver)); /* Query for 'clmver' to get CLM version info from firmware */ memset(buf, 0, sizeof(buf)); @@ -319,20 +365,23 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) if (err) { brcmf_dbg(TRACE, "retrieving clmver failed, %d\n", err); } else { - buf[sizeof(buf) - 1] = '\0'; clmver = (char *)buf; + /* store CLM version for adding it to revinfo debugfs file */ + memcpy(ifp->drvr->clmver, clmver, sizeof(ifp->drvr->clmver)); /* Replace all newline/linefeed characters with space * character */ strreplace(clmver, '\n', ' '); - /* store CLM version for adding it to revinfo debugfs file */ - memcpy(ifp->drvr->clmver, clmver, sizeof(ifp->drvr->clmver)); - brcmf_dbg(INFO, "CLM version = %s\n", clmver); } + /* set apsta */ + err = brcmf_fil_iovar_int_set(ifp, "apsta", 1); + if (err) + brcmf_info("failed setting apsta, %d\n", err); + /* set mpc */ err = brcmf_fil_iovar_int_set(ifp, "mpc", 1); if (err) { @@ -357,6 +406,43 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) goto done; } + /* Enable event_msg_ext specific to 43012 chip */ + if (bus->chip == CY_CC_43012_CHIP_ID) { + /* Program event_msg_ext to support event larger than 128 */ + msglen = (roundup(BRCMF_E_LAST, NBBY) / NBBY) + + EVENTMSGS_EXT_STRUCT_SIZE; + /* Allocate buffer for eventmask_msg */ + eventmask_msg = kzalloc(msglen, GFP_KERNEL); + if (!eventmask_msg) { + err = -ENOMEM; + goto done; + } + + /* Read the current programmed event_msgs_ext */ + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->len = roundup(BRCMF_E_LAST, NBBY) / NBBY; + err = brcmf_fil_iovar_data_get(ifp, "event_msgs_ext", + eventmask_msg, + msglen); + + /* Enable ULP event */ + brcmf_dbg(EVENT, "enable event ULP\n"); + setbit(eventmask_msg->mask, BRCMF_E_ULP); + + /* Write updated Event mask */ + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->command = EVENTMSGS_SET_MASK; + eventmask_msg->len = (roundup(BRCMF_E_LAST, NBBY) / NBBY); + + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", + eventmask_msg, msglen); + if (err) { + brcmf_err("Set event_msgs_ext error (%d)\n", err); + kfree(eventmask_msg); + goto done; + } + kfree(eventmask_msg); + } /* Setup default scan channel time */ err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, BRCMF_DEFAULT_SCAN_CHANNEL_TIME); @@ -377,6 +463,20 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) /* Enable tx beamforming, errors can be ignored (not supported) */ (void)brcmf_fil_iovar_int_set(ifp, "txbf", 1); + err = brcmf_fil_iovar_int_set(ifp, "chanspec", 0x1001); + if (err < 0) + bphy_err(drvr, "Initial Channel failed %d\n", err); + /* add unicast packet filter */ + err = brcmf_pktfilter_add_remove(ifp->ndev, + BRCMF_UNICAST_FILTER_NUM, true); + if (err == -BRCMF_FW_UNSUPPORTED) { + /* FW not support can be ignored */ + err = 0; + goto done; + } else if (err) { + bphy_err(drvr, "Add unicast filter error (%d)\n", err); + } + done: return err; } @@ -448,15 +548,22 @@ struct brcmf_mp_device *brcmf_get_module_param(struct device *dev, if (!settings) return NULL; - /* start by using the module paramaters */ + /* start by using the module parameters */ settings->p2p_enable = !!brcmf_p2p_enable; settings->feature_disable = brcmf_feature_disable; settings->fcmode = brcmf_fcmode; settings->roamoff = !!brcmf_roamoff; settings->iapp = !!brcmf_iapp_enable; + settings->eap_restrict = !!brcmf_eap_restrict; + settings->default_pm = !!brcmf_max_pm ? PM_MAX : PM_FAST; #ifdef DEBUG settings->ignore_probe_fail = !!brcmf_ignore_probe_fail; #endif + settings->fw_ap_select = !!brcmf_fw_ap_select; + settings->disable_6ghz = !!brcmf_disable_6ghz; + settings->sdio_in_isr = !!brcmf_sdio_in_isr; + settings->pkt_prio = !!brcmf_pkt_prio_enable; + settings->sdio_rxf_in_kthread_enabled = !!brcmf_sdio_rxf_in_kthread; if (bus_type == BRCMF_BUSTYPE_SDIO) settings->bus.sdio.txglomsz = brcmf_sdiod_txglomsz; @@ -495,13 +602,22 @@ void brcmf_release_module_param(struct brcmf_mp_device *module_param) kfree(module_param); } +static int +brcmf_reboot_callback(struct notifier_block *this, unsigned long code, void *unused) +{ + brcmf_dbg(INFO, "code = %ld\n", code); + if (code == SYS_RESTART) + brcmf_core_exit(); + return NOTIFY_DONE; +} + static int __init brcmf_common_pd_probe(struct platform_device *pdev) { brcmf_dbg(INFO, "Enter\n"); brcmfmac_pdata = dev_get_platdata(&pdev->dev); - if (brcmfmac_pdata->power_on) + if (brcmfmac_pdata && brcmfmac_pdata->power_on) brcmfmac_pdata->power_on(); return 0; @@ -533,7 +649,7 @@ static int __init brcmfmac_module_init(void) if (err == -ENODEV) brcmf_dbg(INFO, "No platform data available.\n"); - /* Initialize global module paramaters */ + /* Initialize global module parameters */ brcmf_mp_attach(); /* Continue the initialization by registering the different busses */ @@ -541,6 +657,8 @@ static int __init brcmfmac_module_init(void) if (err) { if (brcmfmac_pdata) platform_driver_unregister(&brcmf_pd); + } else { + register_reboot_notifier(&brcmf_reboot_notifier); } return err; @@ -549,6 +667,7 @@ static int __init brcmfmac_module_init(void) static void __exit brcmfmac_module_exit(void) { brcmf_core_exit(); + unregister_reboot_notifier(&brcmf_reboot_notifier); if (brcmfmac_pdata) platform_driver_unregister(&brcmf_pd); } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h index aa25abffcc7db..59671e68a8171 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h @@ -37,10 +37,16 @@ extern struct brcmf_mp_global_t brcmf_mp_global; * @feature_disable: Feature_disable bitmask. * @fcmode: FWS flow control. * @roamoff: Firmware roaming off? + * @eap_restrict: Not allow data tx/rx until 802.1X auth succeeds + * @default_pm: default power management (PM) mode. * @ignore_probe_fail: Ignore probe failure. * @trivial_ccode_map: Assume firmware uses ISO3166 country codes with rev 0 + * @fw_ap_select: Allow FW to select AP. + * @disable_6ghz: Disable 6GHz operation + * @sdio_in_isr: Handle SDIO DPC in ISR. * @country_codes: If available, pointer to struct for translating country codes * @bus: Bus specific platform data. Only SDIO at the mmoment. + * @pkt_prio: Support customer dscp to WMM up mapping. */ struct brcmf_mp_device { bool p2p_enable; @@ -48,8 +54,14 @@ struct brcmf_mp_device { int fcmode; bool roamoff; bool iapp; + bool eap_restrict; + int default_pm; bool ignore_probe_fail; bool trivial_ccode_map; + bool fw_ap_select; + bool disable_6ghz; + bool sdio_in_isr; + bool sdio_rxf_in_kthread_enabled; struct brcmfmac_pd_cc *country_codes; const char *board_type; unsigned char mac[ETH_ALEN]; @@ -57,6 +69,7 @@ struct brcmf_mp_device { union { struct brcmfmac_sdio_pd sdio; } bus; + bool pkt_prio; }; void brcmf_c_set_joinpref_default(struct brcmf_if *ifp); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index 175272c2694d7..48264520115fa 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include "core.h" #include "bus.h" @@ -28,6 +30,7 @@ #include "proto.h" #include "pcie.h" #include "common.h" +#include "twt.h" #define MAX_WAIT_FOR_8021X_TX msecs_to_jiffies(950) @@ -37,6 +40,23 @@ #define D11_PHY_HDR_LEN 6 +#define WL_CNT_XTLV_SLICE_IDX 256 + +#define IOVAR_XTLV_BEGIN 4 + +#define XTLV_TYPE_SIZE 2 + +#define XTLV_TYPE_LEN_SIZE 4 + +#define WL_CNT_IOV_BUF 2048 + +#define CNT_VER_6 6 +#define CNT_VER_10 10 +#define CNT_VER_30 30 + +/* Macro to calculate packing factor with scalar 4 in a xTLV */ +#define PACKING_FACTOR(args) ((args) % 4 == 0 ? 0 : (4 - ((args) % 4))) + struct d11rxhdr_le { __le16 RxFrameSize; u16 PAD; @@ -63,6 +83,142 @@ struct wlc_d11rxhdr { s8 rxpwr[4]; } __packed; +static const char fmac_ethtool_string_stats_v6[][ETH_GSTRING_LEN] = { + "txbyte", "txerror", "txprshort", "txnobuf", "txrunt", "txcmiss", "txphyerr", + "rxframe", "rxerror", "rxnobuf", "rxbadds", "rxfragerr", + "rxgiant", "rxbadproto", "rxbadda", "rxoflo", "d11cnt_rxcrc_off", "dmade", + "dmape", "tbtt", "pkt_callback_reg_fail", "txackfrm", "txbcnfrm", "rxtoolate", + "txtplunfl", "rxinvmachdr", "rxbadplcp", "rxstrt", "rxmfrmucastmbss", + "rxrtsucast", "rxackucast", "rxmfrmocast", "rxrtsocast", "rxdfrmmcast", + "rxcfrmmcast", "rxdfrmucastobss", "rxrsptmout", "rxf0ovfl", "rxf2ovfl", "pmqovfl", + "frmscons", "rxback", "txfrag", "txfail", "txretrie", "txrts", "txnoack", "rxmulti", + + "txfrmsnt", "tkipmicfaill", "tkipreplay", "ccmpreplay", "fourwayfail", "wepicverr", + "tkipicverr", "tkipmicfaill_mcst", "tkipreplay_mcst", "ccmpreplay_mcst", + "fourwayfail_mcst", "wepicverr_mcst", "tkipicverr_mcst", "txexptime", "phywatchdog", + "prq_undirected_entries", "atim_suppress_count", "bcn_template_not_ready_done", + + "rx1mbps", "rx5mbps5", "rx9mbps", "rx12mbps", "rx24mbps", "rx48mbps", + "rx108mbps", "rx216mbps", "rx324mbps", "rx432mbps", "rx540mbps", + "pktengrxdmcast", "bphy_txmpdu_sgi", "txmpdu_stbc", "rxdrop20s", +}; + +static const char fmac_ethtool_string_stats_v10[][ETH_GSTRING_LEN] = { + "txframe", "txbyte", "txretrans", "txerror", "txctl", "txprshort", + "txserr", "txnobuf", "txnoassoc", "txrunt", + "txchit", "txcmiss", "txphyerr", "txphycrs", "rxframe", "rxbyte", + "rxerror", "rxctl", "rxnobuf", "rxnondata", + "rxbadds", "rxbadcm", "rxfragerr", "rxrunt", "rxgiant", "rxnoscb", + "rxbadprot", "rxbadsrcma", "rxbadda", "rxfilter", + "rxoflo", "rxuflo[0]", "rxuflo[1]", "rxuflo[2]", "rxuflo[3]", + "rxuflo[4]", "rxuflo[5]", "d11cnt_rxcrc_off", "d11cnt_txnocts_off", + "dmade", "dmada", "dmape", "reset", "tbtt", "txdmawar", + "pkt_callback_reg_fail", "txallfrm", "txrtsfrm", "txctsfrm", + "txackfrm", "txdnlfrm", "txbcnfrm", "txfunfl[0]", "txfunfl[1]", + "txfunfl[2]", "txfunfl[3]", "txfunfl[4]", "txfunfl[5]", "rxtoolate", + "txfbw", "txtplunfl", "txphyerror", "rxfrmtoolong", "rxfrmtooshrt", + "rxinvmachdr", "rxbadfcs", "rxbadplcp", "rxcrsglitch", + "rxstrt", "rxdfrmucastmbss", "rxmfrmucastmbss", "rxcfrmucast", + "rxrtsucast", "rxctsucast", "rxackucast", + "rxdfrmocast", "rxmfrmocast", "rxcfrmocast", "rxrtsocast", + "rxctsocast", "rxdfrmmcast", "rxmfrmmcast", "rxcfrmmcast", + "rxbeaconmbss", "rxdfrmucastobss", "rxbeaconobss", "rxrsptmout", + "bcntxcancl", "rxf0ovfl", "rxf1ovfl", "rxf2ovfl", "txsfovfl", + "pmqovfl", "rxcgprqfrm", "rxcgprsqovfl", "txcgprsfail", "txcgprssuc", "prs_timeout", + "rxnack", "frmscons", "txnack", "rxback", "txback", "txfrag", "txmulti", "txfail", + "txretry", "txretrie", "rxdup", "txrts", "txnocts", "txnoack", "rxfrag", "rxmulti", + "rxcrc", "txfrmsnt", "rxundec", "tkipmicfaill", "tkipcntrmsr", "tkipreplay", "ccmpfmterr", + "ccmpreplay", "ccmpundec", "fourwayfail", "wepundec", "wepicverr", "decsuccess", + "tkipicverr", "wepexcluded", "psmwds", "phywatchdog", "prq_entries_handled", + "prq_undirected_entries", "prq_bad_entries", "atim_suppress_count", + "bcn_template_not_ready", "bcn_template_not_ready_done", "late_tbtt_dpc", + "rx1mbps", "rx2mbps", "rx5mbps5", "rx6mbps", "rx9mbps", "rx11mbps", "rx12mbps", "rx18mbps", + "rx24mbps", "rx36mbps", "rx48mbps", "rx54mbps", "rx108mbps", "rx162mbps", + "rx216mbps", "rx270mbps", "rx324mbps", "rx378mbps", "rx432mbps", "rx486mbps", "rx540mbps", + "pktengrxducast", "pktengrxdmcast", "bphy_rxcrsglitch", "bphy_b", "txexptime", + "rxmpdu_sgi", "txmpdu_stbc", "rxmpdu_stbc", "tkipmicfaill_mcst", "tkipcntrmsr_mcst", + "tkipreplay_mcst", "ccmpfmterr_mcst", "ccmpreplay_mcst", "ccmpundec_mcst", + "fourwayfail_mcst", "wepundec_mcst", "wepicverr_mcst", "decsuccess_mcst", + "tkipicverr_mcst", "wepexcluded_mcst", "reinit", "pstatxnoassoc", + "pstarxucast", "pstarxbcmc", "pstatxbcmc", "cso_normal", "chained", + "chainedsz1", "unchained", "maxchainsz", "currchainsz", "rxdrop20s", + "pciereset", "cfgrestore", "reinitreason[0]", "reinitreason[1]", + "reinitreason[2]", "reinitreason[3]", "reinitreason[4]", + "reinitreason[5]", "reinitreason[6]", "reinitreason[7]", "rxrtry", +}; + +static const char fmac_ethtool_string_stats_v30[][ETH_GSTRING_LEN] = { + "txframe", "txbyte", "txretrans", "txerror", "txctl", "txprshort", "txserr", "txnobuf", + "txnoassoc", "txrunt", "txchit", "txcmiss", "txuflo", "txphyerr", "txphycrs", + "rxframe", "rxbyte", "rxerror", "rxctl", "rxnobuf", "rxnondata", "rxbadds", "rxbadcm", + "rxfragerr", "rxrunt", "rxgiant", "rxnoscb", "rxbadproto", "rxbadsrcmac", + "rxbadda", "rxfilter", "rxoflo", "rxuflo[0]", "rxuflo[1]", + "rxuflo[2]", "rxuflo[3]", "rxuflo[4]", "rxuflo[5]", + + "d11cnt_txrts_off", "d11cnt_rxcrc_off", "d11cnt_txnocts_off", "dmade", "dmada", + "dmape", "reset", "tbtt", "txdmawar", "pkt_callback_reg_fail", + "txfrag", "txmulti", "txfail", "txretry", + "txretrie", "rxdup", "txrts", "txnocts", "txnoack", "rxfrag", + "rxmulti", "rxcrc", "txfrmsnt", "rxundec", + "tkipmicfaill", "tkipcntrmsr", "tkipreplay", "ccmpfmterr", + "ccmpreplay", "ccmpundec", "fourwayfail", "wepundec", + "wepicverr", "decsuccess", "tkipicverr", "wepexcluded", + "txchanrej", "psmwds", "phywatchdog", + "prq_entries_handled", "prq_undirected_entries", "prq_bad_entries", + "atim_suppress_count", "bcn_template_not_ready", "bcn_template_not_ready_done", + "late_tbtt_dpc", + + "rx1mbps", "rx2mbps", "rx5mbps5", "rx6mbps", "rx9mbps", + "rx11mbps", "rx12mbps", "rx18mbps", "rx24mbps", "rx36mbps", + "rx48mbps", "rx54mbps", "rx108mbps", "rx162mbps", "rx216mbps", + "rx270mbps", "rx324mbps", "rx378mbps", "rx432mbps", "rx486mbps", + "rx540mbps", "rfdisable", "txexptime", "txmpdu_sgi", "rxmpdu_sgi", + "txmpdu_stbc", "rxmpdu_stbc", "rxundec_mcst", + + "tkipmicfaill_mcst", "tkipcntrmsr_mcst", "tkipreplay_mcst", + "ccmpfmterr_mcst", "ccmpreplay_mcst", "ccmpundec_mcst", + "fourwayfail_mcst", "wepundec_mcst", "wepicverr_mcst", + "decsuccess_mcst", "tkipicverr_mcst", "wepexcluded_mcst", + "dma_hang", "reinit", "pstatxucast", + "pstatxnoassoc", "pstarxucast", "pstarxbcmc", + "pstatxbcmc", "cso_passthrough", "cso_normal", + "chained", "chainedsz1", "unchained", + "maxchainsz", "currchainsz", "pciereset", + "cfgrestore", "reinitreason[0]", "reinitreason[1]", + "reinitreason[2]", "reinitreason[3]", "reinitreason[4]", + "reinitreason[5]", "reinitreason[6]", "reinitreason[7]", + "rxrtry", "rxmpdu_mu", + + "txbar", "rxbar", "txpspoll", "rxpspoll", "txnull", + "rxnull", "txqosnull", "rxqosnull", "txassocreq", "rxassocreq", + "txreassocreq", "rxreassocreq", "txdisassoc", "rxdisassoc", + "txassocrsp", "rxassocrsp", "txreassocrsp", "rxreassocrsp", + "txauth", "rxauth", "txdeauth", "rxdeauth", "txprobereq", + "rxprobereq", "txprobersp", "rxprobersp", "txaction", + "rxaction", "ampdu_wds", "txlost", "txdatamcast", + "txdatabcast", "psmxwds", "rxback", "txback", + "p2p_tbtt", "p2p_tbtt_miss", "txqueue_start", "txqueue_end", + "txbcast", "txdropped", "rxbcast", "rxdropped", + "txq_end_assoccb", +}; + +#define BRCMF_IF_STA_LIST_LOCK_INIT(ifp) spin_lock_init(&(ifp)->sta_list_lock) +#define BRCMF_IF_STA_LIST_LOCK(ifp, flags) \ + spin_lock_irqsave(&(ifp)->sta_list_lock, (flags)) +#define BRCMF_IF_STA_LIST_UNLOCK(ifp, flags) \ + spin_unlock_irqrestore(&(ifp)->sta_list_lock, (flags)) + +#define BRCMF_STA_NULL ((struct brcmf_sta *)NULL) + +/* dscp exception format {dscp hex, up} */ +struct cfg80211_dscp_exception dscp_excpt[] = { +{DSCP_EF, 6}, {DSCP_CS4, 5}, {DSCP_AF41, 5}, {DSCP_CS3, 4} }; + +/* dscp range : up[0 ~ 7] */ +struct cfg80211_dscp_range dscp_range[8] = { +{0, 7}, {8, 15}, {16, 23}, {24, 31}, +{32, 39}, {40, 47}, {48, 55}, {56, 63} }; + char *brcmf_ifname(struct brcmf_if *ifp) { if (!ifp) @@ -97,6 +253,11 @@ void brcmf_configure_arp_nd_offload(struct brcmf_if *ifp, bool enable) s32 err; u32 mode; + if (enable && brcmf_is_apmode_operating(ifp->drvr->wiphy)) { + brcmf_dbg(TRACE, "Skip ARP/ND offload enable when soft AP is running\n"); + return; + } + if (enable) mode = BRCMF_ARP_OL_AGENT | BRCMF_ARP_OL_PEER_AUTO_REPLY; else @@ -493,7 +654,7 @@ static int brcmf_rx_hdrpull(struct brcmf_pub *drvr, struct sk_buff *skb, return 0; } -void brcmf_rx_frame(struct device *dev, struct sk_buff *skb, bool handle_event, +struct sk_buff *brcmf_rx_frame(struct device *dev, struct sk_buff *skb, bool handle_event, bool inirq) { struct brcmf_if *ifp; @@ -503,7 +664,7 @@ void brcmf_rx_frame(struct device *dev, struct sk_buff *skb, bool handle_event, brcmf_dbg(DATA, "Enter: %s: rxp=%p\n", dev_name(dev), skb); if (brcmf_rx_hdrpull(drvr, skb, &ifp)) - return; + return NULL; if (brcmf_proto_is_reorder_skb(skb)) { brcmf_proto_rxreorder(ifp, skb); @@ -515,8 +676,14 @@ void brcmf_rx_frame(struct device *dev, struct sk_buff *skb, bool handle_event, brcmf_fweh_process_skb(ifp->drvr, skb, BCMILCP_SUBTYPE_VENDOR_LONG, gfp); } + + /* if sdio_rxf_in_kthread, enqueue it and process it later. */ + if (brcmf_feat_is_sdio_rxf_in_kthread(drvr)) + return skb; + else brcmf_netif_rx(ifp, skb); } + return NULL; } void brcmf_rx_event(struct device *dev, struct sk_buff *skb) @@ -568,10 +735,148 @@ static void brcmf_ethtool_get_drvinfo(struct net_device *ndev, strscpy(info->fw_version, drvr->fwver, sizeof(info->fw_version)); strscpy(info->bus_info, dev_name(drvr->bus_if->dev), sizeof(info->bus_info)); + + if (!drvr->cnt_ver) { + int ret; + u8 *iovar_out; + + iovar_out = kzalloc(WL_CNT_IOV_BUF, GFP_KERNEL); + if (!iovar_out) + return; + ret = brcmf_fil_iovar_data_get(ifp, "counters", iovar_out, WL_CNT_IOV_BUF); + if (ret) { + brcmf_err("Failed to get counters, code :%d\n", ret); + goto done; + } + memcpy(&drvr->cnt_ver, iovar_out, sizeof(drvr->cnt_ver)); +done: + kfree(iovar_out); + iovar_out = NULL; + } +} + +static void brcmf_et_get_strings(struct net_device *net_dev, + u32 sset, u8 *strings) +{ + struct brcmf_if *ifp = netdev_priv(net_dev); + struct brcmf_pub *drvr = ifp->drvr; + + if (sset == ETH_SS_STATS) { + switch (drvr->cnt_ver) { + case CNT_VER_6: + memcpy(strings, fmac_ethtool_string_stats_v6, + sizeof(fmac_ethtool_string_stats_v6)); + break; + case CNT_VER_10: + memcpy(strings, fmac_ethtool_string_stats_v10, + sizeof(fmac_ethtool_string_stats_v10)); + break; + case CNT_VER_30: + memcpy(strings, fmac_ethtool_string_stats_v30, + sizeof(fmac_ethtool_string_stats_v30)); + break; + default: + brcmf_err("Unsupported counters version\n"); + } + } +} + +static int brcmf_find_wlc_cntr_tlv(u8 *src, u16 *len) +{ + u16 tlv_id, data_len; + u16 packing_offset, cur_tlv = IOVAR_XTLV_BEGIN; + + while (cur_tlv < *len) { + memcpy(&tlv_id, (src + cur_tlv), sizeof(*len)); + memcpy(&data_len, (src + cur_tlv + XTLV_TYPE_SIZE), sizeof(*len)); + if (tlv_id == WL_CNT_XTLV_SLICE_IDX) { + *len = data_len; + return cur_tlv; + } + /* xTLV data has 4 bytes packing. So caclculate the packing offset using the data */ + packing_offset = PACKING_FACTOR(data_len); + cur_tlv += XTLV_TYPE_LEN_SIZE + data_len + packing_offset; + } + return -EINVAL; +} + +static void brcmf_et_get_stats(struct net_device *netdev, + struct ethtool_stats *et_stats, u64 *results_buf) +{ + struct brcmf_if *ifp = netdev_priv(netdev); + u8 *iovar_out, *src, ret; + u16 version, len, xTLV_wl_cnt_offset = 0; + u16 soffset = 0, idx = 0; + + iovar_out = kzalloc(WL_CNT_IOV_BUF, GFP_KERNEL); + + if (!iovar_out) + return; + + ret = brcmf_fil_iovar_data_get(ifp, "counters", iovar_out, WL_CNT_IOV_BUF); + if (ret) { + brcmf_err("Failed to get counters, code :%d\n", ret); + goto done; + } + src = iovar_out; + + memcpy(&version, src, sizeof(version)); + soffset += sizeof(version); + memcpy(&len, (src + soffset), sizeof(len)); + soffset += sizeof(len); + + /* Check counters version and decide if its non-TLV or TLV (version>=30)*/ + if (version >= CNT_VER_30) { + xTLV_wl_cnt_offset = brcmf_find_wlc_cntr_tlv(src, &len); + len = (len / sizeof(u32)); + } else { + len = (len / sizeof(u32)) - sizeof(u32); + } + + src = src + soffset + xTLV_wl_cnt_offset; + while (idx < (len)) { + results_buf[idx++] = *((u32 *)src); + src += sizeof(u32); + } +done: + kfree(iovar_out); + iovar_out = NULL; +} + +static int brcmf_et_get_scount(struct net_device *dev, int sset) +{ + u16 array_size; + struct brcmf_if *ifp = netdev_priv(dev); + struct brcmf_pub *drvr = ifp->drvr; + + if (sset == ETH_SS_STATS) { + switch (drvr->cnt_ver) { + case CNT_VER_6: + array_size = ARRAY_SIZE(fmac_ethtool_string_stats_v6); + break; + case CNT_VER_10: + array_size = ARRAY_SIZE(fmac_ethtool_string_stats_v10); + break; + case CNT_VER_30: + array_size = ARRAY_SIZE(fmac_ethtool_string_stats_v30); + break; + default: + brcmf_err("Unsupported counters version\n"); + return -EOPNOTSUPP; + } + } else { + brcmf_dbg(INFO, "Does not support ethtool string set %d\n", sset); + return -EOPNOTSUPP; + } + return array_size; } static const struct ethtool_ops brcmf_ethtool_ops = { .get_drvinfo = brcmf_ethtool_get_drvinfo, + .get_ts_info = ethtool_op_get_ts_info, + .get_strings = brcmf_et_get_strings, + .get_ethtool_stats = brcmf_et_get_stats, + .get_sset_count = brcmf_et_get_scount, }; static int brcmf_netdev_stop(struct net_device *ndev) @@ -894,6 +1199,13 @@ struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx, init_waitqueue_head(&ifp->pend_8021x_wait); spin_lock_init(&ifp->netif_stop_lock); + BRCMF_IF_STA_LIST_LOCK_INIT(ifp); + /* Initialize STA info list */ + INIT_LIST_HEAD(&ifp->sta_list); + + spin_lock_init(&ifp->twt_sess_list_lock); + /* Initialize TWT Session list */ + INIT_LIST_HEAD(&ifp->twt_sess_list); if (mac_addr != NULL) memcpy(ifp->mac_addr, mac_addr, ETH_ALEN); @@ -1126,6 +1438,15 @@ static int brcmf_inet6addr_changed(struct notifier_block *nb, } #endif + +int brcmf_fwlog_attach(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + + return brcmf_debug_fwlog_init(drvr); +} + static int brcmf_revinfo_read(struct seq_file *s, void *data) { struct brcmf_bus *bus_if = dev_get_drvdata(s->private); @@ -1192,6 +1513,7 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops) struct brcmf_bus *bus_if = drvr->bus_if; struct brcmf_if *ifp; struct brcmf_if *p2p_ifp; + int i, num; brcmf_dbg(TRACE, "\n"); @@ -1231,6 +1553,22 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops) goto fail; } + /* update custom DSCP to PRIO mapping */ + if (drvr->settings->pkt_prio) { + drvr->qos_map = kzalloc(sizeof(struct cfg80211_qos_map), GFP_KERNEL); + if (!drvr->qos_map) { + ret = -ENOMEM; + goto fail; + } + num = sizeof(dscp_excpt) / (sizeof(struct cfg80211_dscp_exception)); + drvr->qos_map->num_des = num; + for (i = 0; i < num; i++) { + drvr->qos_map->dscp_exception[i].dscp = dscp_excpt[i].dscp; + drvr->qos_map->dscp_exception[i].up = dscp_excpt[i].up; + } + memcpy(drvr->qos_map->up, dscp_range, sizeof(dscp_range[8])); + } + ret = brcmf_net_attach(ifp, false); if ((!ret) && (drvr->settings->p2p_enable)) { @@ -1267,6 +1605,7 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops) brcmf_feat_debugfs_create(drvr); brcmf_proto_debugfs_create(drvr); brcmf_bus_debugfs_create(bus_if); + brcmf_twt_debugfs_create(drvr); return 0; @@ -1316,7 +1655,7 @@ int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings) return 0; } -int brcmf_attach(struct device *dev) +int brcmf_attach(struct device *dev, bool start_bus) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); struct brcmf_pub *drvr = bus_if->drvr; @@ -1333,6 +1672,7 @@ int brcmf_attach(struct device *dev) /* Link to bus module */ drvr->hdrlen = 0; + drvr->req_mpc = 1; /* Attach and link in the protocol */ ret = brcmf_proto_attach(drvr); if (ret != 0) { @@ -1347,10 +1687,13 @@ int brcmf_attach(struct device *dev) /* attach firmware event handler */ brcmf_fweh_attach(drvr); + if (start_bus) { ret = brcmf_bus_started(drvr, drvr->ops); if (ret != 0) { - bphy_err(drvr, "dongle is not responding: err=%d\n", ret); + bphy_err(drvr, "dongle is not responding: err=%d\n", + ret); goto fail; + } } return 0; @@ -1400,6 +1743,7 @@ void brcmf_fw_crashed(struct device *dev) brcmf_dev_coredump(dev); + if (drvr->bus_reset.func) schedule_work(&drvr->bus_reset); } @@ -1430,6 +1774,11 @@ void brcmf_detach(struct device *dev) } brcmf_bus_stop(drvr->bus_if); + if (drvr->settings->pkt_prio) { + kfree(drvr->qos_map); + drvr->qos_map = NULL; + } + brcmf_fweh_detach(drvr); brcmf_proto_detach(drvr); @@ -1548,3 +1897,270 @@ void __exit brcmf_core_exit(void) brcmf_pcie_exit(); } +int +brcmf_pktfilter_add_remove(struct net_device *ndev, int filter_num, bool add) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_pkt_filter_le *pkt_filter; + int filter_fixed_len = offsetof(struct brcmf_pkt_filter_le, u); + int pattern_fixed_len = offsetof(struct brcmf_pkt_filter_pattern_le, + mask_and_pattern); + u16 mask_and_pattern[MAX_PKTFILTER_PATTERN_SIZE]; + int buflen = 0; + int ret = 0; + + brcmf_dbg(INFO, "%s packet filter number %d\n", + (add ? "add" : "remove"), filter_num); + + pkt_filter = kzalloc(sizeof(*pkt_filter) + + (MAX_PKTFILTER_PATTERN_SIZE * 2), GFP_ATOMIC); + if (!pkt_filter) + return -ENOMEM; + + switch (filter_num) { + case BRCMF_UNICAST_FILTER_NUM: + pkt_filter->id = 100; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 1; + mask_and_pattern[0] = 0x0001; + break; + case BRCMF_BROADCAST_FILTER_NUM: + //filter_pattern = "101 0 0 0 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF"; + pkt_filter->id = 101; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 6; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0xFFFF; + mask_and_pattern[2] = 0xFFFF; + mask_and_pattern[3] = 0xFFFF; + mask_and_pattern[4] = 0xFFFF; + mask_and_pattern[5] = 0xFFFF; + break; + case BRCMF_MULTICAST4_FILTER_NUM: + //filter_pattern = "102 0 0 0 0xFFFFFF 0x01005E"; + pkt_filter->id = 102; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 3; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0x01FF; + mask_and_pattern[2] = 0x5E00; + break; + case BRCMF_MULTICAST6_FILTER_NUM: + //filter_pattern = "103 0 0 0 0xFFFF 0x3333"; + pkt_filter->id = 103; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 2; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0x3333; + break; + case BRCMF_MDNS_FILTER_NUM: + //filter_pattern = "104 0 0 0 0xFFFFFFFFFFFF 0x01005E0000FB"; + pkt_filter->id = 104; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 6; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0xFFFF; + mask_and_pattern[2] = 0xFFFF; + mask_and_pattern[3] = 0x0001; + mask_and_pattern[4] = 0x005E; + mask_and_pattern[5] = 0xFB00; + break; + case BRCMF_ARP_FILTER_NUM: + //filter_pattern = "105 0 0 12 0xFFFF 0x0806"; + pkt_filter->id = 105; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 12; + pkt_filter->u.pattern.size_bytes = 2; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0x0608; + break; + case BRCMF_BROADCAST_ARP_FILTER_NUM: + //filter_pattern = "106 0 0 0 + //0xFFFFFFFFFFFF0000000000000806 + //0xFFFFFFFFFFFF0000000000000806"; + pkt_filter->id = 106; + pkt_filter->type = 0; + pkt_filter->negate_match = 0; + pkt_filter->u.pattern.offset = 0; + pkt_filter->u.pattern.size_bytes = 14; + mask_and_pattern[0] = 0xFFFF; + mask_and_pattern[1] = 0xFFFF; + mask_and_pattern[2] = 0xFFFF; + mask_and_pattern[3] = 0x0000; + mask_and_pattern[4] = 0x0000; + mask_and_pattern[5] = 0x0000; + mask_and_pattern[6] = 0x0608; + mask_and_pattern[7] = 0xFFFF; + mask_and_pattern[8] = 0xFFFF; + mask_and_pattern[9] = 0xFFFF; + mask_and_pattern[10] = 0x0000; + mask_and_pattern[11] = 0x0000; + mask_and_pattern[12] = 0x0000; + mask_and_pattern[13] = 0x0608; + break; + default: + ret = -EINVAL; + goto failed; + } + memcpy(pkt_filter->u.pattern.mask_and_pattern, mask_and_pattern, + pkt_filter->u.pattern.size_bytes * 2); + buflen = filter_fixed_len + pattern_fixed_len + + pkt_filter->u.pattern.size_bytes * 2; + + if (add) { + /* Add filter */ + ifp->fwil_fwerr = true; + ret = brcmf_fil_iovar_data_set(ifp, "pkt_filter_add", + pkt_filter, buflen); + ifp->fwil_fwerr = false; + if (ret) + goto failed; + drvr->pkt_filter[filter_num].id = pkt_filter->id; + drvr->pkt_filter[filter_num].enable = 0; + + } else { + /* Delete filter */ + ifp->fwil_fwerr = true; + ret = brcmf_fil_iovar_int_set(ifp, "pkt_filter_delete", + pkt_filter->id); + ifp->fwil_fwerr = false; + if (ret == -BRCMF_FW_BADARG) + ret = 0; + if (ret) + goto failed; + + drvr->pkt_filter[filter_num].id = 0; + drvr->pkt_filter[filter_num].enable = 0; + } +failed: + if (ret) + brcmf_err("%s packet filter failed, ret=%d\n", + (add ? "add" : "remove"), ret); + + kfree(pkt_filter); + return ret; +} + +int brcmf_pktfilter_enable(struct net_device *ndev, bool enable) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + int ret = 0; + int idx = 0; + + for (idx = 0; idx < MAX_PKT_FILTER_COUNT; ++idx) { + if (drvr->pkt_filter[idx].id != 0) { + drvr->pkt_filter[idx].enable = enable; + ret = brcmf_fil_iovar_data_set(ifp, "pkt_filter_enable", + &drvr->pkt_filter[idx], + sizeof(struct brcmf_pkt_filter_enable_le)); + if (ret) { + brcmf_err("%s packet filter id(%d) failed, ret=%d\n", + (enable ? "enable" : "disable"), + drvr->pkt_filter[idx].id, ret); + } + } + } + return ret; +} + +/** Find STA with MAC address ea in an interface's STA list. */ +struct brcmf_sta * +brcmf_find_sta(struct brcmf_if *ifp, const u8 *ea) +{ + struct brcmf_sta *sta; + unsigned long flags; + + BRCMF_IF_STA_LIST_LOCK(ifp, flags); + list_for_each_entry(sta, &ifp->sta_list, list) { + if (!memcmp(sta->ea.octet, ea, ETH_ALEN)) { + brcmf_dbg(INFO, "Found STA: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x into sta list\n", + sta->ea.octet[0], sta->ea.octet[1], + sta->ea.octet[2], sta->ea.octet[3], + sta->ea.octet[4], sta->ea.octet[5]); + BRCMF_IF_STA_LIST_UNLOCK(ifp, flags); + return sta; + } + } + BRCMF_IF_STA_LIST_UNLOCK(ifp, flags); + + return BRCMF_STA_NULL; +} + +/** Add STA into the interface's STA list. */ +struct brcmf_sta * +brcmf_add_sta(struct brcmf_if *ifp, const u8 *ea) +{ + struct brcmf_sta *sta; + unsigned long flags; + + sta = kzalloc(sizeof(*sta), GFP_KERNEL); + if (sta == BRCMF_STA_NULL) { + brcmf_err("Alloc failed\n"); + return BRCMF_STA_NULL; + } + memcpy(sta->ea.octet, ea, ETH_ALEN); + brcmf_dbg(INFO, "Add STA: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x into sta list\n", + sta->ea.octet[0], sta->ea.octet[1], + sta->ea.octet[2], sta->ea.octet[3], + sta->ea.octet[4], sta->ea.octet[5]); + + /* link the sta and the dhd interface */ + sta->ifp = ifp; + INIT_LIST_HEAD(&sta->list); + + BRCMF_IF_STA_LIST_LOCK(ifp, flags); + + list_add_tail(&sta->list, &ifp->sta_list); + + BRCMF_IF_STA_LIST_UNLOCK(ifp, flags); + return sta; +} + +/** Delete STA from the interface's STA list. */ +void +brcmf_del_sta(struct brcmf_if *ifp, const u8 *ea) +{ + struct brcmf_sta *sta, *next; + unsigned long flags; + + BRCMF_IF_STA_LIST_LOCK(ifp, flags); + list_for_each_entry_safe(sta, next, &ifp->sta_list, list) { + if (!memcmp(sta->ea.octet, ea, ETH_ALEN)) { + brcmf_dbg(INFO, "del STA: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x from sta list\n", + ea[0], ea[1], ea[2], ea[3], + ea[4], ea[5]); + list_del(&sta->list); + kfree(sta); + } + } + + BRCMF_IF_STA_LIST_UNLOCK(ifp, flags); +} + +/** Add STA if it doesn't exist. Not reentrant. */ +struct brcmf_sta* +brcmf_findadd_sta(struct brcmf_if *ifp, const u8 *ea) +{ + struct brcmf_sta *sta = NULL; + + sta = brcmf_find_sta(ifp, ea); + + if (!sta) { + /* Add entry */ + sta = brcmf_add_sta(ifp, ea); + } + return sta; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h index 340346c122d32..4455527729173 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h @@ -12,6 +12,7 @@ #include #include "fweh.h" +#include "fwil_types.h" #define TOE_TX_CSUM_OL 0x00000001 #define TOE_RX_CSUM_OL 0x00000002 @@ -123,6 +124,7 @@ struct brcmf_pub { u32 feat_flags; u32 chip_quirks; + int req_mpc; struct brcmf_rev_info revinfo; #ifdef DEBUG @@ -136,6 +138,11 @@ struct brcmf_pub { struct work_struct bus_reset; u8 clmver[BRCMF_DCMD_SMLEN]; + struct brcmf_pkt_filter_enable_le pkt_filter[MAX_PKT_FILTER_COUNT]; + u8 sta_mac_idx; + u16 cnt_ver; + + struct cfg80211_qos_map *qos_map; }; /* forward declarations */ @@ -185,6 +192,7 @@ struct brcmf_if { struct brcmf_fws_mac_descriptor *fws_desc; int ifidx; s32 bsscfgidx; + bool isap; u8 mac_addr[ETH_ALEN]; u8 netif_stop; spinlock_t netif_stop_lock; @@ -193,6 +201,22 @@ struct brcmf_if { struct in6_addr ipv6_addr_tbl[NDOL_MAX_ENTRIES]; u8 ipv6addr_idx; bool fwil_fwerr; + struct list_head sta_list; /* dll of associated stations */ + spinlock_t sta_list_lock; + struct list_head twt_sess_list; /* dll of TWT sessions */ + spinlock_t twt_sess_list_lock; + bool fmac_pkt_fwd_en; +}; + +struct ether_addr { + u8 octet[ETH_ALEN]; +}; + +/** Per STA params. A list of dhd_sta objects are managed in dhd_if */ +struct brcmf_sta { + void *ifp; /* associated brcm_if */ + struct ether_addr ea; /* stations ethernet mac address */ + struct list_head list; /* link into brcmf_if::sta_list */ }; int brcmf_netdev_wait_pend8021x(struct brcmf_if *ifp); @@ -215,5 +239,10 @@ int brcmf_net_mon_attach(struct brcmf_if *ifp); void brcmf_net_setcarrier(struct brcmf_if *ifp, bool on); int __init brcmf_core_init(void); void __exit brcmf_core_exit(void); - +int brcmf_pktfilter_add_remove(struct net_device *ndev, int filter_num, + bool add); +int brcmf_pktfilter_enable(struct net_device *ndev, bool enable); +void brcmf_del_sta(struct brcmf_if *ifp, const u8 *ea); +struct brcmf_sta *brcmf_find_sta(struct brcmf_if *ifp, const u8 *ea); +struct brcmf_sta *brcmf_findadd_sta(struct brcmf_if *ifp, const u8 *ea); #endif /* BRCMFMAC_CORE_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.c index eecf8a38d94aa..f5da560bfc124 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.c @@ -14,6 +14,82 @@ #include "fweh.h" #include "debug.h" +static int +brcmf_debug_msgtrace_seqchk(u32 *prev, u32 cur) +{ + if ((cur == 0 && *prev == 0xFFFFFFFF) || ((cur - *prev) == 1)) { + goto done; + } else if (cur == *prev) { + brcmf_dbg(FWCON, "duplicate trace\n"); + return -1; + } else if (cur > *prev) { + brcmf_dbg(FWCON, "lost %d packets\n", cur - *prev); + } else { + brcmf_dbg(FWCON, "seq out of order, host %d, dongle %d\n", + *prev, cur); + } +done: + *prev = cur; + return 0; +} + +static int +brcmf_debug_msg_parser(void *event_data) +{ + int err = 0; + struct msgtrace_hdr *hdr; + char *data, *s; + static u32 seqnum_prev; + + hdr = (struct msgtrace_hdr *)event_data; + data = (char *)event_data + MSGTRACE_HDRLEN; + + /* There are 2 bytes available at the end of data */ + data[ntohs(hdr->len)] = '\0'; + + if (ntohl(hdr->discarded_bytes) || ntohl(hdr->discarded_printf)) { + brcmf_dbg(FWCON, "Discarded_bytes %d discarded_printf %d\n", + ntohl(hdr->discarded_bytes), + ntohl(hdr->discarded_printf)); + } + + err = brcmf_debug_msgtrace_seqchk(&seqnum_prev, ntohl(hdr->seqnum)); + if (err) + return err; + + while (*data != '\0' && (s = strstr(data, "\n")) != NULL) { + *s = '\0'; + brcmf_dbg(FWCON, "CONSOLE: %s\n", data); + data = s + 1; + } + if (*data) + brcmf_dbg(FWCON, "CONSOLE: %s", data); + + return err; +} + +static int +brcmf_debug_trace_parser(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *event_data) +{ + int err = 0; + struct msgtrace_hdr *hdr; + + hdr = (struct msgtrace_hdr *)event_data; + if (hdr->version != MSGTRACE_VERSION) { + brcmf_dbg(FWCON, "trace version mismatch host %d dngl %d\n", + MSGTRACE_VERSION, hdr->version); + err = -EPROTO; + return err; + } + + if (hdr->trace_type == MSGTRACE_HDR_TYPE_MSG) + err = brcmf_debug_msg_parser(event_data); + + return err; +} + int brcmf_debug_create_memdump(struct brcmf_bus *bus, const void *data, size_t len) { @@ -42,6 +118,13 @@ int brcmf_debug_create_memdump(struct brcmf_bus *bus, const void *data, return 0; } + +int brcmf_debug_fwlog_init(struct brcmf_pub *drvr) +{ + return brcmf_fweh_register(drvr, BRCMF_E_TRACE, + brcmf_debug_trace_parser); +} + struct dentry *brcmf_debugfs_get_devdir(struct brcmf_pub *drvr) { return drvr->wiphy->debugfsdir; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h index 9bb5f709d41a2..3e8f11b5db06e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h @@ -29,6 +29,8 @@ #define BRCMF_MSGBUF_VAL 0x00040000 #define BRCMF_PCIE_VAL 0x00080000 #define BRCMF_FWCON_VAL 0x00100000 +#define BRCMF_ULP_VAL 0x00200000 +#define BRCMF_TWT_VAL 0x00400000 /* set default print format */ #undef pr_fmt @@ -107,6 +109,10 @@ do { \ #endif /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */ +#define MSGTRACE_VERSION 1 +#define MSGTRACE_HDR_TYPE_MSG 0 +#define MSGTRACE_HDR_TYPE_LOG 1 + #define brcmf_dbg_hex_dump(test, data, len, fmt, ...) \ do { \ trace_brcmf_hexdump((void *)data, len); \ @@ -123,6 +129,7 @@ void brcmf_debugfs_add_entry(struct brcmf_pub *drvr, const char *fn, int (*read_fn)(struct seq_file *seq, void *data)); int brcmf_debug_create_memdump(struct brcmf_bus *bus, const void *data, size_t len); +int brcmf_debug_fwlog_init(struct brcmf_pub *drvr); #else static inline struct dentry *brcmf_debugfs_get_devdir(struct brcmf_pub *drvr) { @@ -138,6 +145,25 @@ int brcmf_debug_create_memdump(struct brcmf_bus *bus, const void *data, { return 0; } + +static inline +int brcmf_debug_fwlog_init(struct brcmf_pub *drvr) +{ + return 0; +} #endif +/* Message trace header */ +struct msgtrace_hdr { + u8 version; + u8 trace_type; + u16 len; /* Len of the trace */ + u32 seqnum; /* Sequence number of message */ + /* Number of discarded bytes because of trace overflow */ + u32 discarded_bytes; + /* Number of discarded printf because of trace overflow */ + u32 discarded_printf; +}; + +#define MSGTRACE_HDRLEN sizeof(struct msgtrace_hdr) #endif /* BRCMFMAC_DEBUG_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 2c2f3e026c136..093fcca810ded 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -16,7 +16,6 @@ #include "feature.h" #include "common.h" -#define BRCMF_FW_UNSUPPORTED 23 /* * expand feature list to array of feature strings. @@ -41,8 +40,12 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = { { BRCMF_FEAT_MONITOR_FLAG, "rtap" }, { BRCMF_FEAT_MONITOR_FMT_RADIOTAP, "rtap" }, { BRCMF_FEAT_DOT11H, "802.11h" }, - { BRCMF_FEAT_SAE, "sae" }, + { BRCMF_FEAT_SAE, "sae " }, { BRCMF_FEAT_FWAUTH, "idauth" }, + { BRCMF_FEAT_SAE_EXT, "sae_ext " }, + { BRCMF_FEAT_FBT, "fbt " }, + { BRCMF_FEAT_OKC, "okc" }, + { BRCMF_FEAT_GCMP, "gcmp" }, }; #ifdef DEBUG @@ -143,7 +146,7 @@ static void brcmf_feat_iovar_int_get(struct brcmf_if *ifp, ifp->fwil_fwerr = true; err = brcmf_fil_iovar_int_get(ifp, name, &data); - if (err == 0) { + if (err != -BRCMF_FW_UNSUPPORTED) { brcmf_dbg(INFO, "enabling feature: %s\n", brcmf_feat_names[id]); ifp->drvr->feat_flags |= BIT(id); } else { @@ -281,6 +284,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_RSDB, "rsdb_mode"); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_TDLS, "tdls_enable"); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_MFP, "mfp"); + brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_DUMP_OBSS, "dump_obss"); pfn_mac.version = BRCMF_PFN_MACADDR_CFG_VER; err = brcmf_fil_iovar_data_get(ifp, "pfn_macaddr", &pfn_mac, @@ -289,6 +293,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr) ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_RANDOM_MAC); brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa"); + brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_TWT, "twt"); if (drvr->settings->feature_disable) { brcmf_dbg(INFO, "Features: 0x%02x, disable: 0x%02x\n", @@ -329,3 +334,16 @@ bool brcmf_feat_is_quirk_enabled(struct brcmf_if *ifp, { return (ifp->drvr->chip_quirks & BIT(quirk)); } + +bool brcmf_feat_is_6ghz_enabled(struct brcmf_if *ifp) +{ + return (!ifp->drvr->settings->disable_6ghz); +} + +bool brcmf_feat_is_sdio_rxf_in_kthread(struct brcmf_pub *drvr) +{ + if (drvr) + return drvr->settings->sdio_rxf_in_kthread_enabled; + else + return false; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index d1f4257af696b..3c802ad36a98c 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -29,6 +29,10 @@ * DOT11H: firmware supports 802.11h * SAE: simultaneous authentication of equals * FWAUTH: Firmware authenticator + * DUMP_OBSS: Firmware has capable to dump obss info to support ACS + * SAE_EXT: SAE be handled by userspace supplicant + * GCMP: firmware has defined GCMP or not. + * TWT: Firmware has the TWT Module Support. */ #define BRCMF_FEAT_LIST \ BRCMF_FEAT_DEF(MBSS) \ @@ -51,7 +55,13 @@ BRCMF_FEAT_DEF(MONITOR_FMT_HW_RX_HDR) \ BRCMF_FEAT_DEF(DOT11H) \ BRCMF_FEAT_DEF(SAE) \ - BRCMF_FEAT_DEF(FWAUTH) + BRCMF_FEAT_DEF(FWAUTH) \ + BRCMF_FEAT_DEF(DUMP_OBSS) \ + BRCMF_FEAT_DEF(SAE_EXT) \ + BRCMF_FEAT_DEF(FBT) \ + BRCMF_FEAT_DEF(OKC) \ + BRCMF_FEAT_DEF(GCMP) \ + BRCMF_FEAT_DEF(TWT) /* * Quirks: @@ -120,4 +130,20 @@ bool brcmf_feat_is_enabled(struct brcmf_if *ifp, enum brcmf_feat_id id); bool brcmf_feat_is_quirk_enabled(struct brcmf_if *ifp, enum brcmf_feat_quirk quirk); +/** + * brcmf_feat_is_6ghz_enabled() - Find if 6GHZ Operation is allowed + * + * @ifp: interface instance. + * + * Return: true if 6GHz operation is allowed; otherwise false. + */ +bool brcmf_feat_is_6ghz_enabled(struct brcmf_if *ifp); + +/** + * brcmf_feat_is_sdio_rxf_in_kthread() - handle SDIO Rx frame in kthread. + * + * @drvr: driver instance. + */ +bool brcmf_feat_is_sdio_rxf_in_kthread(struct brcmf_pub *drvr); + #endif /* _BRCMF_FEATURE_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c index 09d2f2dc2b46f..3dfddd6d55182 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c @@ -527,7 +527,8 @@ static void brcmf_fw_free_request(struct brcmf_fw_request *req) int i; for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) { - if (item->type == BRCMF_FW_TYPE_BINARY) + if (item->type == BRCMF_FW_TYPE_BINARY || + item->type == BRCMF_FW_TYPE_TRXSE) release_firmware(item->binary); else if (item->type == BRCMF_FW_TYPE_NVRAM) brcmf_fw_nvram_free(item->nv_data.data); @@ -599,6 +600,7 @@ static int brcmf_fw_complete_request(const struct firmware *fw, ret = brcmf_fw_request_nvram_done(fw, fwctx); break; case BRCMF_FW_TYPE_BINARY: + case BRCMF_FW_TYPE_TRXSE: if (fw) cur->binary = fw; else @@ -659,9 +661,12 @@ static int brcmf_fw_request_firmware(const struct firmware **fw, if (!alt_path) goto fallback; - ret = firmware_request_nowarn(fw, alt_path, fwctx->dev); + ret = request_firmware_direct(fw, alt_path, fwctx->dev); kfree(alt_path); - if (ret == 0) + if (ret) + brcmf_info("no board-specific nvram available (ret=%d), device will use %s\n", + ret, cur->path); + else return ret; } @@ -672,8 +677,19 @@ static int brcmf_fw_request_firmware(const struct firmware **fw, static void brcmf_fw_request_done(const struct firmware *fw, void *ctx) { struct brcmf_fw *fwctx = ctx; + struct brcmf_fw_item *cur = &fwctx->req->items[fwctx->curpos]; + char alt_path[BRCMF_FW_NAME_LEN]; int ret; + if (!fw && cur->type == BRCMF_FW_TYPE_TRXSE) { + strlcpy(alt_path, cur->path, BRCMF_FW_NAME_LEN); + /* strip 'se' from .trxse at the end */ + alt_path[strlen(alt_path) - 2] = 0; + ret = request_firmware(&fw, alt_path, fwctx->dev); + if (!ret) + cur->path = alt_path; + } + ret = brcmf_fw_complete_request(fw, fwctx); while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) { @@ -803,11 +819,6 @@ brcmf_fw_alloc_request(u32 chip, u32 chiprev, u32 i, j; char end = '\0'; - if (chiprev >= BITS_PER_TYPE(u32)) { - brcmf_err("Invalid chip revision %u\n", chiprev); - return NULL; - } - for (i = 0; i < table_size; i++) { if (mapping_table[i].chipid == chip && mapping_table[i].revmask & BIT(chiprev)) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h index 1266cbaee0729..38acf1fb0bbd0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.h @@ -12,6 +12,7 @@ #define BRCMF_FW_DEFAULT_PATH "brcm/" #define BRCMF_FW_MAX_BOARD_TYPES 8 +#define CY_FW_DEFAULT_PATH "cypress/" /** * struct brcmf_firmware_mapping - Used to map chipid/revmask to firmware @@ -41,6 +42,16 @@ static const char BRCM_ ## fw_name ## _FIRMWARE_BASENAME[] = \ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH fw_base ".bin"); \ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH fw_base ".clm_blob") +#define CY_FW_DEF(fw_name, fw_base) \ +static const char BRCM_ ## fw_name ## _FIRMWARE_BASENAME[] = \ + CY_FW_DEFAULT_PATH fw_base; \ +MODULE_FIRMWARE(CY_FW_DEFAULT_PATH fw_base ".bin") + +#define CY_FW_TRXSE_DEF(fw_name, fw_base) \ +static const char BRCM_ ## fw_name ## _FIRMWARE_BASENAME[] = \ + CY_FW_DEFAULT_PATH fw_base; \ +MODULE_FIRMWARE(CY_FW_DEFAULT_PATH fw_base ".trxse") + #define BRCMF_FW_ENTRY(chipid, mask, name) \ { chipid, mask, BRCM_ ## name ## _FIRMWARE_BASENAME } @@ -48,7 +59,8 @@ void brcmf_fw_nvram_free(void *nvram); enum brcmf_fw_type { BRCMF_FW_TYPE_BINARY, - BRCMF_FW_TYPE_NVRAM + BRCMF_FW_TYPE_NVRAM, + BRCMF_FW_TYPE_TRXSE }; struct brcmf_fw_item { diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c index dac7eb77799bd..cd0626a003339 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c @@ -359,26 +359,42 @@ int brcmf_fweh_activate_events(struct brcmf_if *ifp) { struct brcmf_pub *drvr = ifp->drvr; int i, err; - s8 eventmask[BRCMF_EVENTING_MASK_LEN]; + struct eventmsgs_ext *eventmask_msg; + u32 msglen; + + msglen = EVENTMSGS_EXT_STRUCT_SIZE + BRCMF_EVENTING_MASK_LEN; + eventmask_msg = kzalloc(msglen, GFP_KERNEL); + if (!eventmask_msg) + return -ENOMEM; - memset(eventmask, 0, sizeof(eventmask)); for (i = 0; i < BRCMF_E_LAST; i++) { if (ifp->drvr->fweh.evt_handler[i]) { brcmf_dbg(EVENT, "enable event %s\n", brcmf_fweh_event_name(i)); - setbit(eventmask, i); + setbit(eventmask_msg->mask, i); } } /* want to handle IF event as well */ brcmf_dbg(EVENT, "enable event IF\n"); - setbit(eventmask, BRCMF_E_IF); + setbit(eventmask_msg->mask, BRCMF_E_IF); + + eventmask_msg->ver = EVENTMSGS_VER; + eventmask_msg->command = EVENTMSGS_SET_MASK; + eventmask_msg->len = BRCMF_EVENTING_MASK_LEN; + + err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", eventmask_msg, + msglen); + if (!err) + goto end; - err = brcmf_fil_iovar_data_set(ifp, "event_msgs", - eventmask, BRCMF_EVENTING_MASK_LEN); + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", eventmask_msg->mask, + BRCMF_EVENTING_MASK_LEN); if (err) bphy_err(drvr, "Set event_msgs error (%d)\n", err); +end: + kfree(eventmask_msg); return err; } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h index 48414e8b93890..e32379dcadc9d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h @@ -90,7 +90,15 @@ struct brcmf_cfg80211_info; BRCMF_ENUM_DEF(FIFO_CREDIT_MAP, 74) \ BRCMF_ENUM_DEF(ACTION_FRAME_RX, 75) \ BRCMF_ENUM_DEF(TDLS_PEER_EVENT, 92) \ - BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) + BRCMF_ENUM_DEF(PHY_TEMP, 111) \ + BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) \ + BRCMF_ENUM_DEF(ULP, 146) \ + BRCMF_ENUM_DEF(TWT_SETUP, 157) \ + BRCMF_ENUM_DEF(EXT_AUTH_REQ, 187) \ + BRCMF_ENUM_DEF(EXT_AUTH_FRAME_RX, 188) \ + BRCMF_ENUM_DEF(MGMT_FRAME_TXSTATUS, 189) \ + BRCMF_ENUM_DEF(MGMT_FRAME_OFF_CHAN_COMPLETE, 190) \ + BRCMF_ENUM_DEF(TWT_TEARDOWN, 195) #define BRCMF_ENUM_DEF(id, val) \ BRCMF_E_##id = (val), @@ -102,7 +110,7 @@ enum brcmf_fweh_event_code { * minimum length check in device firmware so it is * hard-coded here. */ - BRCMF_E_LAST = 139 + BRCMF_E_LAST = 196 }; #undef BRCMF_ENUM_DEF @@ -283,6 +291,28 @@ struct brcmf_if_event { u8 role; }; +enum event_msgs_ext_command { + EVENTMSGS_NONE = 0, + EVENTMSGS_SET_BIT = 1, + EVENTMSGS_RESET_BIT = 2, + EVENTMSGS_SET_MASK = 3 +}; + +#define EVENTMSGS_VER 1 +#define EVENTMSGS_EXT_STRUCT_SIZE offsetof(struct eventmsgs_ext, mask[0]) + +/* len- for SET it would be mask size from the application to the firmware */ +/* for GET it would be actual firmware mask size */ +/* maxgetsize - is only used for GET. indicate max mask size that the */ +/* application can read from the firmware */ +struct eventmsgs_ext { + u8 ver; + u8 command; + u8 len; + u8 maxgetsize; + u8 mask[1]; +}; + typedef int (*brcmf_fweh_handler_t)(struct brcmf_if *ifp, const struct brcmf_event_msg *evtmsg, void *data); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil.h index bc693157c4b1c..c729ff69c48bf 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil.h @@ -71,6 +71,7 @@ #define BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON 201 #define BRCMF_C_SET_ASSOC_PREFER 205 #define BRCMF_C_GET_VALID_CHANNELS 217 +#define BRCMF_C_GET_FAKEFRAG 218 #define BRCMF_C_SET_FAKEFRAG 219 #define BRCMF_C_GET_KEY_PRIMARY 235 #define BRCMF_C_SET_KEY_PRIMARY 236 @@ -79,6 +80,9 @@ #define BRCMF_C_SET_VAR 263 #define BRCMF_C_SET_WSEC_PMK 268 +#define BRCMF_FW_BADARG 2 +#define BRCMF_FW_UNSUPPORTED 23 + s32 brcmf_fil_cmd_data_set(struct brcmf_if *ifp, u32 cmd, void *data, u32 len); s32 brcmf_fil_cmd_data_get(struct brcmf_if *ifp, u32 cmd, void *data, u32 len); s32 brcmf_fil_cmd_int_set(struct brcmf_if *ifp, u32 cmd, u32 data); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h index f518e025d6e46..ccf1cc497be7a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h @@ -59,6 +59,8 @@ #define BRCMF_SCANTYPE_PASSIVE 1 #define BRCMF_WSEC_MAX_PSK_LEN 32 +#define BRCMF_WSEC_PMK_LEN_SUITEB_192 48 +#define BRCMF_WSEC_MAX_PMK_LEN 64 /* SUITE-B-192's PMK is 48 bytes */ #define BRCMF_WSEC_PASSPHRASE BIT(0) #define BRCMF_WSEC_MAX_SAE_PASSWORD_LEN 128 @@ -138,6 +140,19 @@ #define BRCMF_WOWL_MAXPATTERNS 16 #define BRCMF_WOWL_MAXPATTERNSIZE 128 +enum { + BRCMF_UNICAST_FILTER_NUM = 0, + BRCMF_BROADCAST_FILTER_NUM, + BRCMF_MULTICAST4_FILTER_NUM, + BRCMF_MULTICAST6_FILTER_NUM, + BRCMF_MDNS_FILTER_NUM, + BRCMF_ARP_FILTER_NUM, + BRCMF_BROADCAST_ARP_FILTER_NUM, + MAX_PKT_FILTER_COUNT +}; + +#define MAX_PKTFILTER_PATTERN_SIZE 16 + #define BRCMF_COUNTRY_BUF_SZ 4 #define BRCMF_ANT_MAX 4 @@ -169,6 +184,11 @@ #define BRCMF_HE_CAP_MCS_MAP_NSS_MAX 8 +#define BRCMF_EXTAUTH_START 1 +#define BRCMF_EXTAUTH_ABORT 2 +#define BRCMF_EXTAUTH_FAIL 3 +#define BRCMF_EXTAUTH_SUCCESS 4 + /* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each * ioctl. It is relatively small because firmware has small maximum size input * playload restriction for ioctls. @@ -184,6 +204,10 @@ #define DL_TYPE_CLM 2 +#define MAX_RSSI_LEVELS 8 +#define WL_RSSI_EVENT_BRCM_VERSION 0 +#define WL_RSSI_EVENT_IFX_VERSION 1 + /* join preference types for join_pref iovar */ enum brcmf_join_pref_types { BRCMF_JOIN_PREF_RSSI = 1, @@ -517,7 +541,7 @@ struct brcmf_wsec_key_le { struct brcmf_wsec_pmk_le { __le16 key_len; __le16 flags; - u8 key[2 * BRCMF_WSEC_MAX_PSK_LEN + 1]; + u8 key[2 * BRCMF_WSEC_MAX_PMK_LEN + 1]; }; /** @@ -531,6 +555,47 @@ struct brcmf_wsec_sae_pwd_le { u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN]; }; +/** + * struct brcmf_auth_req_status_le - external auth request and status update + * + * @flags: flags for external auth status + * @peer_mac: peer MAC address + * @ssid_len: length of ssid + * @ssid: ssid characters + */ +struct brcmf_auth_req_status_le { + __le16 flags; + u8 peer_mac[ETH_ALEN]; + __le32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 pmkid[WLAN_PMKID_LEN]; +}; + +/** + * struct brcmf_mf_params_le - management frame parameters for mgmt_frame iovar + * + * @version: version of the iovar + * @dwell_time: dwell duration in ms + * @len: length of frame data + * @frame_control: frame control + * @channel: channel + * @da: peer MAC address + * @bssid: BSS network identifier + * @packet_id: packet identifier + * @data: frame data + */ +struct brcmf_mf_params_le { + __le32 version; + __le32 dwell_time; + __le16 len; + __le16 frame_control; + __le16 channel; + u8 da[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le32 packet_id; + u8 data[1]; +}; + /* Used to get specific STA parameters */ struct brcmf_scb_val_le { __le32 val; @@ -1071,4 +1136,20 @@ struct brcmf_mkeep_alive_pkt_le { u8 data[]; } __packed; +/* BRCM_E_RSSI event data */ +struct wl_event_data_rssi { + s32 rssi; + s32 snr; + s32 noise; +}; + +/** RSSI event notification configuration. */ +struct wl_rssi_event { + u32 rate_limit_msec; + u8 num_rssi_levels; + s8 rssi_levels[MAX_RSSI_LEVELS]; + u8 version; + s8 pad[2]; +}; + #endif /* FWIL_TYPES_H_ */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c index 36af81975855c..4bb3bbc9bb1b6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c @@ -621,7 +621,6 @@ static inline int brcmf_fws_hanger_poppkt(struct brcmf_fws_hanger *h, static void brcmf_fws_psq_flush(struct brcmf_fws_info *fws, struct pktq *q, int ifidx) { - struct brcmf_fws_hanger_item *hi; bool (*matchfn)(struct sk_buff *, void *) = NULL; struct sk_buff *skb; int prec; @@ -633,9 +632,6 @@ static void brcmf_fws_psq_flush(struct brcmf_fws_info *fws, struct pktq *q, skb = brcmu_pktq_pdeq_match(q, prec, matchfn, &ifidx); while (skb) { hslot = brcmf_skb_htod_tag_get_field(skb, HSLOT); - hi = &fws->hanger.items[hslot]; - WARN_ON(skb != hi->pkt); - hi->state = BRCMF_FWS_HANGER_ITEM_STATE_FREE; brcmf_fws_hanger_poppkt(&fws->hanger, hslot, &skb, true); brcmu_pkt_buf_free_skb(skb); @@ -1844,7 +1840,7 @@ void brcmf_fws_hdrpull(struct brcmf_if *ifp, s16 siglen, struct sk_buff *skb) u8 *signal_data; s16 data_len; u8 type; - u8 len; + s16 len; u8 *data; s32 status; s32 err; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c index 45fbcbdc7d9e4..85f08b2d728ff 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ #include "flowring.h" #include "bus.h" #include "tracepoint.h" +#include "pcie.h" #define MSGBUF_IOCTL_RESP_TIMEOUT msecs_to_jiffies(2000) @@ -47,6 +49,8 @@ #define MSGBUF_TYPE_RX_CMPLT 0x12 #define MSGBUF_TYPE_LPBK_DMAXFER 0x13 #define MSGBUF_TYPE_LPBK_DMAXFER_CMPLT 0x14 +#define MSGBUF_TYPE_H2D_MAILBOX_DATA 0x23 +#define MSGBUF_TYPE_D2H_MAILBOX_DATA 0x24 #define NR_TX_PKTIDS 2048 #define NR_RX_PKTIDS 1024 @@ -104,6 +108,12 @@ struct msgbuf_tx_msghdr { __le32 rsvd0; }; +struct msgbuf_h2d_mbdata { + struct msgbuf_common_hdr msg; + __le32 mbdata; + __le16 rsvd0[7]; +}; + struct msgbuf_rx_bufpost { struct msgbuf_common_hdr msg; __le16 metadata_buf_len; @@ -218,6 +228,13 @@ struct msgbuf_flowring_flush_resp { __le32 rsvd0[3]; }; +struct msgbuf_d2h_mailbox_data { + struct msgbuf_common_hdr msg; + struct msgbuf_completion_hdr compl_hdr; + __le32 mbdata; + __le32 rsvd0[2]; +} d2h_mailbox_data_t; + struct brcmf_msgbuf_work_item { struct list_head queue; u32 flowid; @@ -290,6 +307,8 @@ struct brcmf_msgbuf_pktids { }; static void brcmf_msgbuf_rxbuf_ioctlresp_post(struct brcmf_msgbuf *msgbuf); +static void brcmf_msgbuf_process_d2h_mbdata(struct brcmf_msgbuf *msgbuf, + void *buf); static struct brcmf_msgbuf_pktids * @@ -347,11 +366,8 @@ brcmf_msgbuf_alloc_pktid(struct device *dev, count++; } while (count < pktids->array_size); - if (count == pktids->array_size) { - dma_unmap_single(dev, *physaddr, skb->len - data_offset, - pktids->direction); + if (count == pktids->array_size) return -ENOMEM; - } array[*idx].data_offset = data_offset; array[*idx].physaddr = *physaddr; @@ -427,6 +443,34 @@ static void brcmf_msgbuf_release_pktids(struct brcmf_msgbuf *msgbuf) msgbuf->tx_pktids); } +int brcmf_msgbuf_tx_mbdata(struct brcmf_pub *drvr, u32 mbdata) +{ + struct brcmf_msgbuf *msgbuf = (struct brcmf_msgbuf *)drvr->proto->pd; + struct brcmf_commonring *commonring; + struct msgbuf_h2d_mbdata *h2d_mbdata; + void *ret_ptr; + int err; + + commonring = msgbuf->commonrings[BRCMF_H2D_MSGRING_CONTROL_SUBMIT]; + brcmf_commonring_lock(commonring); + ret_ptr = brcmf_commonring_reserve_for_write(commonring); + if (!ret_ptr) { + brcmf_err("Failed to reserve space in commonring\n"); + brcmf_commonring_unlock(commonring); + return -ENOMEM; + } + h2d_mbdata = (struct msgbuf_h2d_mbdata *)ret_ptr; + memset(h2d_mbdata, 0, sizeof(*h2d_mbdata)); + + h2d_mbdata->msg.msgtype = MSGBUF_TYPE_H2D_MAILBOX_DATA; + h2d_mbdata->mbdata = cpu_to_le32(mbdata); + + err = brcmf_commonring_write_complete(commonring); + brcmf_commonring_unlock(commonring); + + return err; +} + static int brcmf_msgbuf_tx_ioctl(struct brcmf_pub *drvr, int ifidx, uint cmd, void *buf, uint len) @@ -722,6 +766,7 @@ static void brcmf_msgbuf_txflow(struct brcmf_msgbuf *msgbuf, u16 flowid) brcmf_flowring_qlen(flow, flowid)); break; } + skb_tx_timestamp(skb); skb_orphan(skb); if (brcmf_msgbuf_alloc_pktid(msgbuf->drvr->bus_if->dev, msgbuf->tx_pktids, skb, ETH_HLEN, @@ -1038,7 +1083,6 @@ brcmf_msgbuf_rxbuf_ctrl_post(struct brcmf_msgbuf *msgbuf, bool event_buf, memset(rx_bufpost, 0, sizeof(*rx_bufpost)); skb = brcmu_pkt_buf_get_skb(BRCMF_MSGBUF_MAX_CTL_PKT_SIZE); - if (skb == NULL) { bphy_err(drvr, "Failed to alloc SKB\n"); brcmf_commonring_write_cancel(commonring, alloced - i); @@ -1148,7 +1192,8 @@ brcmf_msgbuf_process_rx_complete(struct brcmf_msgbuf *msgbuf, void *buf) { struct brcmf_pub *drvr = msgbuf->drvr; struct msgbuf_rx_complete *rx_complete; - struct sk_buff *skb; + struct sk_buff *skb, *cpskb = NULL; + struct ethhdr *eh; u16 data_offset; u16 buflen; u16 flags; @@ -1197,6 +1242,34 @@ brcmf_msgbuf_process_rx_complete(struct brcmf_msgbuf *msgbuf, void *buf) return; } + if (ifp->isap && ifp->fmac_pkt_fwd_en) { + eh = (struct ethhdr *)(skb->data); + skb_set_network_header(skb, sizeof(struct ethhdr)); + skb->protocol = eh->h_proto; + skb->priority = cfg80211_classify8021d(skb, NULL); + if (is_unicast_ether_addr(eh->h_dest)) { + if (brcmf_find_sta(ifp, eh->h_dest)) { + /* determine the priority */ + if (skb->priority == 0 || skb->priority > 7) { + skb->priority = + cfg80211_classify8021d(skb, + NULL); + } + brcmf_proto_tx_queue_data(ifp->drvr, + ifp->ifidx, skb); + return; + } + } else { + cpskb = pskb_copy(skb, GFP_ATOMIC); + if (cpskb) { + brcmf_proto_tx_queue_data(ifp->drvr, + ifp->ifidx, + cpskb); + } else { + brcmf_err("Unable to do skb copy\n"); + } + } + } skb->protocol = eth_type_trans(skb, ifp->ndev); brcmf_netif_rx(ifp, skb); } @@ -1284,6 +1357,21 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf, brcmf_msgbuf_remove_flowring(msgbuf, flowid); } +static void +brcmf_msgbuf_process_d2h_mbdata(struct brcmf_msgbuf *msgbuf, + void *buf) +{ + struct msgbuf_d2h_mailbox_data *d2h_mbdata; + + d2h_mbdata = (struct msgbuf_d2h_mailbox_data *)buf; + + if (!d2h_mbdata) { + brcmf_err("d2h_mbdata is null\n"); + return; + } + + brcmf_pcie_handle_mb_data(msgbuf->drvr->bus_if, d2h_mbdata->mbdata); +} static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf) { @@ -1327,6 +1415,11 @@ static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf) brcmf_dbg(MSGBUF, "MSGBUF_TYPE_RX_CMPLT\n"); brcmf_msgbuf_process_rx_complete(msgbuf, buf); break; + case MSGBUF_TYPE_D2H_MAILBOX_DATA: + brcmf_dbg(MSGBUF, "MSGBUF_TYPE_D2H_MAILBOX_DATA\n"); + brcmf_msgbuf_process_d2h_mbdata(msgbuf, buf); + break; + default: bphy_err(drvr, "Unsupported msgtype %d\n", msg->msgtype); break; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h index 6a849f4a94dd7..99359a1523451 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h @@ -39,5 +39,6 @@ static inline int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr) } static inline void brcmf_proto_msgbuf_detach(struct brcmf_pub *drvr) {} #endif +int brcmf_msgbuf_tx_mbdata(struct brcmf_pub *drvr, u32 mbdata); #endif /* BRCMFMAC_MSGBUF_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c index fdd0c9abc1a10..ac8b27b2cbe25 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/of.c @@ -74,13 +74,13 @@ void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type, int irq; int err; u32 irqf; - u32 val; + u32 val32; + u16 val16; /* Apple ARM64 platforms have their own idea of board type, passed in * via the device tree. They also have an antenna SKU parameter */ - err = of_property_read_string(np, "brcm,board-type", &prop); - if (!err) + if (!of_property_read_string(np, "brcm,board-type", &prop)) settings->board_type = prop; if (!of_property_read_string(np, "apple,antenna-sku", &prop)) @@ -88,7 +88,7 @@ void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type, /* Set board-type to the first string of the machine compatible prop */ root = of_find_node_by_path("/"); - if (root && err) { + if (root && !settings->board_type) { char *board_type; const char *tmp; @@ -118,8 +118,15 @@ void brcmf_of_probe(struct device *dev, enum brcmf_bus_type bus_type, if (bus_type != BRCMF_BUSTYPE_SDIO) return; - if (of_property_read_u32(np, "brcm,drive-strength", &val) == 0) - sdio->drive_strength = val; + if (of_property_read_u32(np, "brcm,drive-strength", &val32) == 0) + sdio->drive_strength = val32; + + sdio->broken_sg_support = of_property_read_bool(np, + "brcm,broken_sg_support"); + if (of_property_read_u16(np, "brcm,sd_head_align", &val16) == 0) + sdio->sd_head_align = val16; + if (of_property_read_u16(np, "brcm,sd_sgentry_align", &val16) == 0) + sdio->sd_sgentry_align = val16; /* make sure there are interrupts defined in the node */ if (!of_find_property(np, "interrupts", NULL)) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index 10d9d9c63b281..9598940b18ec4 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -63,6 +63,7 @@ #define P2P_AF_TX_MAX_RETRY 5 #define P2P_AF_MAX_WAIT_TIME msecs_to_jiffies(2000) #define P2P_INVALID_CHANNEL -1 +#define P2P_INVALID_CHANSPEC 0 #define P2P_CHANNEL_SYNC_RETRY 5 #define P2P_AF_FRM_SCAN_MAX_WAIT msecs_to_jiffies(450) #define P2P_DEFAULT_SLEEP_TIME_VSDB 200 @@ -231,7 +232,35 @@ static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len) if (pact_frm->category == P2P_PUB_AF_CATEGORY && pact_frm->action == P2P_PUB_AF_ACTION && pact_frm->oui_type == P2P_VER && - memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) + memcmp(pact_frm->oui, WFA_OUI, P2P_OUI_LEN) == 0) + return true; + + return false; +} + +/** + * brcmf_p2p_is_dpp_pub_action() - true if dpp public type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is dpp public action type + */ +static bool brcmf_p2p_is_dpp_pub_action(void *frame, u32 frame_len) +{ + struct brcmf_p2p_pub_act_frame *pact_frm; + + if (!frame) + return false; + + pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2p_pub_act_frame) - 1) + return false; + + if (pact_frm->category == WLAN_CATEGORY_PUBLIC && + pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC && + pact_frm->oui_type == DPP_VER && + memcmp(pact_frm->oui, WFA_OUI, TLV_OUI_LEN) == 0) return true; return false; @@ -894,7 +923,8 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; - int err; + int err = 0; + struct brcmu_chan ch; if (brcmf_p2p_scan_is_p2p_request(request)) { /* find my listen channel */ @@ -903,7 +933,12 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, if (err < 0) return err; - p2p->afx_hdl.my_listen_chan = err; + ch.band = BRCMU_CHAN_BAND_2G; + ch.bw = BRCMU_CHAN_BW_20; + ch.sb = BRCMU_CHAN_SB_NONE; + ch.chnum = err; + p2p->cfg->d11inf.encchspec(&ch); + p2p->afx_hdl.my_listen_chan = ch.chspec; clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); brcmf_dbg(INFO, "P2P: GO_NEG_PHASE status cleared\n"); @@ -912,10 +947,14 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, if (err) return err; + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + /* override .run_escan() callback. */ cfg->escan_info.run = brcmf_p2p_run_escan; } - return 0; + err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBREQ_FLAG, + request->ie, request->ie_len); + return err; } @@ -923,16 +962,15 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, * brcmf_p2p_discover_listen() - set firmware to discover listen state. * * @p2p: p2p device. - * @channel: channel nr for discover listen. + * @chspec: chspec for discover listen. * @duration: time in ms to stay on channel. * */ static s32 -brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) +brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 chspec, u32 duration) { struct brcmf_pub *drvr = p2p->cfg->pub; struct brcmf_cfg80211_vif *vif; - struct brcmu_chan ch; s32 err = 0; vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; @@ -948,11 +986,8 @@ brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) goto exit; } - ch.chnum = channel; - ch.bw = BRCMU_CHAN_BW_20; - p2p->cfg->d11inf.encchspec(&ch); err = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_LISTEN, - ch.chspec, (u16)duration); + chspec, (u16)duration); if (!err) { set_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status); p2p->remain_on_channel_cookie++; @@ -978,19 +1013,17 @@ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; s32 err; - u16 channel_nr; - - channel_nr = ieee80211_frequency_to_channel(channel->center_freq); - brcmf_dbg(TRACE, "Enter, channel: %d, duration ms (%d)\n", channel_nr, - duration); err = brcmf_p2p_enable_discovery(p2p); if (err) goto exit; - err = brcmf_p2p_discover_listen(p2p, channel_nr, duration); + err = brcmf_p2p_discover_listen(p2p, + channel_to_chanspec(&cfg->d11inf, channel), duration); if (err) goto exit; + p2p->remin_on_channel_wdev = wdev; + memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); *cookie = p2p->remain_on_channel_cookie; cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); @@ -1014,6 +1047,7 @@ int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, { struct brcmf_cfg80211_info *cfg = ifp->drvr->config; struct brcmf_p2p_info *p2p = &cfg->p2p; + struct wireless_dev *wdev = p2p->remin_on_channel_wdev; brcmf_dbg(TRACE, "Enter\n"); if (test_and_clear_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, @@ -1026,10 +1060,16 @@ int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, complete(&p2p->wait_next_af); } - cfg80211_remain_on_channel_expired(&ifp->vif->wdev, + wdev = p2p->remin_on_channel_wdev ? + p2p->remin_on_channel_wdev : + &ifp->vif->wdev; + + cfg80211_remain_on_channel_expired(wdev, p2p->remain_on_channel_cookie, &p2p->remain_on_channel, GFP_KERNEL); + p2p->remin_on_channel_wdev = NULL; + } return 0; } @@ -1054,12 +1094,12 @@ void brcmf_p2p_cancel_remain_on_channel(struct brcmf_if *ifp) * brcmf_p2p_act_frm_search() - search function for action frame. * * @p2p: p2p device. - * @channel: channel on which action frame is to be trasmitted. + * @chspec: chspec on which action frame is to be trasmitted. * * search function to reach at common channel to send action frame. When * channel is 0 then all social channels will be used to send af */ -static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 channel) +static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 chspec) { struct brcmf_pub *drvr = p2p->cfg->pub; s32 err; @@ -1070,7 +1110,7 @@ static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 channel) brcmf_dbg(TRACE, "Enter\n"); - if (channel) + if (chspec) channel_cnt = AF_PEER_SEARCH_CNT; else channel_cnt = SOCIAL_CHAN_CNT; @@ -1081,14 +1121,13 @@ static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 channel) err = -ENOMEM; goto exit; } - ch.bw = BRCMU_CHAN_BW_20; - if (channel) { - ch.chnum = channel; - p2p->cfg->d11inf.encchspec(&ch); - /* insert same channel to the chan_list */ + + if (chspec) { for (i = 0; i < channel_cnt; i++) - default_chan_list[i] = ch.chspec; + default_chan_list[i] = chspec; } else { + ch.band = BRCMU_CHAN_BAND_2G; + ch.bw = BRCMU_CHAN_BW_20; ch.chnum = SOCIAL_CHAN_1; p2p->cfg->d11inf.encchspec(&ch); default_chan_list[0] = ch.chspec; @@ -1147,7 +1186,7 @@ static void brcmf_p2p_afx_handler(struct work_struct *work) * @p2p: p2p device info struct. * */ -static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) +static u16 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) { struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmf_cfg80211_vif *pri_vif; @@ -1160,14 +1199,14 @@ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) reinit_completion(&afx_hdl->act_frm_scan); set_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status); afx_hdl->is_active = true; - afx_hdl->peer_chan = P2P_INVALID_CHANNEL; + afx_hdl->peer_chan = P2P_INVALID_CHANSPEC; /* Loop to wait until we find a peer's channel or the * pending action frame tx is cancelled. */ retry = 0; while ((retry < P2P_CHANNEL_SYNC_RETRY) && - (afx_hdl->peer_chan == P2P_INVALID_CHANNEL)) { + (afx_hdl->peer_chan == P2P_INVALID_CHANSPEC)) { afx_hdl->is_listen = false; brcmf_dbg(TRACE, "Scheduling action frame for sending.. (%d)\n", retry); @@ -1175,13 +1214,13 @@ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) schedule_work(&afx_hdl->afx_work); wait_for_completion_timeout(&afx_hdl->act_frm_scan, P2P_AF_FRM_SCAN_MAX_WAIT); - if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || + if ((afx_hdl->peer_chan != P2P_INVALID_CHANSPEC) || (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status))) break; if (afx_hdl->my_listen_chan) { - brcmf_dbg(TRACE, "Scheduling listen peer, channel=%d\n", + brcmf_dbg(TRACE, "Scheduling listen peer, chanspec=0x%04x\n", afx_hdl->my_listen_chan); /* listen on my listen channel */ afx_hdl->is_listen = true; @@ -1189,7 +1228,7 @@ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) wait_for_completion_timeout(&afx_hdl->act_frm_scan, P2P_AF_FRM_SCAN_MAX_WAIT); } - if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || + if ((afx_hdl->peer_chan != P2P_INVALID_CHANSPEC) || (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status))) break; @@ -1203,7 +1242,7 @@ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) msleep(P2P_DEFAULT_SLEEP_TIME_VSDB); } - brcmf_dbg(TRACE, "Completed search/listen peer_chan=%d\n", + brcmf_dbg(TRACE, "Completed search/listen peer_chan=0x%4x\n", afx_hdl->peer_chan); afx_hdl->is_active = false; @@ -1226,7 +1265,6 @@ bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, { struct brcmf_p2p_info *p2p = &cfg->p2p; struct afx_hdl *afx_hdl = &p2p->afx_hdl; - struct brcmu_chan ch; u8 *ie; s32 err; u8 p2p_dev_addr[ETH_ALEN]; @@ -1236,7 +1274,7 @@ bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, if (bi == NULL) { brcmf_dbg(TRACE, "ACTION FRAME SCAN Done\n"); - if (afx_hdl->peer_chan == P2P_INVALID_CHANNEL) + if (afx_hdl->peer_chan == P2P_INVALID_CHANSPEC) complete(&afx_hdl->act_frm_scan); return true; } @@ -1252,13 +1290,8 @@ bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, p2p_dev_addr, sizeof(p2p_dev_addr)); if ((err >= 0) && (ether_addr_equal(p2p_dev_addr, afx_hdl->tx_dst_addr))) { - if (!bi->ctl_ch) { - ch.chspec = le16_to_cpu(bi->chanspec); - cfg->d11inf.decchspec(&ch); - bi->ctl_ch = ch.control_ch_num; - } - afx_hdl->peer_chan = bi->ctl_ch; - brcmf_dbg(TRACE, "ACTION FRAME SCAN : Peer %pM found, channel : %d\n", + afx_hdl->peer_chan = le16_to_cpu(bi->chanspec); + brcmf_dbg(TRACE, "ACTION FRAME SCAN : Peer %pM found, chanspec : 0x%04x\n", afx_hdl->tx_dst_addr, afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } @@ -1281,6 +1314,10 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg) brcmf_dbg(TRACE, "Enter\n"); vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + + if (!vif) + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val, sizeof(s32)); if (err) @@ -1426,8 +1463,8 @@ int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status) && (ether_addr_equal(afx_hdl->tx_dst_addr, e->addr))) { - afx_hdl->peer_chan = ch.control_ch_num; - brcmf_dbg(INFO, "GON request: Peer found, channel=%d\n", + afx_hdl->peer_chan = be16_to_cpu(rxframe->chanspec); + brcmf_dbg(INFO, "GON request: Peer found, chanspec=0x%04x\n", afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } @@ -1470,9 +1507,7 @@ int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, mgmt_frame_len += offsetof(struct ieee80211_mgmt, u); freq = ieee80211_channel_to_frequency(ch.control_ch_num, - ch.band == BRCMU_CHAN_BAND_2G ? - NL80211_BAND_2GHZ : - NL80211_BAND_5GHZ); + BRCMU_CHAN_BAND_TO_NL80211(ch.band)); wdev = &ifp->vif->wdev; cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len, 0); @@ -1531,6 +1566,7 @@ int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, * * @p2p: p2p info struct for vif. * @af_params: action frame data/info. + * @vif: vif to send * * Send an action frame immediately without doing channel synchronization. * @@ -1539,12 +1575,17 @@ int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, * frame is transmitted. */ static s32 brcmf_p2p_tx_action_frame(struct brcmf_p2p_info *p2p, - struct brcmf_fil_af_params_le *af_params) + struct brcmf_fil_af_params_le *af_params, + struct brcmf_cfg80211_vif *vif + ) { struct brcmf_pub *drvr = p2p->cfg->pub; - struct brcmf_cfg80211_vif *vif; - struct brcmf_p2p_action_frame *p2p_af; s32 err = 0; + struct brcmf_fil_action_frame_le *action_frame; + u16 action_frame_len; + + action_frame = &af_params->action_frame; + action_frame_len = le16_to_cpu(action_frame->len); brcmf_dbg(TRACE, "Enter\n"); @@ -1552,13 +1593,6 @@ static s32 brcmf_p2p_tx_action_frame(struct brcmf_p2p_info *p2p, clear_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); clear_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); - /* check if it is a p2p_presence response */ - p2p_af = (struct brcmf_p2p_action_frame *)af_params->action_frame.data; - if (p2p_af->subtype == P2P_AF_PRESENCE_RSP) - vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; - else - vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; - err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe", af_params, sizeof(*af_params)); if (err) { @@ -1714,10 +1748,14 @@ static bool brcmf_p2p_check_dwell_overflow(u32 requested_dwell, * @cfg: driver private data for cfg80211 interface. * @ndev: net device to transmit on. * @af_params: configuration data for action frame. + * @vif: virtual interface to send */ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, - struct brcmf_fil_af_params_le *af_params) + struct brcmf_fil_af_params_le *af_params, + struct brcmf_cfg80211_vif *vif, + struct ieee80211_channel *peer_listen_chan + ) { struct brcmf_p2p_info *p2p = &cfg->p2p; struct brcmf_if *ifp = netdev_priv(ndev); @@ -1725,6 +1763,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, struct brcmf_config_af_params config_af_params; struct afx_hdl *afx_hdl = &p2p->afx_hdl; struct brcmf_pub *drvr = cfg->pub; + struct brcmu_chan ch; u16 action_frame_len; bool ack = false; u8 category; @@ -1789,7 +1828,9 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, goto exit; } } else if (brcmf_p2p_is_p2p_action(action_frame->data, - action_frame_len)) { + action_frame_len) || + brcmf_p2p_is_dpp_pub_action(action_frame->data, + action_frame_len)) { /* do not configure anything. it will be */ /* sent with a default configuration */ } else { @@ -1826,12 +1867,13 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, /* validate channel and p2p ies */ if (config_af_params.search_channel && IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif && p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { afx_hdl = &p2p->afx_hdl; - afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); + afx_hdl->peer_listen_chan = channel_to_chanspec(&cfg->d11inf, peer_listen_chan); if (brcmf_p2p_af_searching_channel(p2p) == - P2P_INVALID_CHANNEL) { + P2P_INVALID_CHANSPEC) { bphy_err(drvr, "Couldn't find peer's channel.\n"); goto exit; } @@ -1844,7 +1886,9 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, brcmf_notify_escan_complete(cfg, ifp, true, true); /* update channel */ - af_params->channel = cpu_to_le32(afx_hdl->peer_chan); + ch.chspec = afx_hdl->peer_chan; + cfg->d11inf.decchspec(&ch); + af_params->channel = cpu_to_le32(ch.control_ch_num); } dwell_jiffies = jiffies; dwell_overflow = brcmf_p2p_check_dwell_overflow(requested_dwell, @@ -1857,7 +1901,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, if (af_params->channel) msleep(P2P_AF_RETRY_DELAY_TIME); - ack = !brcmf_p2p_tx_action_frame(p2p, af_params); + ack = !brcmf_p2p_tx_action_frame(p2p, af_params, vif); tx_retry++; dwell_overflow = brcmf_p2p_check_dwell_overflow(requested_dwell, dwell_jiffies); @@ -1876,9 +1920,11 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, * not keep the dwell time, go to listen state again to get next action * response frame. */ + ch.chspec = afx_hdl->my_listen_chan; + cfg->d11inf.decchspec(&ch); if (ack && config_af_params.extra_listen && !p2p->block_gon_req_tx && test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) && - p2p->af_sent_channel == afx_hdl->my_listen_chan) { + p2p->af_sent_channel == ch.control_ch_num) { delta_ms = jiffies_to_msecs(jiffies - p2p->af_tx_sent_jiffies); if (le32_to_cpu(af_params->dwell_time) > delta_ms) extra_listen_time = le32_to_cpu(af_params->dwell_time) - @@ -1893,7 +1939,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, extra_listen_time); extra_listen_time += 100; if (!brcmf_p2p_discover_listen(p2p, - p2p->af_sent_channel, + afx_hdl->my_listen_chan, extra_listen_time)) { unsigned long duration; @@ -1958,8 +2004,8 @@ s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status) && (ether_addr_equal(afx_hdl->tx_dst_addr, e->addr))) { - afx_hdl->peer_chan = ch.control_ch_num; - brcmf_dbg(INFO, "PROBE REQUEST: Peer found, channel=%d\n", + afx_hdl->peer_chan = be16_to_cpu(rxframe->chanspec); + brcmf_dbg(INFO, "PROBE REQUEST: Peer found, chanspec=0x%04x\n", afx_hdl->peer_chan); complete(&afx_hdl->act_frm_scan); } @@ -1984,9 +2030,7 @@ s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, mgmt_frame = (u8 *)(rxframe + 1); mgmt_frame_len = e->datalen - sizeof(*rxframe); freq = ieee80211_channel_to_frequency(ch.control_ch_num, - ch.band == BRCMU_CHAN_BAND_2G ? - NL80211_BAND_2GHZ : - NL80211_BAND_5GHZ); + BRCMU_CHAN_BAND_TO_NL80211(ch.band)); cfg80211_rx_mgmt(&vif->wdev, freq, 0, mgmt_frame, mgmt_frame_len, 0); @@ -2030,6 +2074,7 @@ static void brcmf_p2p_get_current_chanspec(struct brcmf_p2p_info *p2p, } } /* Use default channel for P2P */ + ch.band = BRCMU_CHAN_BAND_2G; ch.chnum = BRCMF_P2P_TEMP_CHAN; ch.bw = BRCMU_CHAN_BW_20; p2p->cfg->d11inf.encchspec(&ch); @@ -2424,8 +2469,12 @@ int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev) brcmf_remove_interface(vif->ifp, true); brcmf_cfg80211_arm_vif_event(cfg, NULL); - if (iftype != NL80211_IFTYPE_P2P_DEVICE) - p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif = NULL; + if (iftype != NL80211_IFTYPE_P2P_DEVICE) { + if (vif == p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif) + p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif = NULL; + if (vif == p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION2].vif) + p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION2].vif = NULL; + } return err; } @@ -2508,6 +2557,7 @@ s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg, bool p2pdev_forced) pri_ifp = brcmf_get_ifp(cfg->pub, 0); p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif = pri_ifp->vif; + init_completion(&p2p->send_af_done); if (p2pdev_forced) { err_ptr = brcmf_p2p_create_p2pdev(p2p, NULL, NULL); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h index d2ecee565bf2e..d71709aae7abc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h @@ -87,7 +87,7 @@ struct afx_hdl { struct work_struct afx_work; struct completion act_frm_scan; bool is_active; - s32 peer_chan; + u16 peer_chan; bool is_listen; u16 my_listen_chan; u16 peer_listen_chan; @@ -138,6 +138,7 @@ struct brcmf_p2p_info { bool block_gon_req_tx; bool p2pdev_dynamically; bool wait_for_offchan_complete; + struct wireless_dev *remin_on_channel_wdev; }; s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg, bool p2pdev_forced); @@ -170,7 +171,9 @@ int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, void *data); bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, - struct brcmf_fil_af_params_le *af_params); + struct brcmf_fil_af_params_le *af_params, + struct brcmf_cfg80211_vif *vif, + struct ieee80211_channel *peer_listen_chan); bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bi); s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c index 3b1277a8bd617..78dc6a50fb875 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c @@ -13,7 +13,8 @@ #include #include #include -#include +#include +#include #include #include @@ -40,6 +41,8 @@ #include "chip.h" #include "core.h" #include "common.h" +#include "cfg80211.h" +#include "trxhdr.h" enum brcmf_pcie_state { @@ -50,10 +53,10 @@ enum brcmf_pcie_state { BRCMF_FW_DEF(43602, "brcmfmac43602-pcie"); BRCMF_FW_DEF(4350, "brcmfmac4350-pcie"); BRCMF_FW_DEF(4350C, "brcmfmac4350c2-pcie"); -BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-pcie"); -BRCMF_FW_CLM_DEF(43570, "brcmfmac43570-pcie"); +CY_FW_DEF(4356, "cyfmac4356-pcie"); +CY_FW_DEF(43570, "cyfmac43570-pcie"); BRCMF_FW_DEF(4358, "brcmfmac4358-pcie"); -BRCMF_FW_DEF(4359, "brcmfmac4359-pcie"); +CY_FW_DEF(4359, "cyfmac4359-pcie"); BRCMF_FW_DEF(4364, "brcmfmac4364-pcie"); BRCMF_FW_DEF(4365B, "brcmfmac4365b-pcie"); BRCMF_FW_DEF(4365C, "brcmfmac4365c-pcie"); @@ -61,7 +64,9 @@ BRCMF_FW_DEF(4366B, "brcmfmac4366b-pcie"); BRCMF_FW_DEF(4366C, "brcmfmac4366c-pcie"); BRCMF_FW_DEF(4371, "brcmfmac4371-pcie"); BRCMF_FW_CLM_DEF(4378B1, "brcmfmac4378b1-pcie"); -BRCMF_FW_DEF(4355, "brcmfmac89459-pcie"); +CY_FW_DEF(4355, "cyfmac54591-pcie"); +CY_FW_TRXSE_DEF(55572, "cyfmac55572-pcie"); +CY_FW_DEF(4373, "cyfmac4373-pcie"); /* firmware config files */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.txt"); @@ -69,7 +74,6 @@ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.txt"); /* per-board firmware binaries */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.bin"); -MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.*.clm_blob"); static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43602_CHIP_ID, 0xFFFFFFFF, 43602), @@ -93,8 +97,11 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_4371_CHIP_ID, 0xFFFFFFFF, 4371), BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0xFFFFFFFF, 4378B1), /* revision ID 3 */ BRCMF_FW_ENTRY(CY_CC_89459_CHIP_ID, 0xFFFFFFFF, 4355), + BRCMF_FW_ENTRY(CY_CC_55572_CHIP_ID, 0xFFFFFFFF, 55572), + BRCMF_FW_ENTRY(CY_CC_4373_CHIP_ID, 0xFFFFFFFF, 4373), }; +#define BRCMF_PCIE_READ_SHARED_TIMEOUT 5000 /* msec */ #define BRCMF_PCIE_FW_UP_TIMEOUT 5000 /* msec */ #define BRCMF_PCIE_REG_MAP_SIZE (32 * 1024) @@ -105,7 +112,8 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_BAR0_WRAPPERBASE 0x70 #define BRCMF_PCIE_BAR0_WRAPBASE_DMP_OFFSET 0x1000 -#define BRCMF_PCIE_BARO_PCIE_ENUM_OFFSET 0x2000 +#define BRCMF_PCIE_BAR0_PCIE_ENUM_OFFSET 0x2000 +#define BRCMF_CYW55572_PCIE_BAR0_PCIE_ENUM_OFFSET 0x3000 #define BRCMF_PCIE_ARMCR4REG_BANKIDX 0x40 #define BRCMF_PCIE_ARMCR4REG_BANKPDA 0x4C @@ -123,6 +131,8 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_PCIE2REG_CONFIGDATA 0x124 #define BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0 0x140 #define BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1 0x144 +#define BRCMF_PCIE_PCIE2REG_DAR_D2H_MSG_0 0xA80 +#define BRCMF_PCIE_PCIE2REG_DAR_H2D_MSG_0 0xA90 #define BRCMF_PCIE_64_PCIE2REG_INTMASK 0xC14 #define BRCMF_PCIE_64_PCIE2REG_MAILBOXINT 0xC30 @@ -194,12 +204,14 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { BRCMF_PCIE_64_MB_INT_D2H7_DB0 | \ BRCMF_PCIE_64_MB_INT_D2H7_DB1) +#define BRCMF_PCIE_SHARED_VERSION_6 6 #define BRCMF_PCIE_SHARED_VERSION_7 7 #define BRCMF_PCIE_MIN_SHARED_VERSION 5 #define BRCMF_PCIE_MAX_SHARED_VERSION BRCMF_PCIE_SHARED_VERSION_7 #define BRCMF_PCIE_SHARED_VERSION_MASK 0x00FF #define BRCMF_PCIE_SHARED_DMA_INDEX 0x10000 #define BRCMF_PCIE_SHARED_DMA_2B_IDX 0x100000 +#define BRCMF_PCIE_SHARED_USE_MAILBOX 0x2000000 #define BRCMF_PCIE_SHARED_HOSTRDY_DB1 0x10000000 #define BRCMF_PCIE_FLAGS_HTOD_SPLIT 0x4000 @@ -216,6 +228,7 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_SHARED_DMA_SCRATCH_ADDR_OFFSET 56 #define BRCMF_SHARED_DMA_RINGUPD_LEN_OFFSET 64 #define BRCMF_SHARED_DMA_RINGUPD_ADDR_OFFSET 68 +#define BRCMF_SHARED_HOST_CAP_OFFSET 84 #define BRCMF_RING_H2D_RING_COUNT_OFFSET 0 #define BRCMF_RING_D2H_RING_COUNT_OFFSET 1 @@ -230,6 +243,9 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_DEF_MAX_RXBUFPOST 255 +#define BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY 0x400 +#define BRCMF_HOSTCAP_DS_NO_OOB_DW 0x1000 + #define BRCMF_CONSOLE_BUFADDR_OFFSET 8 #define BRCMF_CONSOLE_BUFSIZE_OFFSET 12 #define BRCMF_CONSOLE_WRITEIDX_OFFSET 16 @@ -255,18 +271,32 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = { #define BRCMF_PCIE_CFGREG_MSI_ADDR_L 0x5C #define BRCMF_PCIE_CFGREG_MSI_ADDR_H 0x60 #define BRCMF_PCIE_CFGREG_MSI_DATA 0x64 +#define BRCMF_PCIE_CFGREG_REVID 0x6C #define BRCMF_PCIE_CFGREG_LINK_STATUS_CTRL 0xBC #define BRCMF_PCIE_CFGREG_LINK_STATUS_CTRL2 0xDC #define BRCMF_PCIE_CFGREG_RBAR_CTRL 0x228 #define BRCMF_PCIE_CFGREG_PML1_SUB_CTRL1 0x248 #define BRCMF_PCIE_CFGREG_REG_BAR2_CONFIG 0x4E0 #define BRCMF_PCIE_CFGREG_REG_BAR3_CONFIG 0x4F4 +#define BRCMF_PCIE_CFGREG_REVID_SECURE_MODE BIT(31) #define BRCMF_PCIE_LINK_STATUS_CTRL_ASPM_ENAB 3 /* Magic number at a magic location to find RAM size */ #define BRCMF_RAMSIZE_MAGIC 0x534d4152 /* SMAR */ #define BRCMF_RAMSIZE_OFFSET 0x6c +#define BRCMF_ENTROPY_SEED_LEN 64u +#define BRCMF_ENTROPY_NONCE_LEN 16u +#define BRCMF_ENTROPY_HOST_LEN (BRCMF_ENTROPY_SEED_LEN + \ + BRCMF_ENTROPY_NONCE_LEN) +#define BRCMF_NVRAM_OFFSET_TCM 4u +#define BRCMF_NVRAM_COMPRS_FACTOR 4u +#define BRCMF_NVRAM_RNG_SIGNATURE 0xFEEDC0DEu + +struct brcmf_rand_metadata { + u32 signature; + u32 count; +}; struct brcmf_pcie_console { u32 base_addr; @@ -318,8 +348,6 @@ struct brcmf_pciedev_info { struct pci_dev *pdev; char fw_name[BRCMF_FW_NAME_LEN]; char nvram_name[BRCMF_FW_NAME_LEN]; - char clm_name[BRCMF_FW_NAME_LEN]; - const struct firmware *clm_fw; const struct brcmf_pcie_reginfo *reginfo; void __iomem *regs; void __iomem *tcm; @@ -328,6 +356,9 @@ struct brcmf_pciedev_info { struct brcmf_chip *ci; u32 coreid; struct brcmf_pcie_shared_info shared; + u8 hostready; + bool use_mailbox; + bool use_d0_inform; wait_queue_head_t mbdata_resp_wait; bool mbdata_completed; bool irq_allocated; @@ -341,6 +372,12 @@ struct brcmf_pciedev_info { u16 value); struct brcmf_mp_device *settings; struct brcmf_otp_params otp; + ulong bar1_size; +#ifdef DEBUG + u32 console_interval; + bool console_active; + struct timer_list timer; +#endif }; struct brcmf_pcie_ringbuf { @@ -441,6 +478,17 @@ static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_fw_request *fwreq); static struct brcmf_fw_request * brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo); +static void brcmf_pcie_bus_console_init(struct brcmf_pciedev_info *devinfo); +static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo, + bool error); + +static void +brcmf_pcie_fwcon_timer(struct brcmf_pciedev_info *devinfo, bool active); +static void brcmf_pcie_debugfs_create(struct device *dev); + +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ +DEFINE_RAW_SPINLOCK(pcie_lock); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ static u16 brcmf_pcie_read_reg16(struct brcmf_pciedev_info *devinfo, u32 reg_offset) @@ -472,18 +520,67 @@ brcmf_pcie_write_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset, static u8 brcmf_pcie_read_tcm8(struct brcmf_pciedev_info *devinfo, u32 mem_offset) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; + u8 value; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + value = ioread8(address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); + + return value; +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return -EINVAL; + } return (ioread8(address)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } static u16 brcmf_pcie_read_tcm16(struct brcmf_pciedev_info *devinfo, u32 mem_offset) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + u16 value; + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + value = ioread16(address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); + + return value; +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return -EINVAL; + } return (ioread16(address)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -491,9 +588,31 @@ static void brcmf_pcie_write_tcm16(struct brcmf_pciedev_info *devinfo, u32 mem_offset, u16 value) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + + iowrite16(value, address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } iowrite16(value, address); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -519,9 +638,33 @@ brcmf_pcie_write_idx(struct brcmf_pciedev_info *devinfo, u32 mem_offset, static u32 brcmf_pcie_read_tcm32(struct brcmf_pciedev_info *devinfo, u32 mem_offset) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + u32 value; + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + value = ioread32(address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); + + return value; +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return -EINVAL; + } return (ioread32(address)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -529,18 +672,64 @@ static void brcmf_pcie_write_tcm32(struct brcmf_pciedev_info *devinfo, u32 mem_offset, u32 value) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + iowrite32(value, address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } iowrite32(value, address); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } static u32 brcmf_pcie_read_ram32(struct brcmf_pciedev_info *devinfo, u32 mem_offset) { - void __iomem *addr = devinfo->tcm + devinfo->ci->rambase + mem_offset; + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); + void __iomem *address = devinfo->tcm + devinfo->ci->rambase + + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + u32 value; + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + value = ioread32(address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); + + return value; +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return -EINVAL; + } - return (ioread32(addr)); + return (ioread32(address)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -548,9 +737,145 @@ static void brcmf_pcie_write_ram32(struct brcmf_pciedev_info *devinfo, u32 mem_offset, u32 value) { - void __iomem *addr = devinfo->tcm + devinfo->ci->rambase + mem_offset; + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); + void __iomem *address = devinfo->tcm + devinfo->ci->rambase + + mem_offset; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; + + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } + iowrite32(value, address); + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#else + if ((address - devinfo->tcm) >= devinfo->bar1_size) { + brcmf_err(bus, "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } - iowrite32(value, addr); + iowrite32(value, address); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ +} + + +static void +brcmf_pcie_copy_mem_todev(struct brcmf_pciedev_info *devinfo, u32 mem_offset, + void *srcaddr, u32 len) +{ + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); + void __iomem *address = devinfo->tcm + mem_offset; + __le32 *src32; + __le16 *src16; + u8 *src8; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + + if (((ulong)address & 4) || ((ulong)srcaddr & 4) || (len & 4)) { + if (((ulong)address & 2) || ((ulong)srcaddr & 2) || (len & 2)) { + src8 = (u8 *)srcaddr; + while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - + devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } + iowrite8(*src8, address); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + address++; + src8++; + len--; + } + } else { + len = len / 2; + src16 = (__le16 *)srcaddr; + while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - + devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } + iowrite16(le16_to_cpu(*src16), address); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + address += 2; + src16++; + len--; + } + } + } else { + len = len / 4; + src32 = (__le32 *)srcaddr; + while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } + iowrite32(le32_to_cpu(*src32), address); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + address += 4; + src32++; + len--; + } + } +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -558,16 +883,43 @@ static void brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset, void *dstaddr, u32 len) { + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); void __iomem *address = devinfo->tcm + mem_offset; __le32 *dst32; __le16 *dst16; u8 *dst8; +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + unsigned long flags; +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ if (((ulong)address & 4) || ((ulong)dstaddr & 4) || (len & 4)) { if (((ulong)address & 2) || ((ulong)dstaddr & 2) || (len & 2)) { dst8 = (u8 *)dstaddr; while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - + devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } *dst8 = ioread8(address); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ address++; dst8++; len--; @@ -576,7 +928,29 @@ brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset, len = len / 2; dst16 = (__le16 *)dstaddr; while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - + devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } *dst16 = cpu_to_le16(ioread16(address)); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ address += 2; dst16++; len--; @@ -586,12 +960,36 @@ brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset, len = len / 4; dst32 = (__le32 *)dstaddr; while (len) { +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_lock_irqsave(&pcie_lock, flags); + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + pci_write_config_dword + (devinfo->pdev, + BCMA_PCI_BAR1_WIN, + devinfo->bar1_size); + address = address - devinfo->bar1_size; + } else +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if ((address - devinfo->tcm) >= + devinfo->bar1_size) { + brcmf_err(bus, + "mem_offset:%d exceeds device size=%ld\n", + mem_offset, devinfo->bar1_size); + return; + } *dst32 = cpu_to_le32(ioread32(address)); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + raw_spin_unlock_irqrestore(&pcie_lock, flags); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ address += 4; dst32++; len--; } } +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + pci_write_config_dword(devinfo->pdev, BCMA_PCI_BAR1_WIN, 0x0); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ } @@ -637,6 +1035,9 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo) BRCMF_PCIE_CFGREG_MSI_ADDR_L, BRCMF_PCIE_CFGREG_MSI_ADDR_H, BRCMF_PCIE_CFGREG_MSI_DATA, +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + BCMA_PCI_BAR1_WIN, +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ BRCMF_PCIE_CFGREG_LINK_STATUS_CTRL2, BRCMF_PCIE_CFGREG_RBAR_CTRL, BRCMF_PCIE_CFGREG_PML1_SUB_CTRL1, @@ -658,9 +1059,15 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo) val); /* Watchdog reset */ + if (devinfo->ci->blhs) + devinfo->ci->blhs->init(devinfo->ci); brcmf_pcie_select_core(devinfo, BCMA_CORE_CHIPCOMMON); WRITECC32(devinfo, watchdog, 4); msleep(100); + if (devinfo->ci->blhs) + if (devinfo->ci->blhs->post_wdreset(devinfo->ci)) + return; + /* Restore ASPM */ brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); @@ -699,8 +1106,39 @@ static void brcmf_pcie_attach(struct brcmf_pciedev_info *devinfo) } +static int brcmf_pcie_bus_readshared(struct brcmf_pciedev_info *devinfo, + u32 nvram_csm) +{ + struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); + u32 loop_counter; + u32 addr_le; + u32 addr = 0; + + loop_counter = BRCMF_PCIE_READ_SHARED_TIMEOUT / 50; + while ((addr == 0 || addr == nvram_csm) && (loop_counter)) { + msleep(50); + addr_le = brcmf_pcie_read_ram32(devinfo, + devinfo->ci->ramsize - 4); + addr = le32_to_cpu(addr_le); + loop_counter--; + } + if (addr == 0 || addr == nvram_csm || addr < devinfo->ci->rambase || + addr >= devinfo->ci->rambase + devinfo->ci->ramsize) { + brcmf_err(bus, "Invalid shared RAM address 0x%08x\n", addr); + return -ENODEV; + } + devinfo->shared.tcm_base_address = addr; + brcmf_dbg(PCIE, "Shared RAM addr: 0x%08x\n", addr); + + brcmf_pcie_bus_console_init(devinfo); + return 0; +} + static int brcmf_pcie_enter_download_state(struct brcmf_pciedev_info *devinfo) { + struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); + int err = 0; + if (devinfo->ci->chip == BRCM_CC_43602_CHIP_ID) { brcmf_pcie_select_core(devinfo, BCMA_CORE_ARM_CR4); brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_ARMCR4REG_BANKIDX, @@ -712,7 +1150,19 @@ static int brcmf_pcie_enter_download_state(struct brcmf_pciedev_info *devinfo) brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_ARMCR4REG_BANKPDA, 0); } - return 0; + + if (devinfo->ci->blhs) { + err = devinfo->ci->blhs->prep_fwdl(devinfo->ci); + if (err) { + brcmf_err(bus, "FW download preparation failed"); + return err; + } + + if (!brcmf_pcie_bus_readshared(devinfo, 0)) + brcmf_pcie_bus_console_read(devinfo, false); + } + + return err; } @@ -726,8 +1176,14 @@ static int brcmf_pcie_exit_download_state(struct brcmf_pciedev_info *devinfo, brcmf_chip_resetcore(core, 0, 0, 0); } - if (!brcmf_chip_set_active(devinfo->ci, resetintr)) - return -EIO; + if (devinfo->ci->blhs) { + brcmf_pcie_bus_console_read(devinfo, false); + devinfo->ci->blhs->post_nvramdl(devinfo->ci); + } else { + if (!brcmf_chip_set_active(devinfo->ci, resetintr)) + return -EINVAL; + } + return 0; } @@ -736,41 +1192,53 @@ static int brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data) { struct brcmf_pcie_shared_info *shared; + struct brcmf_bus *bus; + int err; struct brcmf_core *core; u32 addr; u32 cur_htod_mb_data; u32 i; shared = &devinfo->shared; - addr = shared->htod_mb_data_addr; - cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr); - - if (cur_htod_mb_data != 0) - brcmf_dbg(PCIE, "MB transaction is already pending 0x%04x\n", - cur_htod_mb_data); - - i = 0; - while (cur_htod_mb_data != 0) { - msleep(10); - i++; - if (i > 100) - return -EIO; + bus = dev_get_drvdata(&devinfo->pdev->dev); + if (shared->version >= BRCMF_PCIE_SHARED_VERSION_6 && + !devinfo->use_mailbox) { + err = brcmf_msgbuf_tx_mbdata(bus->drvr, htod_mb_data); + if (err) { + brcmf_err(bus, "sendimg mbdata failed err=%d\n", err); + return err; + } + } else { + addr = shared->htod_mb_data_addr; cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr); - } - brcmf_pcie_write_tcm32(devinfo, addr, htod_mb_data); - pci_write_config_dword(devinfo->pdev, BRCMF_PCIE_REG_SBMBX, 1); + if (cur_htod_mb_data != 0) + brcmf_dbg(PCIE, "MB transaction is already pending 0x%04x\n", + cur_htod_mb_data); + + i = 0; + while (cur_htod_mb_data != 0) { + msleep(10); + i++; + if (i > 100) + return -EIO; + cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr); + } - /* Send mailbox interrupt twice as a hardware workaround */ - core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2); - if (core->rev <= 13) + brcmf_pcie_write_tcm32(devinfo, addr, htod_mb_data); pci_write_config_dword(devinfo->pdev, BRCMF_PCIE_REG_SBMBX, 1); + /* Send mailbox interrupt twice as a hardware workaround */ + core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2); + if (core->rev <= 13) + pci_write_config_dword(devinfo->pdev, + BRCMF_PCIE_REG_SBMBX, 1); + } return 0; } -static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo) +static u32 brcmf_pcie_read_mb_data(struct brcmf_pciedev_info *devinfo) { struct brcmf_pcie_shared_info *shared; u32 addr; @@ -779,36 +1247,42 @@ static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo) shared = &devinfo->shared; addr = shared->dtoh_mb_data_addr; dtoh_mb_data = brcmf_pcie_read_tcm32(devinfo, addr); + brcmf_pcie_write_tcm32(devinfo, addr, 0); + return dtoh_mb_data; +} - if (!dtoh_mb_data) - return; +void brcmf_pcie_handle_mb_data(struct brcmf_bus *bus_if, u32 d2h_mb_data) +{ + struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie; + struct brcmf_pciedev_info *devinfo = buspub->devinfo; - brcmf_pcie_write_tcm32(devinfo, addr, 0); + brcmf_dbg(INFO, "D2H_MB_DATA: 0x%04x\n", d2h_mb_data); - brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", dtoh_mb_data); - if (dtoh_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ) { - brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n"); + if (d2h_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ) { + brcmf_dbg(INFO, "D2H_MB_DATA: DEEP SLEEP REQ\n"); brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK); - brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n"); + brcmf_dbg(INFO, "D2H_MB_DATA: sent DEEP SLEEP ACK\n"); } - if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE) - brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n"); - if (dtoh_mb_data & BRCMF_D2H_DEV_D3_ACK) { - brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n"); + + if (d2h_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE) + brcmf_dbg(INFO, "D2H_MB_DATA: DEEP SLEEP EXIT\n"); + if (d2h_mb_data & BRCMF_D2H_DEV_D3_ACK) { + brcmf_dbg(INFO, "D2H_MB_DATA: D3 ACK\n"); devinfo->mbdata_completed = true; wake_up(&devinfo->mbdata_resp_wait); } - if (dtoh_mb_data & BRCMF_D2H_DEV_FWHALT) { - brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n"); + + if (d2h_mb_data & BRCMF_D2H_DEV_FWHALT) { + brcmf_dbg(INFO, "D2H_MB_DATA: FW HALT\n"); brcmf_fw_crashed(&devinfo->pdev->dev); } } - static void brcmf_pcie_bus_console_init(struct brcmf_pciedev_info *devinfo) { struct brcmf_pcie_shared_info *shared; struct brcmf_pcie_console *console; + u32 buf_addr; u32 addr; shared = &devinfo->shared; @@ -817,7 +1291,12 @@ static void brcmf_pcie_bus_console_init(struct brcmf_pciedev_info *devinfo) console->base_addr = brcmf_pcie_read_tcm32(devinfo, addr); addr = console->base_addr + BRCMF_CONSOLE_BUFADDR_OFFSET; - console->buf_addr = brcmf_pcie_read_tcm32(devinfo, addr); + buf_addr = brcmf_pcie_read_tcm32(devinfo, addr); + /* reset console index when buffer address is updated */ + if (console->buf_addr != buf_addr) { + console->buf_addr = buf_addr; + console->read_idx = 0; + } addr = console->base_addr + BRCMF_CONSOLE_BUFSIZE_OFFSET; console->bufsize = brcmf_pcie_read_tcm32(devinfo, addr); @@ -915,6 +1394,9 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) { struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)arg; u32 status; + u32 d2h_mbdata; + struct pci_dev *pdev = devinfo->pdev; + struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev); devinfo->in_irq = true; status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint); @@ -922,8 +1404,10 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg) if (status) { brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status); - if (status & devinfo->reginfo->int_fn0) - brcmf_pcie_handle_mb_data(devinfo); + if (status & devinfo->reginfo->int_fn0) { + d2h_mbdata = brcmf_pcie_read_mb_data(devinfo); + brcmf_pcie_handle_mb_data(bus, d2h_mbdata); + } if (status & devinfo->reginfo->int_d2h_db) { if (devinfo->state == BRCMFMAC_PCIE_STATE_UP) brcmf_proto_msgbuf_rx_trigger( @@ -1206,9 +1690,14 @@ static int brcmf_pcie_init_ringbuffers(struct brcmf_pciedev_info *devinfo) u16 max_flowrings; u16 max_submissionrings; u16 max_completionrings; - +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + brcmf_pcie_copy_dev_tomem(devinfo, devinfo->shared.ring_info_addr, + &ringinfo, sizeof(ringinfo)); +#else memcpy_fromio(&ringinfo, devinfo->tcm + devinfo->shared.ring_info_addr, sizeof(ringinfo)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ + if (devinfo->shared.version >= 6) { max_submissionrings = le16_to_cpu(ringinfo.max_submissionrings); max_flowrings = le16_to_cpu(ringinfo.max_flowrings); @@ -1219,7 +1708,7 @@ static int brcmf_pcie_init_ringbuffers(struct brcmf_pciedev_info *devinfo) BRCMF_NROF_H2D_COMMON_MSGRINGS; max_completionrings = BRCMF_NROF_D2H_COMMON_MSGRINGS; } - if (max_flowrings > 512) { + if (max_flowrings > 256) { brcmf_err(bus, "invalid max_flowrings(%d)\n", max_flowrings); return -EIO; } @@ -1281,8 +1770,14 @@ static int brcmf_pcie_init_ringbuffers(struct brcmf_pciedev_info *devinfo) ringinfo.d2h_r_idx_hostaddr.high_addr = cpu_to_le32(address >> 32); +#ifdef CONFIG_BRCMFMAC_PCIE_BARWIN_SZ + brcmf_pcie_copy_mem_todev(devinfo, + devinfo->shared.ring_info_addr, + &ringinfo, sizeof(ringinfo)); +#else memcpy_toio(devinfo->tcm + devinfo->shared.ring_info_addr, &ringinfo, sizeof(ringinfo)); +#endif /* CONFIG_BRCMFMAC_PCIE_BARWIN_SZ */ brcmf_dbg(PCIE, "Using host memory indices\n"); } @@ -1418,6 +1913,11 @@ static int brcmf_pcie_init_scratchbuffers(struct brcmf_pciedev_info *devinfo) static void brcmf_pcie_down(struct device *dev) { + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pciedev *pcie_bus_dev = bus_if->bus_priv.pcie; + struct brcmf_pciedev_info *devinfo = pcie_bus_dev->devinfo; + + brcmf_pcie_fwcon_timer(devinfo, false); } static int brcmf_pcie_preinit(struct device *dev) @@ -1485,26 +1985,24 @@ static int brcmf_pcie_get_memdump(struct device *dev, void *data, size_t len) return 0; } -static int brcmf_pcie_get_blob(struct device *dev, const struct firmware **fw, - enum brcmf_blob_type type) +static +int brcmf_pcie_get_fwname(struct device *dev, const char *ext, u8 *fw_name) { - struct brcmf_bus *bus_if = dev_get_drvdata(dev); - struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie; - struct brcmf_pciedev_info *devinfo = buspub->devinfo; - - switch (type) { - case BRCMF_BLOB_CLM: - *fw = devinfo->clm_fw; - devinfo->clm_fw = NULL; - break; - default: - return -ENOENT; - } - - if (!*fw) - return -ENOENT; - - return 0; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_fw_request *fwreq; + struct brcmf_fw_name fwnames[] = { + { ext, fw_name }, + }; + + fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev, + brcmf_pcie_fwnames, + ARRAY_SIZE(brcmf_pcie_fwnames), + fwnames, ARRAY_SIZE(fwnames)); + if (!fwreq) + return -ENOMEM; + + kfree(fwreq); + return 0; } static int brcmf_pcie_reset(struct device *dev) @@ -1550,8 +2048,9 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = { .wowl_config = brcmf_pcie_wowl_config, .get_ramsize = brcmf_pcie_get_ramsize, .get_memdump = brcmf_pcie_get_memdump, - .get_blob = brcmf_pcie_get_blob, + .get_fwname = brcmf_pcie_get_fwname, .reset = brcmf_pcie_reset, + .debugfs_create = brcmf_pcie_debugfs_create, }; @@ -1577,6 +2076,28 @@ brcmf_pcie_adjust_ramsize(struct brcmf_pciedev_info *devinfo, u8 *data, } +static void +brcmf_pcie_write_rand(struct brcmf_pciedev_info *devinfo, u32 nvram_csm) +{ + struct brcmf_rand_metadata rand_data; + u8 rand_buf[BRCMF_ENTROPY_HOST_LEN]; + u32 count = BRCMF_ENTROPY_HOST_LEN; + u32 address; + + address = devinfo->ci->rambase + + (devinfo->ci->ramsize - BRCMF_NVRAM_OFFSET_TCM) - + ((nvram_csm & 0xffff) * BRCMF_NVRAM_COMPRS_FACTOR) - + sizeof(rand_data); + memset(rand_buf, 0, BRCMF_ENTROPY_HOST_LEN); + rand_data.signature = cpu_to_le32(BRCMF_NVRAM_RNG_SIGNATURE); + rand_data.count = cpu_to_le32(count); + brcmf_pcie_copy_mem_todev(devinfo, address, &rand_data, + sizeof(rand_data)); + address -= count; + get_random_bytes(rand_buf, count); + brcmf_pcie_copy_mem_todev(devinfo, address, rand_buf, count); +} + static int brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, u32 sharedram_addr) @@ -1584,6 +2105,7 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); struct brcmf_pcie_shared_info *shared; u32 addr; + u32 host_cap; shared = &devinfo->shared; shared->tcm_base_address = sharedram_addr; @@ -1623,6 +2145,30 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, addr = sharedram_addr + BRCMF_SHARED_RING_INFO_ADDR_OFFSET; shared->ring_info_addr = brcmf_pcie_read_tcm32(devinfo, addr); + if (shared->version >= BRCMF_PCIE_SHARED_VERSION_6) { + host_cap = shared->version; + + /* Disable OOB Device Wake based DeepSleep State Machine */ + host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW; + + devinfo->hostready = + ((shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) + == BRCMF_PCIE_SHARED_HOSTRDY_DB1); + if (devinfo->hostready) { + brcmf_dbg(PCIE, "HostReady supported by dongle.\n"); + host_cap |= BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY; + } + devinfo->use_mailbox = + ((shared->flags & BRCMF_PCIE_SHARED_USE_MAILBOX) + == BRCMF_PCIE_SHARED_USE_MAILBOX); + devinfo->use_d0_inform = false; + addr = sharedram_addr + BRCMF_SHARED_HOST_CAP_OFFSET; + + brcmf_pcie_write_tcm32(devinfo, addr, host_cap); + } else { + devinfo->use_d0_inform = true; + } + brcmf_dbg(PCIE, "max rx buf post %d, rx dataoffset %d\n", shared->max_rxbufpost, shared->rx_dataoffset); @@ -1632,25 +2178,22 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo, return 0; } -struct brcmf_random_seed_footer { - __le32 length; - __le32 magic; -}; - -#define BRCMF_RANDOM_SEED_MAGIC 0xfeedc0de -#define BRCMF_RANDOM_SEED_LENGTH 0x100 static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, const struct firmware *fw, void *nvram, u32 nvram_len) { struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); + struct trx_header_le *trx = (struct trx_header_le *)fw->data; + u32 fw_size; u32 sharedram_addr; u32 sharedram_addr_written; u32 loop_counter; int err; u32 address; u32 resetintr; + u32 nvram_lenw; + u32 nvram_csm; brcmf_dbg(PCIE, "Halt ARM.\n"); err = brcmf_pcie_enter_download_state(devinfo); @@ -1658,52 +2201,68 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, return err; brcmf_dbg(PCIE, "Download FW %s\n", devinfo->fw_name); - memcpy_toio(devinfo->tcm + devinfo->ci->rambase, - (void *)fw->data, fw->size); + address = devinfo->ci->rambase; + fw_size = fw->size; + if (trx->magic == cpu_to_le32(TRX_MAGIC)) { + address -= sizeof(struct trx_header_le); + fw_size = le32_to_cpu(trx->len); + } + brcmf_pcie_copy_mem_todev(devinfo, address, (void *)fw->data, fw_size); resetintr = get_unaligned_le32(fw->data); release_firmware(fw); - /* reset last 4 bytes of RAM address. to be used for shared - * area. This identifies when FW is running - */ - brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, 0); + if (devinfo->ci->blhs) { + brcmf_pcie_bus_console_read(devinfo, false); + err = devinfo->ci->blhs->post_fwdl(devinfo->ci); + if (err) { + brcmf_err(bus, "FW download failed, err=%d\n", err); + return err; + } + + err = devinfo->ci->blhs->chk_validation(devinfo->ci); + if (err) { + brcmf_err(bus, "FW valication failed, err=%d\n", err); + return err; + } + } else { + /* reset last 4 bytes of RAM address. to be used for shared + * area. This identifies when FW is running + */ + brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, 0); + } if (nvram) { brcmf_dbg(PCIE, "Download NVRAM %s\n", devinfo->nvram_name); address = devinfo->ci->rambase + devinfo->ci->ramsize - nvram_len; - memcpy_toio(devinfo->tcm + address, nvram, nvram_len); - brcmf_fw_nvram_free(nvram); - if (devinfo->otp.valid) { - size_t rand_len = BRCMF_RANDOM_SEED_LENGTH; - struct brcmf_random_seed_footer footer = { - .length = cpu_to_le32(rand_len), - .magic = cpu_to_le32(BRCMF_RANDOM_SEED_MAGIC), - }; - void *randbuf; - - /* Some Apple chips/firmwares expect a buffer of random - * data to be present before NVRAM - */ - brcmf_dbg(PCIE, "Download random seed\n"); - - address -= sizeof(footer); - memcpy_toio(devinfo->tcm + address, &footer, - sizeof(footer)); - - address -= rand_len; - randbuf = kzalloc(rand_len, GFP_KERNEL); - get_random_bytes(randbuf, rand_len); - memcpy_toio(devinfo->tcm + address, randbuf, rand_len); - kfree(randbuf); - } + if (devinfo->ci->blhs) + address -= 4; + brcmf_pcie_copy_mem_todev(devinfo, address, nvram, nvram_len); + + /* Convert nvram_len to words to determine the length token */ + nvram_lenw = nvram_len / 4; + /* subtract word used to store the token itself on non-blhs devices */ + if (!devinfo->ci->blhs) + nvram_lenw -= 1; + nvram_csm = (~nvram_lenw << 16) | (nvram_lenw & 0x0000FFFF); + brcmf_fw_nvram_free(nvram); } else { + nvram_csm = 0; brcmf_dbg(PCIE, "No matching NVRAM file found %s\n", devinfo->nvram_name); } + if (devinfo->ci->chip == CY_CC_55572_CHIP_ID) { + /* Write the length token to the last word of RAM address */ + brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, + cpu_to_le32(nvram_csm)); + + /* Write random numbers to TCM for randomizing heap address */ + brcmf_pcie_write_rand(devinfo, nvram_csm); + } + sharedram_addr_written = brcmf_pcie_read_ram32(devinfo, devinfo->ci->ramsize - 4); @@ -1712,6 +2271,9 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo, if (err) return err; + if (!brcmf_pcie_bus_readshared(devinfo, nvram_csm)) + brcmf_pcie_bus_console_read(devinfo, false); + brcmf_dbg(PCIE, "Wait for FW init\n"); sharedram_addr = sharedram_addr_written; loop_counter = BRCMF_PCIE_FW_UP_TIMEOUT / 50; @@ -1768,6 +2330,7 @@ static int brcmf_pcie_get_resource(struct brcmf_pciedev_info *devinfo) devinfo->regs = ioremap(bar0_addr, BRCMF_PCIE_REG_MAP_SIZE); devinfo->tcm = ioremap(bar1_addr, bar1_size); + devinfo->bar1_size = bar1_size; if (!devinfo->regs || !devinfo->tcm) { brcmf_err(bus, "ioremap() failed (%p,%p)\n", devinfo->regs, @@ -1794,6 +2357,21 @@ static void brcmf_pcie_release_resource(struct brcmf_pciedev_info *devinfo) pci_disable_device(devinfo->pdev); } +static u32 brcmf_pcie_buscore_blhs_read(void *ctx, u32 reg_offset) +{ + struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx; + + brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); + return brcmf_pcie_read_reg32(devinfo, reg_offset); +} + +static void brcmf_pcie_buscore_blhs_write(void *ctx, u32 reg_offset, u32 value) +{ + struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx; + + brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); + brcmf_pcie_write_reg32(devinfo, reg_offset, value); +} static u32 brcmf_pcie_buscore_prep_addr(const struct pci_dev *pdev, u32 addr) { @@ -1864,12 +2442,64 @@ static void brcmf_pcie_buscore_activate(void *ctx, struct brcmf_chip *chip, } +static int +brcmf_pcie_buscore_sec_attach(void *ctx, struct brcmf_blhs **blhs, struct brcmf_ccsec **ccsec, + u32 flag, uint timeout, uint interval) +{ + struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx; + struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev); + struct brcmf_blhs *blhsh; + u32 regdata; + u32 pcie_enum; + u32 addr; + + if (devinfo->pdev->vendor != CY_PCIE_VENDOR_ID_CYPRESS) + return 0; + + pci_read_config_dword(devinfo->pdev, BRCMF_PCIE_CFGREG_REVID, ®data); + if (regdata & BRCMF_PCIE_CFGREG_REVID_SECURE_MODE) { + blhsh = kzalloc(sizeof(*blhsh), GFP_KERNEL); + if (!blhsh) + return -ENOMEM; + + blhsh->d2h = BRCMF_PCIE_PCIE2REG_DAR_D2H_MSG_0; + blhsh->h2d = BRCMF_PCIE_PCIE2REG_DAR_H2D_MSG_0; + blhsh->read = brcmf_pcie_buscore_blhs_read; + blhsh->write = brcmf_pcie_buscore_blhs_write; + + /* Host indication for bootloarder to start the init */ + if (devinfo->pdev->device == CY_PCIE_55572_DEVICE_ID) + pcie_enum = BRCMF_CYW55572_PCIE_BAR0_PCIE_ENUM_OFFSET; + else + pcie_enum = BRCMF_PCIE_BAR0_PCIE_ENUM_OFFSET; + + pci_read_config_dword(devinfo->pdev, PCI_BASE_ADDRESS_0, + ®data); + addr = regdata + pcie_enum + blhsh->h2d; + brcmf_pcie_buscore_write32(ctx, addr, 0); + + addr = regdata + pcie_enum + blhsh->d2h; + SPINWAIT_MS((brcmf_pcie_buscore_read32(ctx, addr) & flag) == 0, + timeout, interval); + regdata = brcmf_pcie_buscore_read32(ctx, addr); + if (!(regdata & flag)) { + brcmf_err(bus, "Timeout waiting for bootloader ready\n"); + kfree(blhsh); + return -EPERM; + } + *blhs = blhsh; + } + + return 0; +} + static const struct brcmf_buscore_ops brcmf_pcie_buscore_ops = { .prepare = brcmf_pcie_buscoreprep, .reset = brcmf_pcie_buscore_reset, .activate = brcmf_pcie_buscore_activate, .read32 = brcmf_pcie_buscore_read32, .write32 = brcmf_pcie_buscore_write32, + .sec_attach = brcmf_pcie_buscore_sec_attach, }; #define BRCMF_OTP_SYS_VENDOR 0x15 @@ -2071,7 +2701,6 @@ static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo) #define BRCMF_PCIE_FW_CODE 0 #define BRCMF_PCIE_FW_NVRAM 1 -#define BRCMF_PCIE_FW_CLM 2 static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_fw_request *fwreq) @@ -2084,19 +2713,19 @@ static void brcmf_pcie_setup(struct device *dev, int ret, struct brcmf_commonring **flowrings; u32 i, nvram_len; + bus = dev_get_drvdata(dev); + pcie_bus_dev = bus->bus_priv.pcie; + devinfo = pcie_bus_dev->devinfo; + /* check firmware loading result */ if (ret) goto fail; - bus = dev_get_drvdata(dev); - pcie_bus_dev = bus->bus_priv.pcie; - devinfo = pcie_bus_dev->devinfo; brcmf_pcie_attach(devinfo); fw = fwreq->items[BRCMF_PCIE_FW_CODE].binary; nvram = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.len; - devinfo->clm_fw = fwreq->items[BRCMF_PCIE_FW_CLM].binary; kfree(fwreq); ret = brcmf_chip_get_raminfo(devinfo->ci); @@ -2115,8 +2744,11 @@ static void brcmf_pcie_setup(struct device *dev, int ret, brcmf_pcie_adjust_ramsize(devinfo, (u8 *)fw->data, fw->size); ret = brcmf_pcie_download_fw_nvram(devinfo, fw, nvram, nvram_len); - if (ret) + if (ret) { + if (devinfo->ci->blhs && !brcmf_pcie_bus_readshared(devinfo, 0)) + brcmf_pcie_bus_console_read(devinfo, true); goto fail; + } devinfo->state = BRCMFMAC_PCIE_STATE_UP; @@ -2153,15 +2785,20 @@ static void brcmf_pcie_setup(struct device *dev, int ret, init_waitqueue_head(&devinfo->mbdata_resp_wait); - ret = brcmf_attach(&devinfo->pdev->dev); + ret = brcmf_attach(&devinfo->pdev->dev, true); if (ret) goto fail; brcmf_pcie_bus_console_read(devinfo, false); + brcmf_pcie_fwcon_timer(devinfo, true); + return; fail: + brcmf_err(bus, "Dongle setup failed\n"); + brcmf_pcie_bus_console_read(devinfo, true); + brcmf_fw_crashed(dev); device_release_driver(dev); } @@ -2172,21 +2809,26 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) struct brcmf_fw_name fwnames[] = { { ".bin", devinfo->fw_name }, { ".txt", devinfo->nvram_name }, - { ".clm_blob", devinfo->clm_name }, }; + u32 chip; + + if (devinfo->ci->blhs) + fwnames[BRCMF_PCIE_FW_CODE].extension = ".trxse"; - fwreq = brcmf_fw_alloc_request(devinfo->ci->chip, devinfo->ci->chiprev, + chip = devinfo->ci->chip; + fwreq = brcmf_fw_alloc_request(chip, devinfo->ci->chiprev, brcmf_pcie_fwnames, ARRAY_SIZE(brcmf_pcie_fwnames), fwnames, ARRAY_SIZE(fwnames)); if (!fwreq) return NULL; - fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_BINARY; + if (devinfo->ci->blhs) + fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_TRXSE; + else + fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_PCIE_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; fwreq->items[BRCMF_PCIE_FW_NVRAM].flags = BRCMF_FW_REQF_OPTIONAL; - fwreq->items[BRCMF_PCIE_FW_CLM].type = BRCMF_FW_TYPE_BINARY; - fwreq->items[BRCMF_PCIE_FW_CLM].flags = BRCMF_FW_REQF_OPTIONAL; /* NVRAM reserves PCI domain 0 for Broadcom's SDK faked bus */ fwreq->domain_nr = pci_domain_nr(devinfo->pdev->bus) + 1; fwreq->bus_nr = devinfo->pdev->bus->number; @@ -2233,6 +2875,105 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo) return fwreq; } +#ifdef DEBUG +static void +brcmf_pcie_fwcon_timer(struct brcmf_pciedev_info *devinfo, bool active) +{ + if (!active) { + if (devinfo->console_active) { + del_timer_sync(&devinfo->timer); + devinfo->console_active = false; + } + return; + } + + /* don't start the timer */ + if (devinfo->state != BRCMFMAC_PCIE_STATE_UP || + !devinfo->console_interval || !BRCMF_FWCON_ON()) + return; + + if (!devinfo->console_active) { + devinfo->timer.expires = jiffies + devinfo->console_interval; + add_timer(&devinfo->timer); + devinfo->console_active = true; + } else { + /* Reschedule the timer */ + mod_timer(&devinfo->timer, jiffies + devinfo->console_interval); + } +} + +static void +brcmf_pcie_fwcon(struct timer_list *t) +{ + struct brcmf_pciedev_info *devinfo = from_timer(devinfo, t, timer); + + if (!devinfo->console_active) + return; + + brcmf_pcie_bus_console_read(devinfo, false); + + /* Reschedule the timer if console interval is not zero */ + mod_timer(&devinfo->timer, jiffies + devinfo->console_interval); +} + +static int brcmf_pcie_console_interval_get(void *data, u64 *val) +{ + struct brcmf_pciedev_info *devinfo = data; + + *val = devinfo->console_interval; + + return 0; +} + +static int brcmf_pcie_console_interval_set(void *data, u64 val) +{ + struct brcmf_pciedev_info *devinfo = data; + + if (val > MAX_CONSOLE_INTERVAL) + return -EINVAL; + + devinfo->console_interval = val; + + if (!val && devinfo->console_active) + brcmf_pcie_fwcon_timer(devinfo, false); + else if (val) + brcmf_pcie_fwcon_timer(devinfo, true); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(brcmf_pcie_console_interval_fops, + brcmf_pcie_console_interval_get, + brcmf_pcie_console_interval_set, + "%llu\n"); + +static void brcmf_pcie_debugfs_create(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + struct brcmf_pciedev *pcie_bus_dev = bus_if->bus_priv.pcie; + struct brcmf_pciedev_info *devinfo = pcie_bus_dev->devinfo; + struct dentry *dentry = brcmf_debugfs_get_devdir(drvr); + + if (IS_ERR_OR_NULL(dentry)) + return; + + devinfo->console_interval = BRCMF_CONSOLE; + + debugfs_create_file("console_interval", 0644, dentry, devinfo, + &brcmf_pcie_console_interval_fops); +} + +#else +void brcmf_pcie_fwcon_timer(struct brcmf_pciedev_info *devinfo, bool active) +{ +} + +static void brcmf_pcie_debugfs_create(struct device *dev) +{ +} +#endif + static int brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -2314,6 +3055,10 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto fail_brcmf; } +#ifdef DEBUG + /* Set up the fwcon timer */ + timer_setup(&devinfo->timer, brcmf_pcie_fwcon, 0); +#endif fwreq = brcmf_pcie_prepare_fw_request(devinfo); if (!fwreq) { ret = -ENOMEM; @@ -2360,6 +3105,8 @@ brcmf_pcie_remove(struct pci_dev *pdev) devinfo = bus->bus_priv.pcie->devinfo; brcmf_pcie_bus_console_read(devinfo, false); + brcmf_pcie_fwcon_timer(devinfo, false); + devinfo->state = BRCMFMAC_PCIE_STATE_DOWN; if (devinfo->ci) brcmf_pcie_intr_disable(devinfo); @@ -2377,7 +3124,6 @@ brcmf_pcie_remove(struct pci_dev *pdev) brcmf_pcie_release_ringbuffers(devinfo); brcmf_pcie_reset_device(devinfo); brcmf_pcie_release_resource(devinfo); - release_firmware(devinfo->clm_fw); if (devinfo->ci) brcmf_chip_detach(devinfo->ci); @@ -2396,11 +3142,24 @@ static int brcmf_pcie_pm_enter_D3(struct device *dev) { struct brcmf_pciedev_info *devinfo; struct brcmf_bus *bus; + struct brcmf_cfg80211_info *config; + int retry = BRCMF_PM_WAIT_MAXRETRY; brcmf_dbg(PCIE, "Enter\n"); bus = dev_get_drvdata(dev); devinfo = bus->bus_priv.pcie->devinfo; + config = bus->drvr->config; + + while (retry && + config->pm_state == BRCMF_CFG80211_PM_STATE_SUSPENDING) { + usleep_range(10000, 20000); + retry--; + } + if (!retry && config->pm_state == BRCMF_CFG80211_PM_STATE_SUSPENDING) + brcmf_err(bus, "timed out wait for cfg80211 suspended\n"); + + brcmf_pcie_fwcon_timer(devinfo, false); brcmf_bus_change_state(bus, BRCMF_BUS_DOWN); @@ -2437,14 +3196,26 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev) /* Check if device is still up and running, if so we are ready */ if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) { brcmf_dbg(PCIE, "Try to wakeup device....\n"); - if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM)) - goto cleanup; + if (devinfo->use_d0_inform) { + if (brcmf_pcie_send_mb_data(devinfo, + BRCMF_H2D_HOST_D0_INFORM)) + goto cleanup; + } else { + brcmf_pcie_hostready(devinfo); + } + brcmf_dbg(PCIE, "Hot resume, continue....\n"); devinfo->state = BRCMFMAC_PCIE_STATE_UP; brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2); brcmf_bus_change_state(bus, BRCMF_BUS_UP); brcmf_pcie_intr_enable(devinfo); - brcmf_pcie_hostready(devinfo); + if (devinfo->use_d0_inform) { + brcmf_dbg(TRACE, "sending brcmf_pcie_hostready since use_d0_inform=%d\n", + devinfo->use_d0_inform); + brcmf_pcie_hostready(devinfo); + } + + brcmf_pcie_fwcon_timer(devinfo, true); return 0; } @@ -2479,6 +3250,9 @@ static const struct dev_pm_ops brcmf_pciedrvr_pm = { BRCM_PCIE_VENDOR_ID_BROADCOM, dev_id,\ subvend, subdev, PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, 0 } +#define BRCMF_PCIE_DEVICE_CY(dev_id) { CY_PCIE_VENDOR_ID_CYPRESS, dev_id,\ + PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, 0 } + static const struct pci_device_id brcmf_pcie_devid_table[] = { BRCMF_PCIE_DEVICE(BRCM_PCIE_4350_DEVICE_ID), BRCMF_PCIE_DEVICE_SUB(0x4355, BRCM_PCIE_VENDOR_ID_BROADCOM, 0x4355), @@ -2505,13 +3279,19 @@ static const struct pci_device_id brcmf_pcie_devid_table[] = { BRCMF_PCIE_DEVICE(BRCM_PCIE_4378_DEVICE_ID), BRCMF_PCIE_DEVICE(CY_PCIE_89459_DEVICE_ID), BRCMF_PCIE_DEVICE(CY_PCIE_89459_RAW_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_54591_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_54590_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_54594_DEVICE_ID), + BRCMF_PCIE_DEVICE_CY(CY_PCIE_55572_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_4373_RAW_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_4373_DUAL_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_4373_2G_DEVICE_ID), + BRCMF_PCIE_DEVICE(CY_PCIE_4373_5G_DEVICE_ID), { /* end: all zeroes */ } }; - MODULE_DEVICE_TABLE(pci, brcmf_pcie_devid_table); - static struct pci_driver brcmf_pciedrvr = { .node = {}, .name = KBUILD_MODNAME, @@ -2524,14 +3304,12 @@ static struct pci_driver brcmf_pciedrvr = { .driver.coredump = brcmf_dev_coredump, }; - int brcmf_pcie_register(void) { brcmf_dbg(PCIE, "Enter\n"); return pci_register_driver(&brcmf_pciedrvr); } - void brcmf_pcie_exit(void) { brcmf_dbg(PCIE, "Enter\n"); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.h index 8e6c227e8315c..8e4f486378944 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.h @@ -11,4 +11,6 @@ struct brcmf_pciedev { struct brcmf_pciedev_info *devinfo; }; +void brcmf_pcie_handle_mb_data(struct brcmf_bus *bus_if, u32 d2h_mb_data); + #endif /* BRCMFMAC_PCIE_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c index e265a2e411a09..12c2b3596628a 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c @@ -3,6 +3,7 @@ * Copyright (c) 2010 Broadcom Corporation */ +#include #include #include #include @@ -23,7 +24,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -35,14 +38,21 @@ #include "core.h" #include "common.h" #include "bcdc.h" +#include "fwil.h" +#include "bt_shared_sdio.h" +#include "trxhdr.h" +#include "feature.h" #define DCMD_RESP_TIMEOUT msecs_to_jiffies(2500) #define CTL_DONE_TIMEOUT msecs_to_jiffies(2500) +#define ULP_HUDI_PROC_DONE_TIME msecs_to_jiffies(2500) /* watermark expressed in number of words */ #define DEFAULT_F2_WATERMARK 0x8 -#define CY_4373_F2_WATERMARK 0x40 -#define CY_4373_F1_MESBUSYCTRL (CY_4373_F2_WATERMARK | SBSDIO_MESBUSYCTRL_ENAB) +#define CY_4373_F2_WATERMARK 0x4C +#define CY_4373_MES_WATERMARK 0x44 +#define CY_4373_MESBUSYCTRL (CY_4373_MES_WATERMARK | \ + SBSDIO_MESBUSYCTRL_ENAB) #define CY_43012_F2_WATERMARK 0x60 #define CY_43012_MES_WATERMARK 0x50 #define CY_43012_MESBUSYCTRL (CY_43012_MES_WATERMARK | \ @@ -58,6 +68,14 @@ #define CY_435X_F2_WATERMARK 0x40 #define CY_435X_F1_MESBUSYCTRL (CY_435X_F2_WATERMARK | \ SBSDIO_MESBUSYCTRL_ENAB) +#define CY_89459_F2_WATERMARK 0x40 +#define CY_89459_MES_WATERMARK 0x40 +#define CY_89459_MESBUSYCTRL (CY_89459_MES_WATERMARK | \ + SBSDIO_MESBUSYCTRL_ENAB) +#define CYW55572_F2_WATERMARK 0x40 +#define CYW55572_MES_WATERMARK 0x40 +#define CYW55572_F1_MESBUSYCTRL (CYW55572_MES_WATERMARK | \ + SBSDIO_MESBUSYCTRL_ENAB) #ifdef DEBUG @@ -135,8 +153,6 @@ struct rte_console { #define BRCMF_FIRSTREAD (1 << 6) -#define BRCMF_CONSOLE 10 /* watchdog interval to poll console */ - /* SBSDIO_DEVICE_CTL */ /* 1: device will assert busy signal when receiving CMD53 */ @@ -158,6 +174,8 @@ struct rte_console { #define SBSDIO_DEVCTL_RST_BPRESET 0x10 /* Force no backplane reset */ #define SBSDIO_DEVCTL_RST_NOBPRESET 0x20 +/* Reset SB Address to default value */ +#define SBSDIO_DEVCTL_ADDR_RESET 0x40 /* direct(mapped) cis space */ @@ -327,7 +345,19 @@ struct rte_console { #define KSO_WAIT_US 50 #define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY/KSO_WAIT_US) -#define BRCMF_SDIO_MAX_ACCESS_ERRORS 5 +#define BRCMF_SDIO_MAX_ACCESS_ERRORS 20 + +static void brcmf_sdio_firmware_callback(struct device *dev, int err, + struct brcmf_fw_request *fwreq); +static struct brcmf_fw_request * + brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus); +static int brcmf_sdio_f2_ready(struct brcmf_sdio *bus); +static int brcmf_ulp_event_notify(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data); +static void +brcmf_sched_rxf(struct brcmf_sdio *bus, struct sk_buff *skb); + #ifdef DEBUG /* Device console log buffer state */ @@ -400,6 +430,11 @@ struct brcmf_sdio_hdrinfo { u16 tail_pad; }; +struct task_ctl { + struct task_struct *p_task; + struct semaphore sema; +}; + /* * hold counter variables */ @@ -526,6 +561,14 @@ struct brcmf_sdio { bool txglom; /* host tx glomming enable flag */ u16 head_align; /* buffer pointer alignment */ u16 sgentry_align; /* scatter-gather buffer alignment */ + struct mutex sdsem; + bool chipid_preset; + #define MAXSKBPEND 1024 + struct sk_buff *skbbuf[MAXSKBPEND]; + u32 store_idx; + u32 sent_idx; + struct task_ctl thr_rxf_ctl; + spinlock_t rxf_lock; /* lock for rxf idx protection */ }; /* clkstate */ @@ -610,23 +653,26 @@ BRCMF_FW_DEF(43241B5, "brcmfmac43241b5-sdio"); BRCMF_FW_DEF(4329, "brcmfmac4329-sdio"); BRCMF_FW_DEF(4330, "brcmfmac4330-sdio"); BRCMF_FW_DEF(4334, "brcmfmac4334-sdio"); -BRCMF_FW_DEF(43340, "brcmfmac43340-sdio"); +CY_FW_DEF(43340, "cyfmac43340-sdio"); BRCMF_FW_DEF(4335, "brcmfmac4335-sdio"); -BRCMF_FW_DEF(43362, "brcmfmac43362-sdio"); -BRCMF_FW_DEF(4339, "brcmfmac4339-sdio"); +CY_FW_DEF(43362, "cyfmac43362-sdio"); +CY_FW_DEF(4339, "cyfmac4339-sdio"); BRCMF_FW_DEF(43430A0, "brcmfmac43430a0-sdio"); /* Note the names are not postfixed with a1 for backward compatibility */ -BRCMF_FW_CLM_DEF(43430A1, "brcmfmac43430-sdio"); +CY_FW_DEF(43430A1, "cyfmac43430-sdio"); BRCMF_FW_DEF(43430B0, "brcmfmac43430b0-sdio"); -BRCMF_FW_CLM_DEF(43439, "brcmfmac43439-sdio"); -BRCMF_FW_CLM_DEF(43455, "brcmfmac43455-sdio"); +CY_FW_DEF(43439, "cyfmac43439-sdio"); +CY_FW_DEF(43455, "cyfmac43455-sdio"); BRCMF_FW_DEF(43456, "brcmfmac43456-sdio"); -BRCMF_FW_CLM_DEF(4354, "brcmfmac4354-sdio"); -BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-sdio"); -BRCMF_FW_DEF(4359, "brcmfmac4359-sdio"); -BRCMF_FW_CLM_DEF(4373, "brcmfmac4373-sdio"); -BRCMF_FW_CLM_DEF(43012, "brcmfmac43012-sdio"); +CY_FW_DEF(4354, "cyfmac4354-sdio"); +CY_FW_DEF(4356, "cyfmac4356-sdio"); +CY_FW_DEF(4359, "cyfmac4359-sdio"); +CY_FW_DEF(4373, "cyfmac4373-sdio"); +CY_FW_DEF(43012, "cyfmac43012-sdio"); BRCMF_FW_CLM_DEF(43752, "brcmfmac43752-sdio"); +CY_FW_DEF(89459, "cyfmac54591-sdio"); +CY_FW_TRXSE_DEF(55500, "cyfmac55500-sdio"); +CY_FW_TRXSE_DEF(55572, "cyfmac55572-sdio"); /* firmware config files */ MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-sdio.*.txt"); @@ -648,8 +694,9 @@ static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43362_CHIP_ID, 0xFFFFFFFE, 43362), BRCMF_FW_ENTRY(BRCM_CC_4339_CHIP_ID, 0xFFFFFFFF, 4339), BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0x00000001, 43430A0), - BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0x00000002, 43430A1), + BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0x0000001E, 43430A1), BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0xFFFFFFFC, 43430B0), + BRCMF_FW_ENTRY(BRCM_CC_43430_CHIP_ID, 0xFFFFFFE0, 43439), BRCMF_FW_ENTRY(BRCM_CC_4345_CHIP_ID, 0x00000200, 43456), BRCMF_FW_ENTRY(BRCM_CC_4345_CHIP_ID, 0xFFFFFDC0, 43455), BRCMF_FW_ENTRY(BRCM_CC_43454_CHIP_ID, 0x00000040, 43455), @@ -659,7 +706,10 @@ static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = { BRCMF_FW_ENTRY(CY_CC_4373_CHIP_ID, 0xFFFFFFFF, 4373), BRCMF_FW_ENTRY(CY_CC_43012_CHIP_ID, 0xFFFFFFFF, 43012), BRCMF_FW_ENTRY(CY_CC_43439_CHIP_ID, 0xFFFFFFFF, 43439), - BRCMF_FW_ENTRY(CY_CC_43752_CHIP_ID, 0xFFFFFFFF, 43752) + BRCMF_FW_ENTRY(CY_CC_43752_CHIP_ID, 0xFFFFFFFF, 43752), + BRCMF_FW_ENTRY(CY_CC_89459_CHIP_ID, 0xFFFFFFFF, 89459), + BRCMF_FW_ENTRY(CY_CC_55500_CHIP_ID, 0xFFFFFFFF, 55500), + BRCMF_FW_ENTRY(CY_CC_55572_CHIP_ID, 0xFFFFFFFF, 55572) }; #define TXCTL_CREDITS 2 @@ -720,7 +770,9 @@ brcmf_sdio_kso_control(struct brcmf_sdio *bus, bool on) * fail. Thereby just bailing out immediately after clearing KSO * bit, to avoid polling of KSO bit. */ - if (!on && bus->ci->chip == CY_CC_43012_CHIP_ID) + if (!on && ((bus->ci->chip == CY_CC_43012_CHIP_ID) || + (bus->ci->chip == CY_CC_55500_CHIP_ID) || + (bus->ci->chip == CY_CC_55572_CHIP_ID))) return err; if (on) { @@ -937,6 +989,20 @@ static int brcmf_sdio_clkctl(struct brcmf_sdio *bus, uint target, bool pendok) break; case CLK_SDONLY: +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + /* If the request is to switch off backplane clock, + * confirm that BT is inactive before doing so. + * If this call had come from Non Watchdog context any way + * the Watchdog would switch off the clock again when + * nothing is to be done & BT has finished using the bus. + */ + if (brcmf_btsdio_bus_count(bus->sdiodev->bus_if)) { + brcmf_dbg(SDIO, "BT is active, not switching off\n"); + brcmf_sdio_wd_timer(bus, true); + break; + } + +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ /* Remove HT request, or bring up SD clock */ if (bus->clkstate == CLK_NONE) brcmf_sdio_sdclk(bus, true); @@ -948,6 +1014,19 @@ static int brcmf_sdio_clkctl(struct brcmf_sdio *bus, uint target, bool pendok) break; case CLK_NONE: +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + /* If the request is to switch off backplane clock, + * confirm that BT is inactive before doing so. + * If this call had come from non-watchdog context any way + * the watchdog would switch off the clock again when + * nothing is to be done & BT has finished using the bus. + */ + if (brcmf_btsdio_bus_count(bus->sdiodev->bus_if)) { + brcmf_dbg(SDIO, "BT is active, not switching off\n"); + break; + } +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ + /* Make sure to remove HT request */ if (bus->clkstate == CLK_AVAIL) brcmf_sdio_htclk(bus, false, false); @@ -972,6 +1051,29 @@ brcmf_sdio_bus_sleep(struct brcmf_sdio *bus, bool sleep, bool pendok) (sleep ? "SLEEP" : "WAKE"), (bus->sleeping ? "SLEEP" : "WAKE")); +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + /* The following is the assumption based on which the hook is placed. + * From WLAN driver, either from the active contexts OR from the + * watchdog contexts, we will be attempting to go to sleep. At that + * moment if we see that BT is still actively using the bus, we will + * return -EBUSY from here, and the bus sleep state would not have + * changed, so the caller can then schedule the watchdog again + * which will come and attempt to sleep at a later point. + * + * In case if BT is the only one and is the last user, we don't switch + * off the clock immediately, we allow the WLAN to decide when to sleep + * i.e from the watchdog. + * Now if the watchdog becomes active and attempts to switch off the + * clock and if another WLAN context is active they are any way + * serialized with sdlock. + */ + if (brcmf_btsdio_bus_count(bus->sdiodev->bus_if)) { + brcmf_dbg(SDIO, "Cannot sleep when BT is active\n"); + err = -EBUSY; + goto done; + } +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ + /* If SR is enabled control bus state with KSO */ if (bus->sr_enabled) { /* Done if we're already in the requested state */ @@ -1106,7 +1208,7 @@ static void brcmf_sdio_get_console_addr(struct brcmf_sdio *bus) } #endif /* DEBUG */ -static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus) +static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus, u32 *hmbd) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; struct brcmf_core *core = bus->sdio_core; @@ -1195,6 +1297,9 @@ static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus) HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) brcmf_err("Unknown mailbox data content: 0x%02x\n", hmb_data); + /* Populate hmb_data if argument is passed for DS1 check later */ + if (hmbd) + *hmbd = hmb_data; return intstatus; } @@ -1512,17 +1617,77 @@ static void brcmf_sdio_hdpack(struct brcmf_sdio *bus, u8 *header, trace_brcmf_sdpcm_hdr(SDPCM_TX + !!(bus->txglom), header); } +static inline int brcmf_rxf_enqueue(struct brcmf_sdio *bus, struct sk_buff *skb) +{ + u32 store_idx; + u32 sent_idx; + + if (!skb) { + brcmf_err("NULL skb!!!\n"); + return -EINVAL; + } + + spin_lock_bh(&bus->rxf_lock); + store_idx = bus->store_idx; + sent_idx = bus->sent_idx; + if (bus->skbbuf[store_idx]) { + /* Make sure the previous packets are processed */ + spin_unlock_bh(&bus->rxf_lock); + brcmf_err("pktbuf not consumed %p, store idx %d sent idx %d\n", + skb, store_idx, sent_idx); + msleep(1000); + return -EINVAL; + } + brcmf_dbg(DATA, "Store SKB %p. idx %d -> %d\n", + skb, store_idx, (store_idx + 1) & (MAXSKBPEND - 1)); + bus->skbbuf[store_idx] = skb; + bus->store_idx = (store_idx + 1) & (MAXSKBPEND - 1); + spin_unlock_bh(&bus->rxf_lock); + + return 0; +} + +static struct sk_buff *brcmf_rxf_dequeue(struct brcmf_sdio *bus) +{ + u32 store_idx; + u32 sent_idx; + struct sk_buff *skb; + + spin_lock_bh(&bus->rxf_lock); + + store_idx = bus->store_idx; + sent_idx = bus->sent_idx; + skb = bus->skbbuf[sent_idx]; + + if (!skb) { + spin_unlock_bh(&bus->rxf_lock); + brcmf_err("Dequeued packet is NULL, store idx %d sent idx %d\n", + store_idx, sent_idx); + return NULL; + } + + bus->skbbuf[sent_idx] = NULL; + bus->sent_idx = (sent_idx + 1) & (MAXSKBPEND - 1); + + brcmf_dbg(DATA, "dequeue (%p), sent idx %d\n", + skb, sent_idx); + + spin_unlock_bh(&bus->rxf_lock); + + return skb; +} + static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) { u16 dlen, totlen; u8 *dptr, num = 0; u16 sublen; struct sk_buff *pfirst, *pnext; - + struct sk_buff *skb_head = NULL, *skb_prev = NULL, *skb_to_rxfq = NULL; int errcode; u8 doff; - struct brcmf_sdio_hdrinfo rd_new; + struct brcmf_mp_device *settings = bus->sdiodev->settings; /* If packets, issue read(s) and send up packet chain */ /* Return sequence numbers consumed? */ @@ -1533,13 +1698,20 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) /* If there's a descriptor, generate the packet chain */ if (bus->glomd) { pfirst = pnext = NULL; - dlen = (u16) (bus->glomd->len); - dptr = bus->glomd->data; - if (!dlen || (dlen & 1)) { - brcmf_err("bad glomd len(%d), ignore descriptor\n", - dlen); + /* it is a u32 len to u16 dlen, should have a sanity check here. */ + if (bus->glomd->len <= 0xFFFF) { + dlen = (u16)(bus->glomd->len); + if (!dlen || (dlen & 1)) { + brcmf_err("bad glomd len(%d), ignore descriptor\n", + dlen); + dlen = 0; + } + } else { + brcmf_err("overflowed glomd len(%d), ignore descriptor\n", + bus->glomd->len); dlen = 0; } + dptr = bus->glomd->data; for (totlen = num = 0; dlen; num++) { /* Get (and move past) next length */ @@ -1605,6 +1777,7 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) /* Ok -- either we just generated a packet chain, or had one from before */ if (!skb_queue_empty(&bus->glom)) { + u32 len_glom = 0; if (BRCMF_GLOM_ON()) { brcmf_dbg(GLOM, "try superframe read, packet chain:\n"); skb_queue_walk(&bus->glom, pnext) { @@ -1615,7 +1788,14 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) } pfirst = skb_peek(&bus->glom); - dlen = (u16) brcmf_sdio_glom_len(bus); + len_glom = brcmf_sdio_glom_len(bus); + if (len_glom > 0xFFFF) { + brcmf_err("glom_len is %d bytes, overflowed\n", + len_glom); + goto frame_error_handle; + } else { + dlen = (u16)len_glom; + } /* Do an SDIO read for the superframe. Configurable iovar to * read directly into the chained packet, or allocate a large @@ -1631,13 +1811,7 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) if (errcode < 0) { brcmf_err("glom read of %d bytes failed: %d\n", dlen, errcode); - - sdio_claim_host(bus->sdiodev->func1); - brcmf_sdio_rxfail(bus, true, false); - bus->sdcnt.rxglomfail++; - brcmf_sdio_free_glom(bus); - sdio_release_host(bus->sdiodev->func1); - return 0; + goto frame_error_handle; } brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), @@ -1674,16 +1848,9 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) num++; } - if (errcode) { - /* Terminate frame on error */ - sdio_claim_host(bus->sdiodev->func1); - brcmf_sdio_rxfail(bus, true, false); - bus->sdcnt.rxglomfail++; - brcmf_sdio_free_glom(bus); - sdio_release_host(bus->sdiodev->func1); - bus->cur_read.len = 0; - return 0; - } + /* Terminate frame on error */ + if (errcode) + goto frame_error_handle; /* Basic SD framing looks ok - process each packet (header) */ @@ -1713,17 +1880,42 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq) pfirst->len, pfirst->next, pfirst->prev); skb_unlink(pfirst, &bus->glom); - if (brcmf_sdio_fromevntchan(&dptr[SDPCM_HWHDR_LEN])) + if (brcmf_sdio_fromevntchan(&dptr[SDPCM_HWHDR_LEN])) { brcmf_rx_event(bus->sdiodev->dev, pfirst); - else - brcmf_rx_frame(bus->sdiodev->dev, pfirst, - false, false); + skb_to_rxfq = NULL; + } else { + skb_to_rxfq = brcmf_rx_frame(bus->sdiodev->dev, pfirst, + false, false); + } + + if (settings && settings->sdio_rxf_in_kthread_enabled && skb_to_rxfq) { + if (!skb_head) + skb_head = skb_to_rxfq; + else + skb_prev->next = skb_to_rxfq; + + skb_prev = skb_to_rxfq; + } bus->sdcnt.rxglompkts++; } bus->sdcnt.rxglomframes++; } + + if (settings && settings->sdio_rxf_in_kthread_enabled && skb_head) + brcmf_sched_rxf(bus, skb_head); + return num; + +frame_error_handle: + sdio_claim_host(bus->sdiodev->func1); + brcmf_sdio_rxfail(bus, true, false); + bus->sdcnt.rxglomfail++; + brcmf_sdio_free_glom(bus); + sdio_release_host(bus->sdiodev->func1); + bus->cur_read.len = 0; + + return 0; } static int brcmf_sdio_dcmd_resp_wait(struct brcmf_sdio *bus, uint *condition, @@ -1863,6 +2055,8 @@ static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) uint rxcount = 0; /* Total frames read */ struct brcmf_sdio_hdrinfo *rd = &bus->cur_read, rd_new; u8 head_read = 0; + struct sk_buff *skb_to_rxfq = NULL, *skb_head = NULL, *skb_prev = NULL; + struct brcmf_mp_device *settings = bus->sdiodev->settings; brcmf_dbg(SDIO, "Enter\n"); @@ -2045,13 +2239,25 @@ static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) __skb_trim(pkt, rd->len); skb_pull(pkt, rd->dat_offset); - if (pkt->len == 0) + if (pkt->len == 0) { brcmu_pkt_buf_free_skb(pkt); - else if (rd->channel == SDPCM_EVENT_CHANNEL) + skb_to_rxfq = NULL; + } else if (rd->channel == SDPCM_EVENT_CHANNEL) { brcmf_rx_event(bus->sdiodev->dev, pkt); - else - brcmf_rx_frame(bus->sdiodev->dev, pkt, - false, false); + skb_to_rxfq = NULL; + } else { + skb_to_rxfq = brcmf_rx_frame(bus->sdiodev->dev, pkt, + false, false); + } + + if (settings && settings->sdio_rxf_in_kthread_enabled && skb_to_rxfq) { + if (!skb_head) + skb_head = skb_to_rxfq; + else + skb_prev->next = skb_to_rxfq; + + skb_prev = skb_to_rxfq; + } /* prepare the descriptor for the next read */ rd->len = rd->len_nxtfrm << 4; @@ -2060,6 +2266,9 @@ static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) rd->channel = SDPCM_EVENT_CHANNEL; } + if (settings && settings->sdio_rxf_in_kthread_enabled && skb_head) + brcmf_sched_rxf(bus, skb_head); + rxcount = maxframes - rxleft; /* Message if we hit the limit */ if (!rxleft) @@ -2077,7 +2286,7 @@ static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) static void brcmf_sdio_wait_event_wakeup(struct brcmf_sdio *bus) { - wake_up_interruptible(&bus->ctrl_wait); + wake_up(&bus->ctrl_wait); return; } @@ -2358,6 +2567,7 @@ static uint brcmf_sdio_sendfromq(struct brcmf_sdio *bus, uint maxframes) &prec_out); if (pkt == NULL) break; + skb_orphan(pkt); __skb_queue_tail(&pktq, pkt); } spin_unlock_bh(&bus->txq_lock); @@ -2464,6 +2674,16 @@ static bool brcmf_chip_is_ulp(struct brcmf_chip *ci) return false; } +static bool brcmf_sdio_use_ht_avail(struct brcmf_chip *ci) +{ + if (ci->chip == CY_CC_4373_CHIP_ID || + ci->chip == CY_CC_55500_CHIP_ID || + ci->chip == CY_CC_55572_CHIP_ID) + return true; + else + return false; +} + static void brcmf_sdio_bus_stop(struct device *dev) { struct brcmf_bus *bus_if = dev_get_drvdata(dev); @@ -2482,6 +2702,12 @@ static void brcmf_sdio_bus_stop(struct device *dev) bus->watchdog_tsk = NULL; } + if (bus->thr_rxf_ctl.p_task) { + send_sig(SIGTERM, bus->thr_rxf_ctl.p_task, 1); + kthread_stop(bus->thr_rxf_ctl.p_task); + bus->thr_rxf_ctl.p_task = NULL; + } + if (sdiodev->state != BRCMF_SDIOD_NOMEDIUM) { sdio_claim_host(sdiodev->func1); @@ -2500,7 +2726,8 @@ static void brcmf_sdio_bus_stop(struct device *dev) &err); if (!err) { bpreq = saveclk; - bpreq |= brcmf_chip_is_ulp(bus->ci) ? + bpreq |= (brcmf_sdio_use_ht_avail(bus->ci) || + brcmf_chip_is_ulp(bus->ci)) ? SBSDIO_HT_AVAIL_REQ : SBSDIO_FORCE_HT; brcmf_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, @@ -2581,6 +2808,182 @@ static int brcmf_sdio_intr_rstatus(struct brcmf_sdio *bus) return ret; } +/* This Function is used to retrieve important + * details from dongle related to ULP mode Mostly + * values/SHM details that will be vary depending + * on the firmware branches + */ +static void +brcmf_sdio_ulp_preinit(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_if *ifp = bus_if->drvr->iflist[0]; + + brcmf_dbg(ULP, "Enter\n"); + + /* Query ulp_sdioctrl iovar to get the ULP related SHM offsets */ + brcmf_fil_iovar_data_get(ifp, "ulp_sdioctrl", + &sdiodev->fmac_ulp.ulp_shm_offset, + sizeof(sdiodev->fmac_ulp.ulp_shm_offset)); + + sdiodev->ulp = false; + + brcmf_dbg(ULP, "m_ulp_ctrl_sdio[%x] m_ulp_wakeevt_ind [%x]\n", + M_DS1_CTRL_SDIO(sdiodev->fmac_ulp), + M_WAKEEVENT_IND(sdiodev->fmac_ulp)); + brcmf_dbg(ULP, "m_ulp_wakeind [%x]\n", + M_ULP_WAKE_IND(sdiodev->fmac_ulp)); +} + +/* Reinitialize ARM because In DS1 mode ARM got off */ +static int +brcmf_sdio_ulp_reinit_fw(struct brcmf_sdio *bus) +{ + struct brcmf_sdio_dev *sdiodev = bus->sdiodev; + struct brcmf_fw_request *fwreq; + int err = 0; + + /* After firmware redownload tx/rx seq are reset accordingly + * these values are reset on FMAC side tx_max is initially set to 4, + * which later is updated by FW. + */ + bus->tx_seq = 0; + bus->rx_seq = 0; + bus->tx_max = 4; + + fwreq = brcmf_sdio_prepare_fw_request(bus); + if (!fwreq) + return -ENOMEM; + + err = brcmf_fw_get_firmwares(sdiodev->dev, fwreq, + brcmf_sdio_firmware_callback); + if (err != 0) { + brcmf_err("async firmware request failed: %d\n", err); + kfree(fwreq); + } + + return err; +} + +/* Check if device is in DS1 mode and handshake with ULP UCODE */ +static bool +brcmf_sdio_ulp_pre_redownload_check(struct brcmf_sdio *bus, u32 hmb_data) +{ + struct brcmf_sdio_dev *sdiod = bus->sdiodev; + int err = 0; + u32 value = 0; + u32 val32, ulp_wake_ind, wowl_wake_ind; + int reg_addr; + unsigned long timeout; + struct brcmf_ulp *fmac_ulp = &bus->sdiodev->fmac_ulp; + int i = 0; + + /* If any host mail box data is present, ignore DS1 exit sequence */ + if (hmb_data) + return false; + /* Skip if DS1 Exit is already in progress + * This can happen if firmware download is taking more time + */ + if (fmac_ulp->ulp_state == FMAC_ULP_TRIGGERED) + return false; + + value = brcmf_sdiod_func0_rb(sdiod, SDIO_CCCR_IOEx, &err); + + if (value == SDIO_FUNC_ENABLE_1) { + brcmf_dbg(ULP, "GOT THE INTERRUPT FROM UCODE\n"); + sdiod->ulp = true; + fmac_ulp->ulp_state = FMAC_ULP_TRIGGERED; + ulp_wake_ind = D11SHM_RDW(sdiod, + M_ULP_WAKE_IND(sdiod->fmac_ulp), + &err); + wowl_wake_ind = D11SHM_RDW(sdiod, + M_WAKEEVENT_IND(sdiod->fmac_ulp), + &err); + + brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x state %s\n", + wowl_wake_ind, ulp_wake_ind, (fmac_ulp->ulp_state) ? + "DS1 Exit Triggered" : "IDLE State"); + + if (wowl_wake_ind || ulp_wake_ind) { + /* RX wake Don't do anything. + * Just bail out and re-download firmware. + */ + /* Print out PHY TX error block when bit 9 set */ + if ((ulp_wake_ind & C_DS1_PHY_TXERR) && + M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp)) { + brcmf_err("Dump PHY TX Error SHM Locations\n"); + for (i = 0; i < PHYTX_ERR_BLK_SIZE; i++) { + pr_err("0x%x", + D11SHM_RDW(sdiod, + (M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp) + + (i * 2)), &err)); + } + brcmf_err("\n"); + } + } else { + /* TX wake negotiate with MAC */ + brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n", + (u32)D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err)); + val32 = D11SHM_RD(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + D11SHM_WR(sdiod, M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + val32, (C_DS1_CTRL_SDIO_DS1_EXIT | + C_DS1_CTRL_REQ_VALID), &err); + val32 = D11REG_RD(sdiod, D11_MACCONTROL_REG, &err); + val32 = val32 | D11_MACCONTROL_REG_WAKE; + D11REG_WR(sdiod, D11_MACCONTROL_REG, val32, &err); + + /* Poll for PROC_DONE to be set by ucode */ + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + /* Wait here (polling) for C_DS1_CTRL_PROC_DONE */ + timeout = jiffies + ULP_HUDI_PROC_DONE_TIME; + while (!(value & C_DS1_CTRL_PROC_DONE)) { + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + if (time_after(jiffies, timeout)) + break; + usleep_range(1000, 2000); + } + brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n", + (u32)D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), &err)); + value = D11SHM_RDW(sdiod, + M_DS1_CTRL_SDIO(sdiod->fmac_ulp), + &err); + if (!(value & C_DS1_CTRL_PROC_DONE)) { + brcmf_err("Timeout Failed to enter DS1 Exit state!\n"); + return false; + } + } + + ulp_wake_ind = D11SHM_RDW(sdiod, + M_ULP_WAKE_IND(sdiod->fmac_ulp), + &err); + wowl_wake_ind = D11SHM_RDW(sdiod, + M_WAKEEVENT_IND(sdiod->fmac_ulp), + &err); + brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x\n", + wowl_wake_ind, ulp_wake_ind); + reg_addr = CORE_CC_REG( + brcmf_chip_get_pmu(bus->ci)->base, min_res_mask); + brcmf_sdiod_writel(sdiod, reg_addr, + DEFAULT_43012_MIN_RES_MASK, &err); + if (err) + brcmf_err("min_res_mask failed\n"); + + return true; + } + + return false; +} + static void brcmf_sdio_dpc(struct brcmf_sdio *bus) { struct brcmf_sdio_dev *sdiod = bus->sdiodev; @@ -2648,12 +3051,18 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus) atomic_set(&bus->fcstate, !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE))); intstatus |= (newstatus & bus->hostintmask); +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + brcmf_btsdio_int_handler(bus->sdiodev->bus_if); +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ } /* Handle host mailbox indication */ if (intstatus & I_HMB_HOST_INT) { + u32 hmb_data = 0; intstatus &= ~I_HMB_HOST_INT; - intstatus |= brcmf_sdio_hostmail(bus); + intstatus |= brcmf_sdio_hostmail(bus, &hmb_data); + if (brcmf_sdio_ulp_pre_redownload_check(bus, hmb_data)) + brcmf_sdio_ulp_reinit_fw(bus); } sdio_release_host(bus->sdiodev->func1); @@ -2698,7 +3107,7 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus) brcmf_sdio_clrintr(bus); if (bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL) && - txctl_ok(bus)) { + txctl_ok(bus) && brcmf_sdio_f2_ready(bus)) { sdio_claim_host(bus->sdiodev->func1); if (bus->ctrl_frame_stat) { err = brcmf_sdio_tx_ctrlframe(bus, bus->ctrl_frame_buf, @@ -2824,6 +3233,8 @@ static int brcmf_sdio_bus_txdata(struct device *dev, struct sk_buff *pkt) brcmf_dbg(TRACE, "deferring pktq len %d\n", pktq_len(&bus->txq)); bus->sdcnt.fcqueued++; + skb_tx_timestamp(pkt); + /* Priority based enq */ spin_lock_bh(&bus->txq_lock); /* reset bus_flags in packet cb */ @@ -2950,8 +3361,9 @@ brcmf_sdio_bus_txctl(struct device *dev, unsigned char *msg, uint msglen) bus->ctrl_frame_stat = true; brcmf_sdio_trigger_dpc(bus); - wait_event_interruptible_timeout(bus->ctrl_wait, !bus->ctrl_frame_stat, - CTL_DONE_TIMEOUT); + wait_event_timeout(bus->ctrl_wait, !bus->ctrl_frame_stat, + CTL_DONE_TIMEOUT); + ret = 0; if (bus->ctrl_frame_stat) { sdio_claim_host(bus->sdiodev->func1); @@ -3348,17 +3760,26 @@ brcmf_sdio_verifymemory(struct brcmf_sdio_dev *sdiodev, u32 ram_addr, static int brcmf_sdio_download_code_file(struct brcmf_sdio *bus, const struct firmware *fw) { + struct trx_header_le *trx = (struct trx_header_le *)fw->data; + u32 fw_size; + u32 address; int err; brcmf_dbg(TRACE, "Enter\n"); - err = brcmf_sdiod_ramrw(bus->sdiodev, true, bus->ci->rambase, - (u8 *)fw->data, fw->size); + address = bus->ci->rambase; + fw_size = fw->size; + if (trx->magic == cpu_to_le32(TRX_MAGIC)) { + address -= sizeof(struct trx_header_le); + fw_size = le32_to_cpu(trx->len); + } + err = brcmf_sdiod_ramrw(bus->sdiodev, true, address, + (u8 *)fw->data, fw_size); if (err) brcmf_err("error %d on writing %d membytes at 0x%08x\n", - err, (int)fw->size, bus->ci->rambase); - else if (!brcmf_sdio_verifymemory(bus->sdiodev, bus->ci->rambase, - (u8 *)fw->data, fw->size)) + err, (int)fw_size, address); + else if (!brcmf_sdio_verifymemory(bus->sdiodev, address, + (u8 *)fw->data, fw_size)) err = -EIO; return err; @@ -3396,6 +3817,16 @@ static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus, rstvec = get_unaligned_le32(fw->data); brcmf_dbg(SDIO, "firmware rstvec: %x\n", rstvec); + if (bus->ci->blhs) { + bcmerror = bus->ci->blhs->prep_fwdl(bus->ci); + if (bcmerror) { + brcmf_err("FW download preparation failed\n"); + release_firmware(fw); + brcmf_fw_nvram_free(nvram); + goto err; + } + } + bcmerror = brcmf_sdio_download_code_file(bus, fw); release_firmware(fw); if (bcmerror) { @@ -3404,6 +3835,22 @@ static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus, goto err; } + if (bus->ci->blhs) { + bcmerror = bus->ci->blhs->post_fwdl(bus->ci); + if (bcmerror) { + brcmf_err("FW download failed, err=%d\n", bcmerror); + brcmf_fw_nvram_free(nvram); + goto err; + } + + bcmerror = bus->ci->blhs->chk_validation(bus->ci); + if (bcmerror) { + brcmf_err("FW valication failed, err=%d\n", bcmerror); + brcmf_fw_nvram_free(nvram); + goto err; + } + } + bcmerror = brcmf_sdio_download_nvram(bus, nvram, nvlen); brcmf_fw_nvram_free(nvram); if (bcmerror) { @@ -3411,11 +3858,14 @@ static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus, goto err; } - /* Take arm out of reset */ - if (!brcmf_chip_set_active(bus->ci, rstvec)) { - brcmf_err("error getting out of ARM core reset\n"); - bcmerror = -EIO; - goto err; + if (bus->ci->blhs) { + bus->ci->blhs->post_nvramdl(bus->ci); + } else { + /* Take arm out of reset */ + if (!brcmf_chip_set_active(bus->ci, rstvec)) { + brcmf_err("error getting out of ARM core reset\n"); + goto err; + } } err: @@ -3427,7 +3877,14 @@ static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus, static bool brcmf_sdio_aos_no_decode(struct brcmf_sdio *bus) { if (bus->ci->chip == CY_CC_43012_CHIP_ID || - bus->ci->chip == CY_CC_43752_CHIP_ID) + bus->ci->chip == CY_CC_43752_CHIP_ID || + bus->ci->chip == CY_CC_4373_CHIP_ID || + bus->ci->chip == CY_CC_55500_CHIP_ID || + bus->ci->chip == CY_CC_55572_CHIP_ID || + bus->ci->chip == BRCM_CC_4339_CHIP_ID || + bus->ci->chip == BRCM_CC_4345_CHIP_ID || + bus->ci->chip == BRCM_CC_4354_CHIP_ID || + bus->ci->chip == BRCM_CC_4356_CHIP_ID) return true; else return false; @@ -3443,7 +3900,8 @@ static void brcmf_sdio_sr_init(struct brcmf_sdio *bus) brcmf_dbg(TRACE, "Enter\n"); - if (brcmf_chip_is_ulp(bus->ci)) { + if (brcmf_sdio_use_ht_avail(bus->ci) || + brcmf_chip_is_ulp(bus->ci)) { wakeupctrl = SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT; chipclkcsr = SBSDIO_HT_AVAIL_REQ; } else { @@ -3568,6 +4026,10 @@ static int brcmf_sdio_bus_preinit(struct device *dev) if (err < 0) goto done; + /* initialize SHM address from firmware for DS1 */ + if (!bus->sdiodev->ulp) + brcmf_sdio_ulp_preinit(dev); + bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN; if (sdiodev->sg_support) { bus->txglom = false; @@ -3635,10 +4097,8 @@ static int brcmf_sdio_bus_get_memdump(struct device *dev, void *data, void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus) { - if (!bus->dpc_triggered) { - bus->dpc_triggered = true; - queue_work(bus->brcmf_wq, &bus->datawork); - } + bus->dpc_triggered = true; + queue_work(bus->brcmf_wq, &bus->datawork); } void brcmf_sdio_isr(struct brcmf_sdio *bus, bool in_isr) @@ -3663,8 +4123,26 @@ void brcmf_sdio_isr(struct brcmf_sdio *bus, bool in_isr) if (!bus->intr) brcmf_err("isr w/o interrupt configured!\n"); - bus->dpc_triggered = true; - queue_work(bus->brcmf_wq, &bus->datawork); + if (bus->sdiodev->settings->sdio_in_isr) { + if (!mutex_trylock(&bus->sdsem)) { + bus->dpc_triggered = true; + queue_work(bus->brcmf_wq, &bus->datawork); + } else { + bus->dpc_triggered = true; + + /* make sure dpc_triggered is true */ + wmb(); + while (READ_ONCE(bus->dpc_triggered)) { + bus->dpc_triggered = false; + brcmf_sdio_dpc(bus); + bus->idlecount = 0; + } + mutex_unlock(&bus->sdsem); + } + } else { + bus->dpc_triggered = true; + queue_work(bus->brcmf_wq, &bus->datawork); + } } static void brcmf_sdio_bus_watchdog(struct brcmf_sdio *bus) @@ -3727,7 +4205,8 @@ static void brcmf_sdio_bus_watchdog(struct brcmf_sdio *bus) #endif /* DEBUG */ /* On idle timeout clear activity flag and/or turn off clock */ - if (!bus->dpc_triggered) { + if (!bus->dpc_triggered && + brcmf_btsdio_bus_count(bus->sdiodev->bus_if) == 0) { rmb(); if ((!bus->dpc_running) && (bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) { @@ -3757,19 +4236,43 @@ static void brcmf_sdio_dataworker(struct work_struct *work) struct brcmf_sdio *bus = container_of(work, struct brcmf_sdio, datawork); - bus->dpc_running = true; - wmb(); - while (READ_ONCE(bus->dpc_triggered)) { - bus->dpc_triggered = false; - brcmf_sdio_dpc(bus); - bus->idlecount = 0; - } - bus->dpc_running = false; - if (brcmf_sdiod_freezing(bus->sdiodev)) { - brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DOWN); - brcmf_sdiod_try_freeze(bus->sdiodev); - brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DATA); + if (bus->sdiodev->settings->sdio_in_isr) { + if (mutex_trylock(&bus->sdsem)) { + bus->dpc_running = true; + + /* make sure dpc_running is true */ + wmb(); + while (READ_ONCE(bus->dpc_triggered)) { + bus->dpc_triggered = false; + brcmf_sdio_dpc(bus); + bus->idlecount = 0; + } + mutex_unlock(&bus->sdsem); + bus->dpc_running = false; + if (brcmf_sdiod_freezing(bus->sdiodev)) { + brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DOWN); + brcmf_sdiod_try_freeze(bus->sdiodev); + brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DATA); + } + } + } else { + bus->dpc_running = true; + + /* make sure dpc_running is true */ + wmb(); + while (READ_ONCE(bus->dpc_triggered)) { + bus->dpc_triggered = false; + brcmf_sdio_dpc(bus); + bus->idlecount = 0; + } + bus->dpc_running = false; + if (brcmf_sdiod_freezing(bus->sdiodev)) { + brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DOWN); + brcmf_sdiod_try_freeze(bus->sdiodev); + brcmf_sdiod_change_state(bus->sdiodev, BRCMF_SDIOD_DATA); + } } + return; } static void @@ -3842,6 +4345,47 @@ brcmf_sdio_drivestrengthinit(struct brcmf_sdio_dev *sdiodev, } } +static u32 +brcmf_sdio_ccsec_get_buscorebase(struct brcmf_sdio_dev *sdiodev) +{ + u8 devctl = 0; + u32 addr = 0; + int err = 0; + + devctl = brcmf_sdiod_readb(sdiodev, SBSDIO_DEVICE_CTL, NULL); + brcmf_sdiod_writeb(sdiodev, SBSDIO_DEVICE_CTL, devctl | SBSDIO_DEVCTL_ADDR_RESET, &err); + if (err) + goto exit; + + addr |= (brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRLOW, NULL) << 8) | + (brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRMID, NULL) << 16) | + (brcmf_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRHIGH, NULL) << 24); + + brcmf_dbg(INFO, "sdiod core address is 0x%x\n", addr); +exit: + if (err) { + brcmf_err("Get SDIO core base address failed, err=%d", err); + addr = 0; + } + brcmf_sdiod_writeb(sdiodev, SBSDIO_DEVICE_CTL, devctl, &err); + + return addr; +} + +static u32 brcmf_sdio_buscore_blhs_read(void *ctx, u32 reg_offset) +{ + struct brcmf_sdio_dev *sdiodev = (struct brcmf_sdio_dev *)ctx; + + return (u32)brcmf_sdiod_readb(sdiodev, reg_offset, NULL); +} + +static void brcmf_sdio_buscore_blhs_write(void *ctx, u32 reg_offset, u32 value) +{ + struct brcmf_sdio_dev *sdiodev = (struct brcmf_sdio_dev *)ctx; + + brcmf_sdiod_writeb(sdiodev, reg_offset, (u8)value, NULL); +} + static int brcmf_sdio_buscoreprep(void *ctx) { struct brcmf_sdio_dev *sdiodev = ctx; @@ -3938,11 +4482,68 @@ static void brcmf_sdio_buscore_write32(void *ctx, u32 addr, u32 val) brcmf_sdiod_writel(sdiodev, addr, val, NULL); } +static int +brcmf_sdio_buscore_sec_attach(void *ctx, struct brcmf_blhs **blhs, struct brcmf_ccsec **ccsec, + u32 flag, uint timeout, uint interval) +{ + struct brcmf_sdio_dev *sdiodev = (struct brcmf_sdio_dev *)ctx; + struct brcmf_blhs *blhsh = NULL; + struct brcmf_ccsec *ccsech = NULL; + u32 reg_addr; + u32 regdata; + u8 cardcap; + + if (sdiodev->func1->vendor != SDIO_VENDOR_ID_CYPRESS) + return 0; + + cardcap = brcmf_sdiod_func0_rb(sdiodev, SDIO_CCCR_BRCM_CARDCAP, NULL); + if (cardcap & SDIO_CCCR_BRCM_CARDCAP_SECURE_MODE) { + blhsh = kzalloc(sizeof(*blhsh), GFP_KERNEL); + if (!blhsh) + return -ENOMEM; + + blhsh->d2h = BRCMF_SDIO_REG_DAR_D2H_MSG_0; + blhsh->h2d = BRCMF_SDIO_REG_DAR_H2D_MSG_0; + blhsh->read = brcmf_sdio_buscore_blhs_read; + blhsh->write = brcmf_sdio_buscore_blhs_write; + + blhsh->write(ctx, blhsh->h2d, 0); + + SPINWAIT_MS((blhsh->read(ctx, blhsh->d2h) & flag) == 0, + timeout, interval); + + regdata = blhsh->read(ctx, blhsh->d2h); + if (!(regdata & flag)) { + brcmf_err("Timeout waiting for bootloader ready\n"); + kfree(blhsh); + return -EPERM; + } + *blhs = blhsh; + } + + if (cardcap & SDIO_CCCR_BRCM_CARDCAP_CHIPID_PRESENT) { + ccsech = kzalloc(sizeof(*ccsech), GFP_KERNEL); + if (!ccsech) { + kfree(blhsh); + return -ENOMEM; + } + ccsech->bus_corebase = brcmf_sdio_ccsec_get_buscorebase(sdiodev); + reg_addr = ccsech->bus_corebase + SD_REG(eromptr); + ccsech->erombase = brcmf_sdio_buscore_read32(ctx, reg_addr); + reg_addr = ccsech->bus_corebase + SD_REG(chipid); + ccsech->chipid = brcmf_sdio_buscore_read32(ctx, reg_addr); + *ccsec = ccsech; + } + + return 0; +} + static const struct brcmf_buscore_ops brcmf_sdio_buscore_ops = { .prepare = brcmf_sdio_buscoreprep, .activate = brcmf_sdio_buscore_activate, .read32 = brcmf_sdio_buscore_read32, .write32 = brcmf_sdio_buscore_write32, + .sec_attach = brcmf_sdio_buscore_sec_attach, }; static bool @@ -3961,9 +4562,6 @@ brcmf_sdio_probe_attach(struct brcmf_sdio *bus) enum_base = brcmf_chip_enum_base(sdiodev->func1->device); - pr_debug("F1 signature read @0x%08x=0x%4x\n", enum_base, - brcmf_sdiod_readl(sdiodev, enum_base, NULL)); - /* * Force PLL off until brcmf_chip_attach() * programs PLL control regs @@ -3989,6 +4587,10 @@ brcmf_sdio_probe_attach(struct brcmf_sdio *bus) goto fail; } + if (!bus->ci->ccsec) + pr_debug("F1 signature read @0x18000000=0x%4x\n", + brcmf_sdiod_readl(sdiodev, enum_base, NULL)); + /* Pick up the SDIO core info struct from chip.c */ bus->sdio_core = brcmf_chip_get_core(bus->ci, BCMA_CORE_SDIO_DEV); if (!bus->sdio_core) @@ -4054,17 +4656,21 @@ brcmf_sdio_probe_attach(struct brcmf_sdio *bus) if (err) goto fail; - /* set PMUControl so a backplane reset does PMU state reload */ - reg_addr = CORE_CC_REG(brcmf_chip_get_pmu(bus->ci)->base, pmucontrol); - reg_val = brcmf_sdiod_readl(sdiodev, reg_addr, &err); - if (err) - goto fail; + if (!bus->ci->blhs) { + /* set PMUControl so a backplane reset does PMU state reload */ + reg_addr = CORE_CC_REG(brcmf_chip_get_pmu(bus->ci)->base, + pmucontrol); + reg_val = brcmf_sdiod_readl(sdiodev, reg_addr, &err); + if (err) + goto fail; - reg_val |= (BCMA_CC_PMU_CTL_RES_RELOAD << BCMA_CC_PMU_CTL_RES_SHIFT); + reg_val |= (BCMA_CC_PMU_CTL_RES_RELOAD << + BCMA_CC_PMU_CTL_RES_SHIFT); - brcmf_sdiod_writel(sdiodev, reg_addr, reg_val, &err); - if (err) - goto fail; + brcmf_sdiod_writel(sdiodev, reg_addr, reg_val, &err); + if (err) + goto fail; + } sdio_release_host(sdiodev->func1); @@ -4091,6 +4697,80 @@ brcmf_sdio_probe_attach(struct brcmf_sdio *bus) return false; } +static void +brcmf_sched_rxf(struct brcmf_sdio *bus, struct sk_buff *skb) +{ + brcmf_dbg(SDIO, "Enter\n"); + do { + if (!brcmf_rxf_enqueue(bus, skb)) { + break; + } else { + brcmf_err("brcmf_rxf_enqueue failed\n"); + goto done; + } + } while (1); + + if (bus->thr_rxf_ctl.p_task) + up(&bus->thr_rxf_ctl.sema); + +done: + return; +} + +static int +brcmf_sdio_rxf_thread(void *data) +{ + struct brcmf_sdio *bus = (struct brcmf_sdio *)data; + struct sched_param param; + + allow_signal(SIGTERM); + /* This thread doesn't need any user-level access, + * so get rid of all our resources + */ + memset(¶m, 0, sizeof(struct sched_param)); + param.sched_priority = 1; + if (param.sched_priority >= MAX_RT_PRIO / 2) { + /* If the priority is MAX_RT_PRIO/2 or higher, + * it is considered as high priority. + * sched_priority of FIFO task dosen't + * exceed MAX_RT_PRIO/2. + */ + sched_set_fifo(current); + } else { + /* For when you don't much care about FIFO, + * but want to be above SCHED_NORMAL. + */ + sched_set_fifo_low(current); + } + + while (1) { + if (kthread_should_stop()) + break; + + if (down_interruptible(&bus->thr_rxf_ctl.sema) == 0) { + struct sk_buff *skb = NULL; + + smp_mb();/* ensure skb null */ + skb = brcmf_rxf_dequeue(bus); + if (!skb) { + brcmf_err("nothing is dequeued, thread terminate\n"); + break; + } + + while (skb) { + struct sk_buff *skbnext = skb->next; + + skb->next = NULL; + netif_rx(skb); + skb = skbnext; + } + } else { + break; + } + } + return 0; +} + static int brcmf_sdio_watchdog_thread(void *data) { @@ -4132,25 +4812,23 @@ brcmf_sdio_watchdog(struct timer_list *t) } } -static int brcmf_sdio_get_blob(struct device *dev, const struct firmware **fw, - enum brcmf_blob_type type) +static +int brcmf_sdio_get_fwname(struct device *dev, const char *ext, u8 *fw_name) { - struct brcmf_bus *bus_if = dev_get_drvdata(dev); - struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; - - switch (type) { - case BRCMF_BLOB_CLM: - *fw = sdiodev->clm_fw; - sdiodev->clm_fw = NULL; - break; - default: - return -ENOENT; - } - - if (!*fw) - return -ENOENT; - - return 0; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_fw_request *fwreq; + struct brcmf_fw_name fwnames[] = { + { ext, fw_name }, + }; + fwreq = brcmf_fw_alloc_request(bus_if->chip, bus_if->chiprev, + brcmf_sdio_fwnames, + ARRAY_SIZE(brcmf_sdio_fwnames), + fwnames, ARRAY_SIZE(fwnames)); + if (!fwreq) + return -ENOMEM; + + kfree(fwreq); + return 0; } static int brcmf_sdio_bus_reset(struct device *dev) @@ -4184,14 +4862,13 @@ static const struct brcmf_bus_ops brcmf_sdio_bus_ops = { .wowl_config = brcmf_sdio_wowl_config, .get_ramsize = brcmf_sdio_bus_get_ramsize, .get_memdump = brcmf_sdio_bus_get_memdump, - .get_blob = brcmf_sdio_get_blob, + .get_fwname = brcmf_sdio_get_fwname, .debugfs_create = brcmf_sdio_debugfs_create, .reset = brcmf_sdio_bus_reset }; #define BRCMF_SDIO_FW_CODE 0 #define BRCMF_SDIO_FW_NVRAM 1 -#define BRCMF_SDIO_FW_CLM 2 static void brcmf_sdio_firmware_callback(struct device *dev, int err, struct brcmf_fw_request *fwreq) @@ -4206,7 +4883,7 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, u8 saveclk, bpreq; u8 devctl; - brcmf_dbg(TRACE, "Enter: dev=%s, err=%d\n", dev_name(dev), err); + brcmf_dbg(ULP, "Enter: dev=%s, err=%d\n", dev_name(dev), err); if (err) goto fail; @@ -4214,7 +4891,6 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, code = fwreq->items[BRCMF_SDIO_FW_CODE].binary; nvram = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.data; nvram_len = fwreq->items[BRCMF_SDIO_FW_NVRAM].nv_data.len; - sdiod->clm_fw = fwreq->items[BRCMF_SDIO_FW_CLM].binary; kfree(fwreq); /* try to download image and nvram to the dongle */ @@ -4224,10 +4900,6 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, goto fail; bus->alp_only = false; - /* Start the watchdog timer */ - bus->sdcnt.tickcnt = 0; - brcmf_sdio_wd_timer(bus, true); - sdio_claim_host(sdiod->func1); /* Make sure backplane clock is on, needed to generate F2 interrupt */ @@ -4239,7 +4911,8 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, saveclk = brcmf_sdiod_readb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, &err); if (!err) { bpreq = saveclk; - bpreq |= brcmf_chip_is_ulp(bus->ci) ? + bpreq |= (brcmf_sdio_use_ht_avail(bus->ci) || + brcmf_chip_is_ulp(bus->ci)) ? SBSDIO_HT_AVAIL_REQ : SBSDIO_FORCE_HT; brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, bpreq, &err); @@ -4277,7 +4950,7 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, &err); brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, - CY_4373_F1_MESBUSYCTRL, &err); + CY_4373_MESBUSYCTRL, &err); break; case SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012: brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", @@ -4334,6 +5007,36 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, CY_435X_F1_MESBUSYCTRL, &err); break; + case SDIO_DEVICE_ID_BROADCOM_CYPRESS_89459: + case SDIO_DEVICE_ID_CYPRESS_54590: + case SDIO_DEVICE_ID_CYPRESS_54591: + case SDIO_DEVICE_ID_CYPRESS_54594: + brcmf_dbg(INFO, "set F2/MES watermark to 0x%x*4 / 0x%x bytes for 89459\n", + CY_89459_F2_WATERMARK, CY_89459_MESBUSYCTRL); + brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, + CY_89459_F2_WATERMARK, &err); + devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, + &err); + devctl |= SBSDIO_DEVCTL_F2WM_ENAB; + brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, + &err); + brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, + CY_89459_MESBUSYCTRL, &err); + break; + case SDIO_DEVICE_ID_CYPRESS_55572: + case SDIO_DEVICE_ID_CYPRESS_55500: + brcmf_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n", + CYW55572_F2_WATERMARK); + brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, + CYW55572_F2_WATERMARK, &err); + devctl = brcmf_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL, + &err); + devctl |= SBSDIO_DEVCTL_F2WM_ENAB; + brcmf_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl, + &err); + brcmf_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL, + CYW55572_F1_MESBUSYCTRL, &err); + break; default: brcmf_sdiod_writeb(sdiod, SBSDIO_WATERMARK, DEFAULT_F2_WATERMARK, &err); @@ -4374,6 +5077,9 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, goto checkdied; } + /* Start the watchdog timer */ + bus->sdcnt.tickcnt = 0; + brcmf_sdio_wd_timer(bus, true); sdio_release_host(sdiod->func1); err = brcmf_alloc(sdiod->dev, sdiod->settings); @@ -4382,13 +5088,34 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err, goto claim; } +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + err = brcmf_btsdio_init(bus_if); + if (err) { + brcmf_err("brcmf_btsdio_init failed\n"); + goto free; + } +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ + /* Attach to the common layer, reserve hdr space */ - err = brcmf_attach(sdiod->dev); + err = brcmf_attach(sdiod->dev, !bus->sdiodev->ulp); if (err != 0) { brcmf_err("brcmf_attach failed\n"); goto free; } + /* Register for ULP events */ + if (sdiod->func1->device == SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012) + brcmf_fweh_register(bus_if->drvr, BRCMF_E_ULP, + brcmf_ulp_event_notify); + + if (bus->sdiodev->ulp) { + /* For ULP, after firmware redownload complete + * set ULP state to IDLE + */ + if (bus->sdiodev->fmac_ulp.ulp_state == FMAC_ULP_TRIGGERED) + bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_IDLE; + } + /* ready */ return; @@ -4413,9 +5140,11 @@ brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus) struct brcmf_fw_name fwnames[] = { { ".bin", bus->sdiodev->fw_name }, { ".txt", bus->sdiodev->nvram_name }, - { ".clm_blob", bus->sdiodev->clm_name }, }; + if (bus->ci->blhs) + fwnames[BRCMF_SDIO_FW_CODE].extension = ".trxse"; + fwreq = brcmf_fw_alloc_request(bus->ci->chip, bus->ci->chiprev, brcmf_sdio_fwnames, ARRAY_SIZE(brcmf_sdio_fwnames), @@ -4423,10 +5152,11 @@ brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus) if (!fwreq) return NULL; - fwreq->items[BRCMF_SDIO_FW_CODE].type = BRCMF_FW_TYPE_BINARY; + if (bus->ci->blhs) + fwreq->items[BRCMF_SDIO_FW_CODE].type = BRCMF_FW_TYPE_TRXSE; + else + fwreq->items[BRCMF_SDIO_FW_CODE].type = BRCMF_FW_TYPE_BINARY; fwreq->items[BRCMF_SDIO_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM; - fwreq->items[BRCMF_SDIO_FW_CLM].type = BRCMF_FW_TYPE_BINARY; - fwreq->items[BRCMF_SDIO_FW_CLM].flags = BRCMF_FW_REQF_OPTIONAL; fwreq->board_types[0] = bus->sdiodev->settings->board_type; return fwreq; @@ -4475,6 +5205,22 @@ struct brcmf_sdio *brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) spin_lock_init(&bus->txq_lock); init_waitqueue_head(&bus->ctrl_wait); init_waitqueue_head(&bus->dcmd_resp_wait); + /* Initialize thread based operation and lock */ + mutex_init(&bus->sdsem); + + /* too early to have drvr */ + if (sdiodev->settings->sdio_rxf_in_kthread_enabled) { + memset(&bus->skbbuf[0], 0, sizeof(void *) * MAXSKBPEND); + sema_init(&bus->thr_rxf_ctl.sema, 0); + spin_lock_init(&bus->rxf_lock); + bus->thr_rxf_ctl.p_task = kthread_run(brcmf_sdio_rxf_thread, + bus, "brcmf_rxf/%s", + dev_name(&sdiodev->func1->dev)); + if (IS_ERR(bus->thr_rxf_ctl.p_task)) { + brcmf_err("brcmf_sdio_rxf_thread failed to start\n"); + bus->thr_rxf_ctl.p_task = NULL; + } + } /* Set up the watchdog timer */ timer_setup(&bus->timer, brcmf_sdio_watchdog, 0); @@ -4554,6 +5300,12 @@ void brcmf_sdio_remove(struct brcmf_sdio *bus) bus->watchdog_tsk = NULL; } + if (bus->thr_rxf_ctl.p_task) { + send_sig(SIGTERM, bus->thr_rxf_ctl.p_task, 1); + kthread_stop(bus->thr_rxf_ctl.p_task); + bus->thr_rxf_ctl.p_task = NULL; + } + /* De-register interrupt handler */ brcmf_sdiod_intr_unregister(bus->sdiodev); @@ -4574,7 +5326,50 @@ void brcmf_sdio_remove(struct brcmf_sdio *bus) * necessary cores. */ msleep(20); - brcmf_chip_set_passive(bus->ci); + if (bus->sdiodev->fmac_ulp.ulp_state == + FMAC_ULP_ENTRY_RECV) { + brcmf_chip_ulp_reset_lhl_regs(bus->ci); + brcmf_chip_reset_pmu_regs(bus->ci); + } else { + brcmf_chip_set_passive(bus->ci); + } + + if (bus->ci->blhs) + bus->ci->blhs->init(bus->ci); + + /* Configure registers to trigger WLAN reset on + * "SDIO Soft Reset", and set RES bit to trigger + * SDIO as well as WLAN reset + * (instead of using PMU/CC Watchdog register) + */ + if (bus->ci->ccsec) { + struct brcmf_sdio_dev *sdiodev; + int err = 0; + u32 reg_val = 0; + + sdiodev = bus->sdiodev; + /* Set card control so an SDIO card reset + *does a WLAN backplane reset */ + reg_val = brcmf_sdiod_func0_rb(sdiodev, + SDIO_CCCR_BRCM_CARDCTRL, + &err); + reg_val |= SDIO_CCCR_BRCM_CARDCTRL_WLANRESET; + + brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_BRCM_CARDCTRL, + reg_val, &err); + brcmf_sdiod_func0_wb(sdiodev, SDIO_CCCR_ABORT, + sdiodev->func1->num |\ + SDIO_IO_CARD_RESET, + NULL); + } else { + /* Reset the PMU, backplane and all the + * cores by using the PMUWatchdogCounter. + */ + brcmf_chip_reset_watchdog(bus->ci); + } + if (bus->ci->blhs) + bus->ci->blhs->post_wdreset(bus->ci); + brcmf_sdio_clkctl(bus, CLK_NONE, false); sdio_release_host(bus->sdiodev->func1); } @@ -4582,9 +5377,10 @@ void brcmf_sdio_remove(struct brcmf_sdio *bus) } if (bus->sdiodev->settings) brcmf_release_module_param(bus->sdiodev->settings); +#ifdef CONFIG_BRCMFMAC_BT_SHARED_SDIO + brcmf_btsdio_detach(bus->sdiodev->bus_if); +#endif /* CONFIG_BRCMFMAC_BT_SHARED_SDIO */ - release_firmware(bus->sdiodev->clm_fw); - bus->sdiodev->clm_fw = NULL; kfree(bus->rxbuf); kfree(bus->hdrbuf); kfree(bus); @@ -4631,3 +5427,40 @@ int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep) return ret; } + +/* Check F2 Ready bit before sending data to Firmware */ +static int +brcmf_sdio_f2_ready(struct brcmf_sdio *bus) +{ + int ret = -1; + int iordy_status = 0; + + sdio_claim_host(bus->sdiodev->func1); + /* Read the status of IOR2 */ + iordy_status = brcmf_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IORx, NULL); + + sdio_release_host(bus->sdiodev->func1); + ret = iordy_status & SDIO_FUNC_ENABLE_2; + return ret; +} + +static int brcmf_ulp_event_notify(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data) +{ + int err = 0; + struct brcmf_bus *bus_if = ifp->drvr->bus_if; + struct brcmf_sdio_dev *sdiodev; + struct brcmf_sdio *bus; + struct brcmf_ulp_event *ulp_event = (struct brcmf_ulp_event *)data; + + sdiodev = bus_if->bus_priv.sdio; + bus = sdiodev->bus; + + brcmf_dbg(ULP, "Chip went to DS1 state : action %d\n", + ulp_event->ulp_dongle_action); + if (ulp_event->ulp_dongle_action == FMAC_ULP_ENTRY) + bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_ENTRY_RECV; + + return err; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h index 0d18ed15b4032..15fbccfa66bce 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h @@ -28,12 +28,17 @@ #define REG_F0_REG_MASK 0x7FF #define REG_F1_MISC_MASK 0x1FFFF +#define BRCMF_SDIO_REG_DAR_H2D_MSG_0 0x10030 +#define BRCMF_SDIO_REG_DAR_D2H_MSG_0 0x10038 + /* function 0 vendor specific CCCR registers */ #define SDIO_CCCR_BRCM_CARDCAP 0xf0 #define SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT BIT(1) #define SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT BIT(2) #define SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC BIT(3) +#define SDIO_CCCR_BRCM_CARDCAP_CHIPID_PRESENT BIT(6) +#define SDIO_CCCR_BRCM_CARDCAP_SECURE_MODE BIT(7) /* Interrupt enable bits for each function */ #define SDIO_CCCR_IEN_FUNC0 BIT(0) @@ -116,6 +121,10 @@ #define SBSDIO_FUNC1_MISC_REG_START 0x10000 /* f1 misc register start */ #define SBSDIO_FUNC1_MISC_REG_LIMIT 0x1001F /* f1 misc register end */ +/* Sdio Core Rev 31 */ +/* Hard Reset SDIO core, output soft reset signal which should cause backplane reset */ +#define SDIO_IO_CARD_RESET 0x08 + /* function 1 OCP space */ /* sb offset addr is <= 15 bits, 32k */ @@ -143,6 +152,16 @@ /* watchdog polling interval */ #define BRCMF_WD_POLL msecs_to_jiffies(10) +/* SDIO function number definition */ +#define SDIO_FUNC_0 0 +#define SDIO_FUNC_1 1 +#define SDIO_FUNC_2 2 +#define SDIO_FUNC_3 3 +#define SDIO_FUNC_4 4 +#define SDIO_FUNC_5 5 +#define SDIO_FUNC_6 6 +#define SDIO_FUNC_7 7 + /** * enum brcmf_sdiod_state - the state of the bus. * @@ -165,6 +184,35 @@ struct brcmf_sdreg { struct brcmf_sdio; struct brcmf_sdiod_freezer; +/* ULP SHM Offsets info */ +struct ulp_shm_info { + u32 m_ulp_ctrl_sdio; + u32 m_ulp_wakeevt_ind; + u32 m_ulp_wakeind; + u32 m_ulp_phytxblk; +}; + +/* FMAC ULP state machine */ +#define FMAC_ULP_IDLE (0) +#define FMAC_ULP_ENTRY_RECV (1) +#define FMAC_ULP_TRIGGERED (2) + +/* BRCMF_E_ULP event data */ +#define FMAC_ULP_EVENT_VERSION 1 +#define FMAC_ULP_DISABLE_CONSOLE 1 /* Disable console */ +#define FMAC_ULP_UCODE_DOWNLOAD 2 /* Download ULP ucode file */ +#define FMAC_ULP_ENTRY 3 /* Inform ulp entry to Host */ + +struct brcmf_ulp { + uint ulp_state; + struct ulp_shm_info ulp_shm_offset; +}; + +struct brcmf_ulp_event { + u16 version; + u16 ulp_dongle_action; +}; + struct brcmf_sdio_dev { struct sdio_func *func1; struct sdio_func *func2; @@ -186,13 +234,11 @@ struct brcmf_sdio_dev { struct sg_table sgtable; char fw_name[BRCMF_FW_NAME_LEN]; char nvram_name[BRCMF_FW_NAME_LEN]; - char clm_name[BRCMF_FW_NAME_LEN]; bool wowl_enabled; - bool func1_power_manageable; - bool func2_power_manageable; enum brcmf_sdiod_state state; struct brcmf_sdiod_freezer *freezer; - const struct firmware *clm_fw; + struct brcmf_ulp fmac_ulp; + bool ulp; }; /* sdio core registers */ @@ -256,7 +302,11 @@ struct sdpcmd_regs { u32 clockctlstatus; /* rev8 */ u32 PAD[7]; - u32 PAD[128]; /* DMA engines */ + u32 PAD[76]; /* DMA engines */ + + u32 chipid; /* SDIO ChipID Register, 0x330, rev31 */ + u32 eromptr; /* SDIO EromPtrOffset Register, 0x334, rev31 */ + u32 PAD[50]; /* SDIO/PCMCIA CIS region */ char cis[512]; /* 0x400-0x5ff, rev6 */ @@ -367,4 +417,83 @@ void brcmf_sdio_wowl_config(struct device *dev, bool enabled); int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep); void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus); +/* SHM offsets */ +#define M_DS1_CTRL_SDIO(ptr) ((ptr).ulp_shm_offset.m_ulp_ctrl_sdio) +#define M_WAKEEVENT_IND(ptr) ((ptr).ulp_shm_offset.m_ulp_wakeevt_ind) +#define M_ULP_WAKE_IND(ptr) ((ptr).ulp_shm_offset.m_ulp_wakeind) +#define M_DS1_PHYTX_ERR_BLK(ptr) ((ptr).ulp_shm_offset.m_ulp_phytxblk) + +#define D11_BASE_ADDR 0x18001000 +#define D11_AXI_BASE_ADDR 0xE8000000 +#define D11_SHM_BASE_ADDR (D11_AXI_BASE_ADDR + 0x4000) + +#define D11REG_ADDR(offset) (D11_BASE_ADDR + (offset)) +#define D11IHR_ADDR(offset) (D11_AXI_BASE_ADDR + 0x400 + (2 * (offset))) +#define D11SHM_ADDR(offset) (D11_SHM_BASE_ADDR + (offset)) + +/* MacControl register */ +#define D11_MACCONTROL_REG D11REG_ADDR(0x120) +#define D11_MACCONTROL_REG_WAKE 0x4000000 + +/* HUDI Sequence SHM bits */ +#define C_DS1_CTRL_SDIO_DS1_SLEEP 0x1 +#define C_DS1_CTRL_SDIO_MAC_ON 0x2 +#define C_DS1_CTRL_SDIO_RADIO_PHY_ON 0x4 +#define C_DS1_CTRL_SDIO_DS1_EXIT 0x8 +#define C_DS1_CTRL_PROC_DONE 0x100 +#define C_DS1_CTRL_REQ_VALID 0x200 + +/* M_ULP_WAKEIND bits */ +#define C_WATCHDOG_EXPIRY BIT(0) +#define C_FCBS_ERROR BIT(1) +#define C_RETX_FAILURE BIT(2) +#define C_HOST_WAKEUP BIT(3) +#define C_INVALID_FCBS_BLOCK BIT(4) +#define C_HUDI_DS1_EXIT BIT(5) +#define C_LOB_SLEEP BIT(6) +#define C_DS1_PHY_TXERR BIT(9) +#define C_DS1_WAKE_TIMER BIT(10) + +#define PHYTX_ERR_BLK_SIZE 18 +#define D11SHM_FIRST2BYTE_MASK 0xFFFF0000 +#define D11SHM_SECOND2BYTE_MASK 0x0000FFFF +#define D11SHM_2BYTE_SHIFT 16 + +#define D11SHM_RD(sdh, offset, ret) \ + brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) + +/* SHM Read is motified based on SHM 4 byte alignment as SHM size is 2 bytes and + * 2 byte is currently not working on FMAC + * If SHM address is not 4 byte aligned, then right shift by 16 + * otherwise, mask the first two MSB bytes + * Suppose data in address 7260 is 0x440002 and it is 4 byte aligned + * Correct SHM value is 0x2 for this SHM offset and next SHM value is 0x44 + */ +#define D11SHM_RDW(sdh, offset, ret) \ + ((offset % 4) ? \ + (brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \ + >> D11SHM_2BYTE_SHIFT) : \ + (brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \ + & D11SHM_SECOND2BYTE_MASK)) + +/* SHM is of size 2 bytes, 4 bytes write will overwrite other SHM's + * First read 4 bytes and then clear the required two bytes based on + * 4 byte alignment, then update the required value and write the + * 4 byte value now + */ +#define D11SHM_WR(sdh, offset, val, mask, ret) \ + do { \ + if ((offset) % 4) \ + val = (val & D11SHM_SECOND2BYTE_MASK) | \ + ((mask) << D11SHM_2BYTE_SHIFT); \ + else \ + val = (mask) | (val & D11SHM_FIRST2BYTE_MASK); \ + brcmf_sdiod_writel(sdh, D11SHM_ADDR(offset), val, ret); \ + } while (0) +#define D11REG_WR(sdh, addr, val, ret) \ + brcmf_sdiod_writel(sdh, addr, val, ret) + +#define D11REG_RD(sdh, addr, ret) \ + brcmf_sdiod_readl(sdh, addr, ret) + #endif /* BRCMFMAC_SDIO_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/trxhdr.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/trxhdr.h new file mode 100644 index 0000000000000..0411c7c7ffb99 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/trxhdr.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: ISC */ +/* Copyright (c) 2020 Cypress Semiconductor Corporation */ + +#ifndef BRCMFMAC_TRXHDR_H +#define BRCMFMAC_TRXHDR_H + +/* Bootloader makes special use of trx header "offsets" array */ +enum { + TRX_OFFSET_SIGN_INFO_IDX = 0, + TRX_OFFSET_DATA_FOR_SIGN1_IDX = 1, + TRX_OFFSET_DATA_FOR_SIGN2_IDX = 2, + TRX_OFFSET_ROOT_MODULUS_IDX = 3, + TRX_OFFSET_ROOT_EXPONENT_IDX = 67, + TRX_OFFSET_CONT_MODULUS_IDX = 68, + TRX_OFFSET_CONT_EXPONENT_IDX = 132, + TRX_OFFSET_HASH_FW_IDX = 133, + TRX_OFFSET_FW_LEN_IDX = 149, + TRX_OFFSET_TR_RST_IDX = 150, + TRX_OFFSET_FW_VER_FOR_ANTIROOLBACK_IDX = 151, + TRX_OFFSET_IV_IDX = 152, + TRX_OFFSET_NONCE_IDX = 160, + TRX_OFFSET_SIGN_INFO2_IDX = 168, + TRX_OFFSET_MAX_IDX +}; + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ +#define TRX_VERSION 4 /* Version 4 */ +#define TRX_MAX_OFFSET TRX_OFFSET_MAX_IDX /* Max number of file offsets */ + +struct trx_header_le { + __le32 magic; /* "HDR0" */ + __le32 len; /* Length of file including header */ + __le32 crc32; /* CRC from flag_version to end of file */ + __le32 flag_version; /* 0:15 flags, 16:31 version */ + __le32 offsets[TRX_MAX_OFFSET]; /* Offsets of partitions */ +}; + +#endif /* BRCMFMAC_TRXHDR_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c new file mode 100644 index 0000000000000..46080c6a0a3b6 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c @@ -0,0 +1,1016 @@ +/* Infineon WLAN driver: Target Wake Time (TWT) Source + * + * Copyright 2023 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#include "twt.h" +#include "debug.h" +#include "fwil.h" +#include "feature.h" +#include "bus.h" +#include "cfg80211.h" + +/** + * brcmf_twt_session_state_str - array of twt session states in string + */ +const char* brcmf_twt_session_state_str[BRCMF_TWT_SESS_STATE_MAX] = { + "Unspec", + "Setup inprogress", + "Setup complete", + "Teardown inprogres", + "Teardown complete" +}; + +/** + * brcmf_twt_wake_dur_to_min_twt() - Nominal Minimum Wake Duration derivation from Wake Duration + * + * @wake_dur: Wake Duration input. + * @min_twt_unit: Nomial Minimum Wake Duration Unit input. + * + * return: Nominal Minimum Wake Duration in units of min_twt_unit. + */ +static inline u8 +brcmf_twt_wake_dur_to_min_twt(u32 wake_dur, u8 min_twt_unit) +{ + u8 min_twt; + + if (min_twt_unit) { + /* + * If min_twt_unit is 1, then min_twt is + * in units of TUs (i.e) 1024 uS. + */ + min_twt = wake_dur / WAKE_DUR_UNIT_TU; + } else { + /* + * If min_twt_unit is 0, then min_twt is + * in units of 256 uS. + */ + min_twt = wake_dur / WAKE_DUR_UNIT_DEF; + } + + return min_twt; +} + +/** + * brcmf_twt_min_twt_to_wake_dur() - Derive Wake Duration from the + * Nominal Minimum Wake Duration + * + * @min_twt: Nominal Minimum Wake Duration input. + * @min_twt_unit: Nomial Minimum Wake Duration Unit input. + * 0 - 256 uS + * 1 - 1TU (or) 1024 uS + * + * return: Wake Duration in unit of microseconds. + */ +static inline u32 +brcmf_twt_min_twt_to_wake_dur(u8 min_twt, u8 min_twt_unit) +{ + u32 wake_dur; + + if (min_twt_unit) { + /* + * If min_twt_unit is 1, then min_twt is + * in units of TUs (i.e) 1024 uS. + */ + wake_dur = (u32)min_twt * WAKE_DUR_UNIT_TU; + } else { + /* + * If min_twt_unit is 0, then min_twt is + * in units of 256 uS. + */ + wake_dur = (u32)min_twt * WAKE_DUR_UNIT_DEF; + } + + return wake_dur; +} + +/** + * brcmf_twt_u32_to_float() - Derive Wake Interval Mantissa and Exponent + * from the Wake Interval + * + * @wake_int: Wake Interval input in microseconds. + * @exponent: pointer to Wake Interval Exponent output. + * @mantissa: pointer to Wake Interval Mantissa output. + */ +static inline void +brcmf_twt_u32_to_float(u32 wake_int, u8 *exponent, u16 *mantissa) +{ + u8 lzs = (u8)__builtin_clz(wake_int); /* leading 0's */ + u8 shift = lzs < 16 ? 16 - lzs : 0; + + *mantissa = (u16)(wake_int >> shift); + *exponent = shift; +} + +/** + * brcmf_twt_float_to_u32() - Derive Wake Interval derivation from + * Wake Interval Mantissa & Exponent. + * + * @exponent: Wake Interval Exponent input. + * @mantissa: Wake Interval Mantissa input. + * + * return: Wake interval in unit of microseconds. + */ +static inline u32 +brcmf_twt_float_to_u32(u8 exponent, u16 mantissa) +{ + return (u32)mantissa << exponent; +} + +/** + * brcmf_twt_stats_read() - Read the contents of the debugfs file "twt_stats". + * + * @seq: sequence for debugfs entry. + * @data: raw data pointer. + * + * return: 0. + */ +static int +brcmf_twt_stats_read(struct seq_file *seq, void *data) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(seq->private); + struct brcmf_pub *drvr = bus_if->drvr; + int i; + + /* Return if the if TWT is not supported by Firmware */ + if (!(drvr->feat_flags & BIT(BRCMF_FEAT_TWT))) + return 0; + + /* Iterate the interface list in struct brcmf_pub */ + for (i = 0; i < BRCMF_MAX_IFS; i++) { + struct brcmf_if *ifp = drvr->iflist[i]; + struct brcmf_twt_session *twt_sess; + + /* Skip interface if TWT session list in struct brcmf_if is empty */ + if (!ifp || list_empty(&ifp->twt_sess_list)) + continue; + + seq_printf(seq, "ifname: %s, ifidx: %u, bsscfgidx: %d\n", + ifp->ndev ? ifp->ndev->name : "" , + ifp->ifidx, ifp->bsscfgidx); + + /* Iterate the TWT session list in struct brcmf_if */ + list_for_each_entry(twt_sess, &ifp->twt_sess_list, list) { + struct brcmf_twt_params *twt_params; + u32 wake_dur, wake_int; + + twt_params = &twt_sess->twt_params; + + wake_dur = brcmf_twt_min_twt_to_wake_dur(twt_params->min_twt, + twt_params->min_twt_unit); + wake_int = brcmf_twt_float_to_u32(twt_params->exponent, + twt_params->mantissa); + + if (twt_params->negotiation_type == IFX_TWT_PARAM_NEGO_TYPE_ITWT) + seq_printf(seq, "\tiTWT Session, Flow ID: %u\n", + twt_params->flow_id); + else if (twt_params->negotiation_type == IFX_TWT_PARAM_NEGO_TYPE_BTWT) + seq_printf(seq, "\tbTWT Session, Bcast TWT ID: %u\n", + twt_params->bcast_twt_id); + else + continue; + + seq_printf(seq, "\t\tSession state : %s\n", + brcmf_twt_session_state_str[twt_sess->state]); + seq_printf(seq, "\t\tTWT peer : %pM\n", + twt_sess->peer_addr.octet); + seq_printf(seq, "\t\tTarget Wake Time : %llu uS\n", + twt_params->twt); + seq_printf(seq, "\t\tWake Duration : %u uS\n", + wake_dur); + seq_printf(seq, "\t\tWake Interval : %u uS\n", + wake_int); + seq_printf(seq, "\t\tDialog_token : %u\n", + twt_params->dialog_token); + seq_printf(seq, "\t\tSession type : %s, %s, %s\n\n", + twt_params->implicit ? "Implicit" : "Explicit", + twt_params->trigger ? "Trigger based" : "Non-Trigger based", + twt_params->flow_type ? "Un-Announced" : "Announced"); + } + } + return 0; +} + +/** + * brcmf_twt_debugfs_create() - create debugfs entries. + * + * @drvr: driver instance. + */ +void +brcmf_twt_debugfs_create(struct brcmf_pub *drvr) +{ + brcmf_debugfs_add_entry(drvr, "twt_stats", brcmf_twt_stats_read); +} + +/** + * brcmf_twt_cleanup_sessions - Cleanup the TWT sessions from the driver list. + * + * @ifp: interface instatnce. + * + * return: 0 on success, value < 0 on failure. + */ +s32 +brcmf_twt_cleanup_sessions(struct brcmf_if *ifp) +{ + struct brcmf_twt_session *entry = NULL, *next = NULL; + s32 ret = 0; + + if (!ifp) { + brcmf_err("TWT: Failed to cleanup sessions"); + ret = -EIO; + } + + spin_lock(&ifp->twt_sess_list_lock); + + list_for_each_entry_safe(entry, next, &ifp->twt_sess_list, list) { + + list_del(&entry->list); + kfree(entry); + brcmf_dbg(TWT, "TWT: Deleted session with peer: %pM, flow ID: %d", + entry->peer_addr.octet, entry->twt_params.flow_id); + } + + spin_unlock(&ifp->twt_sess_list_lock); + + return ret; +} + +/** + * brcmf_itwt_lookup_session_by_flowid() - Lookup an iTWT sesssion information from + * the driver list based on the Flow ID. + * + * @ifp: interface instance + * @flow_id: iTWT session Flow ID + * + * return: Pointer to a TWT session instance if lookup is successful, NULL on failure. + */ +static struct brcmf_twt_session * +brcmf_itwt_lookup_session_by_flowid(struct brcmf_if *ifp, u8 flow_id) +{ + struct brcmf_twt_session *iter = NULL; + + if (list_empty(&ifp->twt_sess_list)) + return NULL; + + list_for_each_entry(iter, &ifp->twt_sess_list, list) { + if (iter->twt_params.negotiation_type != IFX_TWT_PARAM_NEGO_TYPE_ITWT) + continue; + + if (iter->twt_params.flow_id == flow_id) + return iter; + } + + return NULL; +} + +/** + * brcmf_twt_update_session_state() - Update the state of the TWT Session in the driver list + * + * @ifp: interface instance. + * @twt_sess: TWT session to be updated. + * @state: TWT session state, Refer enum brcmf_twt_session_state. + * + * return: 0 on successful updation, value < 0 on failure. + */ +static s32 +brcmf_twt_update_session_state(struct brcmf_if *ifp, struct brcmf_twt_session *twt_sess, + enum brcmf_twt_session_state state) +{ + s32 ret = 0; + + if (!twt_sess) { + brcmf_dbg(TWT, + "TWT: session is not available to update new state: %s", + brcmf_twt_session_state_str[state]); + ret = -EINVAL; + goto exit; + } + + spin_lock(&ifp->twt_sess_list_lock); + + twt_sess->state = state; + brcmf_dbg(TWT, "TWT: updated session with peer: %pM, " + "flow ID: %d, state: %s", + twt_sess->peer_addr.octet, + twt_sess->twt_params.flow_id, + brcmf_twt_session_state_str[twt_sess->state]); + + spin_unlock(&ifp->twt_sess_list_lock); +exit: + return ret; +} + +/** + * brcmf_twt_update_session() - Update TWT session info in the driver list. + * + * @ifp: interface instance. + * @twt_sess: TWT session to be updated. + * @peer_addr: TWT peer address. + * @state: TWT session state, Refer enum brcmf_twt_session_state. + * @twt_params: TWT session parameters. + * + * return: 0 on successful updation, value < 0 on failure. + */ +static s32 +brcmf_twt_update_session(struct brcmf_if *ifp, struct brcmf_twt_session *twt_sess, + const u8 *peer_addr, enum brcmf_twt_session_state state, + struct brcmf_twt_params *twt_params) +{ + s32 ret = 0; + + if (!twt_sess) { + brcmf_dbg(TWT, "TWT: session is not available to update"); + ret = -EINVAL; + goto exit; + } + + spin_lock(&ifp->twt_sess_list_lock); + + memcpy(twt_sess->peer_addr.octet, peer_addr, ETH_ALEN); + twt_sess->state = state; + memcpy(&twt_sess->twt_params, twt_params, + sizeof(struct brcmf_twt_params)); + + brcmf_dbg(TWT, "TWT: updated session with peer: %pM, " + "flow ID: %d, state: %s", + twt_sess->peer_addr.octet, + twt_sess->twt_params.flow_id, + brcmf_twt_session_state_str[twt_sess->state]); + + spin_unlock(&ifp->twt_sess_list_lock); +exit: + return ret; +} + +/** + * brcmf_twt_del_session() - Delete a TWT sesssion info from the driver list. + * + * @ifp: interface instance. + * @twt_sess: TWT session to be deleted. + * + * return: 0 on successful deletion, value < 0 on failure. + */ +static s32 +brcmf_twt_del_session(struct brcmf_if *ifp, struct brcmf_twt_session *twt_sess) +{ + s32 ret = 0; + u8 flow_id; + u8 peer_addr[ETH_ALEN]; + + if (!twt_sess) { + brcmf_dbg(TWT, "TWT: session is not available to delete"); + ret = -EINVAL; + goto exit; + } + + spin_lock(&ifp->twt_sess_list_lock); + + flow_id = twt_sess->twt_params.flow_id; + memcpy(peer_addr, twt_sess->peer_addr.octet, ETH_ALEN); + + list_del(&twt_sess->list); + kfree(twt_sess); + + brcmf_dbg(TWT, "TWT: Deleted session with peer: %pM, flow ID: %d", + peer_addr, flow_id); + + spin_unlock(&ifp->twt_sess_list_lock); +exit: + return ret; +} + +/** + * brcmf_twt_add_session() - Add a TWT session info to the driver list. + * + * @ifp: interface instance. + * @peer_addr: TWT peer address. + * @state: TWT session state, Refer enum brcmf_twt_session_state. + * @twt_params: TWT session parameters. + * + * return: 0 on successful addition, value < 0 on failure. + */ +static s32 +brcmf_twt_add_session(struct brcmf_if *ifp, const u8 *peer_addr, + enum brcmf_twt_session_state state, + struct brcmf_twt_params *twt_params) +{ + struct brcmf_twt_session *new_twt_sess; + s32 ret = 0; + + new_twt_sess = kzalloc(sizeof(*new_twt_sess), GFP_ATOMIC); + if (!new_twt_sess) { + brcmf_err("TWT: Failed to alloc memory for new session"); + ret = -ENOMEM; + goto exit; + } + + new_twt_sess->ifidx = ifp->ifidx; + new_twt_sess->bsscfgidx = ifp->bsscfgidx; + new_twt_sess->state = state; + + memcpy(new_twt_sess->peer_addr.octet, peer_addr, ETH_ALEN); + memcpy(&new_twt_sess->twt_params, twt_params, + sizeof(struct brcmf_twt_params)); + + spin_lock(&ifp->twt_sess_list_lock); + + list_add_tail(&new_twt_sess->list, &ifp->twt_sess_list); + brcmf_dbg(TWT, "TWT: Added session with peer: %pM, " + "flow ID: %d, state: %s", + new_twt_sess->peer_addr.octet, + new_twt_sess->twt_params.flow_id, + brcmf_twt_session_state_str[new_twt_sess->state]); + + spin_unlock(&ifp->twt_sess_list_lock); +exit: + return ret; +} + +/** + * brcmf_twt_setup_event_handler() - Handle the TWT Setup Event notification from Firmware. + * + * @ifp: interface instatnce. + * @e: event message. + * @data: payload of message, contains TWT session data. + * + * return: 0 on success, value < 0 on failure. + */ +static s32 +brcmf_twt_setup_event_handler(struct brcmf_if *ifp, const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_twt_setup_event *setup_event; + struct brcmf_twt_sdesc *setup_desc; + struct brcmf_twt_session *twt_sess = NULL; + struct brcmf_twt_params twt_params; + s32 ret = -1; + + setup_event = (struct brcmf_twt_setup_event *)data; + setup_desc = (struct brcmf_twt_sdesc *) + (data + sizeof(struct brcmf_twt_setup_event)); + + /* TWT Negotiation_type */ + twt_params.negotiation_type = setup_desc->negotiation_type; + + switch (twt_params.negotiation_type) { + case IFX_TWT_PARAM_NEGO_TYPE_ITWT: + /* Flow ID */ + twt_params.flow_id = setup_desc->flow_id; + break; + case IFX_TWT_PARAM_NEGO_TYPE_BTWT: + /* Broadcast TWT ID */ + twt_params.bcast_twt_id = setup_desc->bid; + + /* TODO: Handle the Broadcast TWT Setup Event */ + /* FALLTHRU */ + default: + brcmf_err("TWT: Setup EVENT: Negotiation Type %d not handled", + twt_params.negotiation_type); + ret = -EOPNOTSUPP; + goto exit; + } + + /* Setup Event */ + if (setup_desc->setup_cmd != IFX_TWT_OPER_SETUP_CMD_TYPE_ACCEPT) { + brcmf_err("TWT: Setup EVENT: Request not accepted by the AP"); + goto exit; + } + twt_params.setup_cmd = setup_desc->setup_cmd; + + /* Dialog Token */ + twt_params.dialog_token = setup_event->dialog; + + /* Flowflags */ + twt_params.implicit = (setup_desc->flow_flags & BRCMF_TWT_FLOW_FLAG_IMPLICIT) ? 1 : 0; + twt_params.flow_type = (setup_desc->flow_flags & BRCMF_TWT_FLOW_FLAG_UNANNOUNCED) ? 1 : 0; + twt_params.trigger = (setup_desc->flow_flags & BRCMF_TWT_FLOW_FLAG_TRIGGER) ? 1 : 0; + twt_params.requestor = (setup_desc->flow_flags & BRCMF_TWT_FLOW_FLAG_REQUEST) ? 1 : 0; + twt_params.protection = (setup_desc->flow_flags & BRCMF_TWT_FLOW_FLAG_PROTECT) ? 1 : 0; + + /* Target Wake Time */ + twt_params.twt = le64_to_cpu((u64)setup_desc->wake_time_h << 32) | + le64_to_cpu((u64)setup_desc->wake_time_l); + + /* Wake Duration or Service Period */ + twt_params.min_twt_unit = 0; + twt_params.min_twt = + brcmf_twt_wake_dur_to_min_twt(le32_to_cpu(setup_desc->wake_dur), + twt_params.min_twt_unit); + + /* Wake Interval or Service Interval */ + brcmf_twt_u32_to_float(le32_to_cpu(setup_desc->wake_int), + &twt_params.exponent, &twt_params.mantissa); + + /* Lookup the session list for the received flow ID */ + twt_sess = brcmf_itwt_lookup_session_by_flowid(ifp, twt_params.flow_id); + if (twt_sess) + ret = brcmf_twt_update_session(ifp, twt_sess, e->addr, + BRCMF_TWT_SESS_STATE_SETUP_COMPLETE, + &twt_params); + else + ret = brcmf_twt_add_session(ifp, e->addr, + BRCMF_TWT_SESS_STATE_SETUP_COMPLETE, + &twt_params); + + if (ret) { + brcmf_err("TWT: Setup EVENT: Failed to update session"); + goto exit; + } + + brcmf_dbg(TWT, "TWT: Setup EVENT: Session Setup Complete\n" + "Setup command : %u\n" + "Flow flags : 0x %02x\n" + "Flow ID : %u\n" + "Broadcast TWT ID : %u\n" + "Wake Time H,L : 0x %08x %08x\n" + "Wake Type : %u\n" + "Wake Duration : %u uS\n" + "Wake Interval : %u uS\n" + "Negotiation type : %u\n", + setup_desc->setup_cmd, + setup_desc->flow_flags, + setup_desc->flow_id, + setup_desc->bid, + setup_desc->wake_time_h, + setup_desc->wake_time_l, + setup_desc->wake_type, + setup_desc->wake_dur, + setup_desc->wake_int, + setup_desc->negotiation_type); +exit: + return ret; +} + +/** + * brcmf_twt_teardown_event_handler() - Handle the TWT Teardown Event notification from Firmware. + * + * @ifp: interface instatnce. + * @e: event message. + * @data: payload of message, contains TWT session data. + * + * return: 0 on success, value < 0 on failure. + */ +static s32 +brcmf_twt_teardown_event_handler(struct brcmf_if *ifp, const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_twt_teardown_event *teardown_event; + struct brcmf_twt_teardesc *teardown_desc; + struct brcmf_twt_session *twt_sess = NULL; + struct brcmf_twt_params twt_params; + s32 ret; + + teardown_event = (struct brcmf_twt_teardown_event *)data; + teardown_desc = (struct brcmf_twt_teardesc *) + (data + sizeof(struct brcmf_twt_teardown_event)); + + /* TWT Negotiation_type */ + twt_params.negotiation_type = teardown_desc->negotiation_type; + + /* Teardown all Negotiated TWT */ + twt_params.teardown_all_twt = teardown_desc->alltwt; + if (twt_params.teardown_all_twt) { + ret = brcmf_twt_cleanup_sessions(ifp); + goto exit; + } + + switch (twt_params.negotiation_type) { + case IFX_TWT_PARAM_NEGO_TYPE_ITWT: + /* Flow ID */ + twt_params.flow_id = teardown_desc->flow_id; + + /* Lookup the session list for the received flow ID */ + twt_sess = brcmf_itwt_lookup_session_by_flowid(ifp, twt_params.flow_id); + if (twt_sess) { + ret = brcmf_twt_update_session_state(ifp, twt_sess, + BRCMF_TWT_SESS_STATE_TEARDOWN_COMPLETE); + if (ret) { + brcmf_err("TWT: Failed to update session state"); + goto exit; + } + + ret = brcmf_twt_del_session(ifp, twt_sess); + if (ret) { + brcmf_err("TWT: Failed to Delete session from list"); + goto exit; + } + } else { + brcmf_dbg(TWT, "TWT: session is not available to delete"); + ret = -EINVAL; + goto exit; + } + break; + case IFX_TWT_PARAM_NEGO_TYPE_BTWT: + /* Broadcast TWT ID */ + twt_params.bcast_twt_id = teardown_desc->bid; + + /* TODO: Handle the Broadcast TWT Teardown Event */ + /* FALLTHRU */ + default: + brcmf_err("TWT: Negotiation Type not handled\n"); + ret = -EOPNOTSUPP; + goto exit; + } + + brcmf_dbg(TWT, "TWT: Teardown EVENT: Session Teardown Complete\n" + "Flow ID : %u\n" + "Broadcast TWT ID : %u\n" + "Negotiation type : %u\n" + "Teardown all TWT : %u\n", + teardown_desc->flow_id, + teardown_desc->bid, + teardown_desc->negotiation_type, + teardown_desc->alltwt); +exit: + return ret; +} + +/** + * brcmf_notify_twt_event() - Handle the TWT Event notifications from Firmware. + * + * @ifp: interface instatnce. + * @e: event message. + * @data: payload of message, contains TWT session data. + * + * return: 0 on success, value < 0 on failure. + */ +s32 +brcmf_notify_twt_event(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data) +{ + s32 ret; + + if (!ifp) { + ret = -EIO; + goto exit; + } + + switch(e->event_code) { + case BRCMF_E_TWT_SETUP: + ret = brcmf_twt_setup_event_handler(ifp, e, data); + if (ret) { + brcmf_err("TWT: EVENT: Failed to handle TWT Setup event"); + goto exit; + } + break; + case BRCMF_E_TWT_TEARDOWN: + ret = brcmf_twt_teardown_event_handler(ifp, e, data); + if (ret) { + brcmf_err("TWT: EVENT: Failed to handle TWT Teardown event"); + goto exit; + } + break; + default: + brcmf_err("TWT: EVENT: Received event %d not handeled", e->event_code); + ret = -EOPNOTSUPP; + goto exit; + } + +exit: + return ret; +} + +/** + * brcmf_twt_setup_oper_handler() - Handle the TWT Setup Operation request from Userspace. + * + * @ifp: interface instance. + * @twt_params: TWT session parameters. + * + * return: 0 on success, value < 0 on failure. + */ +s32 +brcmf_twt_setup_oper_handler(struct brcmf_if *ifp, struct brcmf_twt_params twt_params) +{ + struct brcmf_cfg80211_vif *vif = ifp->vif; + struct brcmf_twt_setup_oper val; + struct brcmf_twt_session *twt_sess = NULL; + s32 ret; + + memset(&val, 0, sizeof(val)); + val.version = BRCMF_TWT_SETUP_VER; + val.length = sizeof(val.version) + sizeof(val.length); + + /* Default values, Override Below */ + val.sdesc.flow_flags = 0x0; + val.sdesc.wake_dur = 0xFFFFFFFF; + val.sdesc.wake_int = 0xFFFFFFFF; + val.sdesc.wake_int_max = 0xFFFFFFFF; + + /* TWT Negotiation_type */ + val.sdesc.negotiation_type = (u8)twt_params.negotiation_type; + + switch (val.sdesc.negotiation_type) { + case IFX_TWT_PARAM_NEGO_TYPE_ITWT: + /* Flow ID */ + if ((twt_params.flow_id >= 0x0 && twt_params.flow_id <= 0x7)) { + val.sdesc.flow_id = twt_params.flow_id; + + /* Lookup the session list for the requested flow ID */ + twt_sess = brcmf_itwt_lookup_session_by_flowid(ifp, + twt_params.flow_id); + if (twt_sess) { + brcmf_err("TWT: Setup REQ: Skipping, " + "session with flow ID %d current state %s", + twt_params.flow_id, + brcmf_twt_session_state_str[twt_sess->state]); + ret = -EINVAL; + goto exit; + } + } else if (twt_params.flow_id == 0xFF) { + /* Let the Firmware choose the Flow ID */ + val.sdesc.flow_id = twt_params.flow_id; + } else { + brcmf_err("TWT: Setup REQ: flow ID: %d is invalid", + twt_params.flow_id); + ret = -EINVAL; + goto exit; + } + break; + case IFX_TWT_PARAM_NEGO_TYPE_BTWT: + /* Broadcast TWT ID */ + val.sdesc.bid = twt_params.bcast_twt_id; + + /* TODO: Handle the Broadcast TWT Setup REQ */ + /* FALLTHRU */ + default: + brcmf_err("TWT: Setup REQ: Negotiation Type %d not handled", + twt_params.negotiation_type); + ret = -EOPNOTSUPP; + goto exit; + } + + /* Setup command */ + val.sdesc.setup_cmd = twt_params.setup_cmd; + + /* Flow flags */ + val.sdesc.flow_flags |= ((twt_params.negotiation_type & 0x02) >> 1 ? + BRCMF_TWT_FLOW_FLAG_BROADCAST : 0); + val.sdesc.flow_flags |= (twt_params.implicit ? BRCMF_TWT_FLOW_FLAG_IMPLICIT : 0); + val.sdesc.flow_flags |= (twt_params.flow_type ? BRCMF_TWT_FLOW_FLAG_UNANNOUNCED : 0); + val.sdesc.flow_flags |= (twt_params.trigger ? BRCMF_TWT_FLOW_FLAG_TRIGGER : 0); + val.sdesc.flow_flags |= ((twt_params.negotiation_type & 0x01) ? + BRCMF_TWT_FLOW_FLAG_WAKE_TBTT_NEGO : 0); + val.sdesc.flow_flags |= (twt_params.requestor ? BRCMF_TWT_FLOW_FLAG_REQUEST : 0); + val.sdesc.flow_flags |= (twt_params.protection ? BRCMF_TWT_FLOW_FLAG_PROTECT : 0); + + if (twt_params.twt) { + /* Target Wake Time parameter */ + val.sdesc.wake_time_h = cpu_to_le32((u32)(twt_params.twt >> 32)); + val.sdesc.wake_time_l = cpu_to_le32((u32)(twt_params.twt)); + val.sdesc.wake_type = BRCMF_TWT_WAKE_TIME_TYPE_BSS; + } else if (twt_params.twt_offset) { + /* Target Wake Time offset parameter */ + val.sdesc.wake_time_h = cpu_to_le32((u32)(twt_params.twt_offset >> 32)); + val.sdesc.wake_time_l = cpu_to_le32((u32)(twt_params.twt_offset)); + val.sdesc.wake_type = BRCMF_TWT_WAKE_TIME_TYPE_OFFSET; + } else { + /* Let the Firmware choose the Target Wake Time */ + val.sdesc.wake_time_h = 0x0; + val.sdesc.wake_time_l = 0x0; + val.sdesc.wake_type = BRCMF_TWT_WAKE_TIME_TYPE_AUTO; + } + + /* Wake Duration or Service Period */ + val.sdesc.wake_dur = cpu_to_le32(brcmf_twt_min_twt_to_wake_dur(twt_params.min_twt, + twt_params.min_twt_unit)); + + /* Wake Interval or Service Interval */ + val.sdesc.wake_int = cpu_to_le32(brcmf_twt_float_to_u32(twt_params.exponent, + twt_params.mantissa)); + + /* Send the TWT Setup request to Firmware */ + ret = brcmf_fil_xtlv_data_set(ifp, "twt", BRCMF_TWT_CMD_SETUP, + (void *)&val, sizeof(val)); + if (ret < 0) { + brcmf_err("TWT: Setup REQ: Failed, ret: %d", ret); + goto exit; + } + + /* Add an entry setup with progress state if flow ID is specified */ + if (twt_params.flow_id != 0xFF) { + ret = brcmf_twt_add_session(ifp, vif->profile.bssid, + BRCMF_TWT_SESS_STATE_SETUP_INPROGRESS, + &twt_params); + if (ret < 0) { + brcmf_err("TWT: Setup EVENT: Failed to add session"); + goto exit; + } + } + + + brcmf_dbg(TWT, "TWT: Setup REQ: Session Setup In Progress\n" + "Setup command : %u\n" + "Flow flags : 0x %02x\n" + "Flow ID : %u\n" + "Broadcast TWT ID : %u\n" + "Wake Time H,L : 0x %08x %08x\n" + "Wake Type : %u\n" + "Wake Duration : %u uS\n" + "Wake Interval : %u uS\n" + "Negotiation type : %u\n", + val.sdesc.setup_cmd, + val.sdesc.flow_flags, + val.sdesc.flow_id, + val.sdesc.bid, + val.sdesc.wake_time_h, + val.sdesc.wake_time_l, + val.sdesc.wake_type, + val.sdesc.wake_dur, + val.sdesc.wake_int, + val.sdesc.negotiation_type); +exit: + return ret; +} + +/** + * brcmf_twt_teardown_oper_handler() - Handle the TWT Teardown Operation request from Userspace. + * + * @ifp: interface instance. + * @twt_params: TWT session parameters. + * + * return: 0 on success, value < 0 on failure. + */ +s32 +brcmf_twt_teardown_oper_handler(struct brcmf_if *ifp, struct brcmf_twt_params twt_params) +{ + struct brcmf_twt_teardown_oper val; + struct brcmf_twt_session *twt_sess = NULL; + s32 ret; + + memset(&val, 0, sizeof(val)); + val.version = BRCMF_TWT_TEARDOWN_VER; + val.length = sizeof(val.version) + sizeof(val.length); + + /* TWT Negotiation_type */ + val.teardesc.negotiation_type = (u8)twt_params.negotiation_type; + + /* Teardown All TWT */ + val.teardesc.alltwt = twt_params.teardown_all_twt; + if (val.teardesc.alltwt) { + /* If Teardown all TWT is set, then check if the TWT session is not empty */ + if (list_empty(&ifp->twt_sess_list)) { + brcmf_err("TWT: Teardown REQ: No active TWT sessions"); + ret = -EINVAL; + goto exit; + } + + /* Reset Flow ID & Bcast TWT ID with a placeholder value */ + twt_params.flow_id = 0xFF; + twt_params.bcast_twt_id = 0xFF; + } + + switch (val.teardesc.negotiation_type) { + case IFX_TWT_PARAM_NEGO_TYPE_ITWT: + /* Flow ID */ + if ((twt_params.flow_id >= 0x0 && twt_params.flow_id <= 0x7)) { + val.teardesc.flow_id = twt_params.flow_id; + + /* Lookup the session list for the requested flow ID */ + twt_sess = brcmf_itwt_lookup_session_by_flowid(ifp, twt_params.flow_id); + if ((twt_sess == NULL) || + (twt_sess->state != BRCMF_TWT_SESS_STATE_SETUP_COMPLETE)) { + brcmf_err("TWT: Teardown REQ: flow ID: %d is not active", + twt_params.flow_id); + ret = -EINVAL; + goto exit; + } + } else if (twt_params.flow_id == 0xFF) { + val.teardesc.flow_id = twt_params.flow_id; + } else { + brcmf_err("TWT: Teardown REQ: flow ID: %d is invalid", + twt_params.flow_id); + ret = -EINVAL; + goto exit; + } + break; + case IFX_TWT_PARAM_NEGO_TYPE_BTWT: + /* Broadcast TWT ID */ + val.teardesc.bid = twt_params.bcast_twt_id; + + /* TODO: Handle the Broadcast TWT Teardown REQ */ + /* FALLTHRU */ + default: + brcmf_err("TWT: Teardown REQ: Negotiation Type %d not handled", + twt_params.negotiation_type); + ret = -EOPNOTSUPP; + goto exit; + } + + /* Send the TWT Teardown request to Firmware */ + ret = brcmf_fil_xtlv_data_set(ifp, "twt", BRCMF_TWT_CMD_TEARDOWN, + (void *)&val, sizeof(val)); + if (ret < 0) { + brcmf_err("TWT: Teardown REQ: Failed, ret: %d", ret); + goto exit; + } + + brcmf_twt_update_session_state(ifp, twt_sess, + BRCMF_TWT_SESS_STATE_TEARDOWN_INPROGRESS); + brcmf_dbg(TWT, "TWT: Teardown REQ: Session Teardown In Progress\n" + "Flow ID : %u\n" + "Broadcast TWT ID : %u\n" + "Negotiation type : %u\n" + "Teardown all TWT : %u\n", + val.teardesc.flow_id, + val.teardesc.bid, + val.teardesc.negotiation_type, + val.teardesc.alltwt); +exit: + return ret; +} + +/** + * brcmf_twt_oper() - Handle the TWT Operation requests from Userspace. + * + * @wiphy: wiphy object for cfg80211 interface. + * @wdev: wireless device. + * @twt_params: TWT session parameters. + * + * return: 0 on success, value < 0 on failure. + */ +s32 +brcmf_twt_oper(struct wiphy *wiphy, struct wireless_dev *wdev, + struct brcmf_twt_params twt_params) +{ + struct brcmf_cfg80211_vif *vif = NULL; + struct brcmf_if *ifp = NULL; + s32 ret; + + vif = wdev_to_vif(wdev); + if (!vif) { + ret = -EIO; + goto exit; + } + + ifp = vif->ifp; + if (!ifp) { + ret = -EIO; + goto exit; + } + + /* Check if TWT feature is supported in the Firmware */ + if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_TWT)) { + brcmf_err("TWT: REQ: Operation %d can't be handled, TWT not enabled", + twt_params.twt_oper); + ret = -EOPNOTSUPP; + goto exit; + } + + /* Check if vif is operating in Station Mode */ + if (wdev->iftype != NL80211_IFTYPE_STATION) { + brcmf_err("TWT: REQ: Operation %d can't be handled, vif is not STA", + twt_params.twt_oper); + ret = -EOPNOTSUPP; + goto exit; + } + + /* Check if the interface is associated with another WLAN device */ + if (!test_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state)) { + brcmf_err("TWT: REQ: Operation %d can't be handled, vif not connected with WLAN peer", + twt_params.twt_oper); + ret = -ENOTCONN; + goto exit; + } + + /* TWT Operation */ + switch (twt_params.twt_oper) { + case IFX_TWT_OPER_SETUP: + ret = brcmf_twt_setup_oper_handler(ifp, twt_params); + break; + case IFX_TWT_OPER_TEARDOWN: + ret = brcmf_twt_teardown_oper_handler(ifp, twt_params); + break; + default: + brcmf_err("TWT: REQ: Operation %d not supported", + twt_params.twt_oper); + ret = -EOPNOTSUPP; + goto exit; + } +exit: + return ret; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h new file mode 100644 index 0000000000000..0208aed505841 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h @@ -0,0 +1,341 @@ +/* Infineon WLAN driver: Target Wake Time (TWT) Header + * + * Copyright 2023 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#ifndef BRCMF_TWT_H +#define BRCMF_TWT_H + +#include "vendor_ifx.h" +#include "core.h" + +/* Min TWT Default Unit */ +#define WAKE_DUR_UNIT_DEF 256 +/* Min TWT Unit in TUs */ +#define WAKE_DUR_UNIT_TU 1024 + +/** + * enum brcmf_twt_cmd - TWT iovar subcmds handled by firmware TWT module + * + * @BRCMF_TWT_CMD_ENAB: Enable the firmware TWT module. + * @BRCMF_TWT_CMD_SETUP: Setup a TWT session with a TWT peer. + * @BRCMF_TWT_CMD_TEARDOWN: Teardown the active TWT session with a TWT peer. + */ +enum brcmf_twt_cmd { + BRCMF_TWT_CMD_ENAB, + BRCMF_TWT_CMD_SETUP, + BRCMF_TWT_CMD_TEARDOWN, +}; + +/* TWT iovar subcmd version */ +#define BRCMF_TWT_SETUP_VER 0u +#define BRCMF_TWT_TEARDOWN_VER 0u + +/** + * enum brcmf_twt_flow_flag - TWT flow flags to be used in TWT iovar setup subcmd + * + * @BRCMF_TWT_FLOW_FLAG_BROADCAST: Broadcast TWT Session. + * @BRCMF_TWT_FLOW_FLAG_IMPLICIT: Implcit TWT session type. + * @BRCMF_TWT_FLOW_FLAG_UNANNOUNCED: Unannounced TWT session type. + * @BRCMF_TWT_FLOW_FLAG_TRIGGER: Trigger based TWT Session type. + * @BRCMF_TWT_FLOW_FLAG_WAKE_TBTT_NEGO: Wake TBTT Negotiation type. + * @BRCMF_TWT_FLOW_FLAG_REQUEST: TWT Session setup requestor. + * @BRCMF_TWT_FLOW_FLAG_RESPONDER_PM: Not used. + * @BRCMF_TWT_FLOW_FLAG_UNSOLICITED: Unsolicited TWT Session Setup. + * @BRCMF_TWT_FLOW_FLAG_PROTECT: Specifies whether Tx within SP is protected, Not used. + */ +enum brcmf_twt_flow_flag { + BRCMF_TWT_FLOW_FLAG_BROADCAST = BIT(0), + BRCMF_TWT_FLOW_FLAG_IMPLICIT = BIT(1), + BRCMF_TWT_FLOW_FLAG_UNANNOUNCED = BIT(2), + BRCMF_TWT_FLOW_FLAG_TRIGGER = BIT(3), + BRCMF_TWT_FLOW_FLAG_WAKE_TBTT_NEGO = BIT(4), + BRCMF_TWT_FLOW_FLAG_REQUEST = BIT(5), + BRCMF_TWT_FLOW_FLAG_RESPONDER_PM = BIT(6), + BRCMF_TWT_FLOW_FLAG_UNSOLICITED = BIT(7), + BRCMF_TWT_FLOW_FLAG_PROTECT = BIT(8) +}; + +/** + * enum brcmf_twt_session_state - TWT session state in the Host driver list + * + * @BRCMF_TWT_SESS_STATE_UNSPEC: Reserved value 0. + * @BRCMF_TWT_SESS_STATE_SETUP_INPROGRESS: TWT session setup request was sent + * to the Firmware. + * @BRCMF_TWT_SESS_STATE_SETUP_COMPLETE: TWT session setup is complete and received + * setup event from the Firmweare. + * @BRCMF_TWT_SESS_STATE_TEARDOWN_INPROGRESS: TWT session teardown request was sent + * to the Firmware. + * @BRCMF_TWT_SESS_STATE_TEARDOWN_COMPLETE: TWT session teardown is complete and + * received Teardown event from the Firmware. + * @BRCMF_TWT_SESS_STATE_MAX: This acts as a the tail of state list. + * Make sure it located at the end of the list. + */ +enum brcmf_twt_session_state { + BRCMF_TWT_SESS_STATE_UNSPEC, + BRCMF_TWT_SESS_STATE_SETUP_INPROGRESS, + BRCMF_TWT_SESS_STATE_SETUP_COMPLETE, + BRCMF_TWT_SESS_STATE_TEARDOWN_INPROGRESS, + BRCMF_TWT_SESS_STATE_TEARDOWN_COMPLETE, + BRCMF_TWT_SESS_STATE_MAX +}; + +/** + * struct brcmf_twt_params - TWT session parameters + * + * @twt_oper: TWT operation, Refer enum ifx_twt_oper. + * @negotiation_type: Negotiation Type, Refer enum ifx_twt_param_nego_type. + * @setup_cmd: Setup cmd, Refer enum ifx_twt_oper_setup_cmd_type. + * @dialog_token: TWT Negotiation Dialog Token. + * @twt: Target Wake Time. + * @twt_offset: Target Wake Time Offset. + * @min_twt: Nominal Minimum Wake Duration. + * @exponent: Wake Interval Exponent. + * @mantissa: Wake Interval Mantissa. + * @requestor: TWT Session requestor or responder. + * @implicit: implicit or Explicit TWT session. + * @flow_type: Announced or Un-Announced TWT session. + * @flow_id: Flow ID. + * @bcast_twt_id: Broadcast TWT ID. + * @protection: Protection, Not used. + * @twt_channel: TWT Channel, Not used. + * @twt_info_frame_disabled: TWT information frame disabled, Not used. + * @min_twt_unit: Nominal Minimum Wake Duration Unit. + * @teardown_all_twt: Teardown All TWT. + */ +struct brcmf_twt_params { + enum ifx_twt_oper twt_oper; + enum ifx_twt_param_nego_type negotiation_type; + enum ifx_twt_oper_setup_cmd_type setup_cmd; + u8 dialog_token; + u64 twt; + u64 twt_offset; + u8 min_twt; + u8 exponent; + u16 mantissa; + u8 requestor; + u8 trigger; + u8 implicit; + u8 flow_type; + u8 flow_id; + u8 bcast_twt_id; + u8 protection; + u8 twt_channel; + u8 twt_info_frame_disabled; + u8 min_twt_unit; + u8 teardown_all_twt; +}; + +/** + * struct brcmf_twt_session - TWT session structure. + * + * @ifidx: interface index. + * @bsscfgidx: bsscfg index. + * @peer: TWT peer address. + * @state: TWT session state, refer enum brcmf_twt_session_state. + * @twt_params: TWT session parameters. + * @list: linked list. + */ +struct brcmf_twt_session { + u8 ifidx; + s32 bsscfgidx; + struct ether_addr peer_addr; + enum brcmf_twt_session_state state; + struct brcmf_twt_params twt_params; + struct list_head list; +}; + +/** + * enum brcmf_twt_wake_time_type - Type of the struct members wake_time_{h/l} in the + * TWT Setup descriptor struct brcmf_twt_sdesc. + * + * @BRCMF_TWT_WAKE_TIME_TYPE_BSS: wake_time_{h/l} is the BSS TSF tiume. + * @BRCMF_TWT_WAKE_TIME_TYPE_OFFSET: wake_time_{h/l} is an offset of TSF time + * when the iovar is processed. + * @BRCMF_TWT_WAKE_TIME_TYPE_AUTO: The target wake time is chosen internally by the Firmware. + */ +enum brcmf_twt_wake_time_type { + BRCMF_TWT_WAKE_TIME_TYPE_BSS, + BRCMF_TWT_WAKE_TIME_TYPE_OFFSET, + BRCMF_TWT_WAKE_TIME_TYPE_AUTO +}; + +/** + * struct brcmf_twt_sdesc - TWT Setup Descriptor. + * + * @setup_cmd: Setup command and event type. Refer enum ifx_twt_oper_setup_cmd_type. + * @flow_flags: Flow attributes, Refer enum brcmf_twt_flow_flag. + * @flow_id: Flow ID, Range 0-7. Set to 0xFF for auto assignment. + * @wake_type: wake_time_{h/l} type, Refer enum brcmf_twt_wake_time_type. + * @wake_time_h: Target Wake Time, high 32 bits. + * @wake_time_l: Target Wake Time, Low 32 bits. + * @wake_dur: Target Wake Duration in unit of uS. + * @wake_int: Target Wake Interval. + * @btwt_persistence: Broadcast TWT Persistence. + * @wake_int_max: Max Wake interval(uS) for TWT. + * @duty_cycle_min: Min Duty cycle for TWT(Percentage). + * @pad: 1 byte pad. + * @bid: Brodacst TWT ID, Range 0-31. Set to 0xFF for auto assignment. + * @channel: TWT channel - Not used. + * @negotiation_type: Negotiation Type, Refer enum ifx_twt_param_nego_type. + * @frame_recomm: Frame recommendation for broadcast TWTs - Not used. + */ +struct brcmf_twt_sdesc { + u8 setup_cmd; + u8 flow_flags; + u8 flow_id; + u8 wake_type; + u32 wake_time_h; + u32 wake_time_l; + u32 wake_dur; + u32 wake_int; + u32 btwt_persistence; + u32 wake_int_max; + u8 duty_cycle_min; + u8 pad; + u8 bid; + u8 channel; + u8 negotiation_type; + u8 frame_recomm; +}; + +/** + * struct brcmf_twt_setup_event - TWT Setup Completion event data from firmware TWT module + * + * @version: Structure version. + * @length:the byte count of fields from 'dialog' onwards. + * @dialog: the dialog token user supplied to the TWT setup API. + * @pad: 3 byte Pad. + * @status: Event status. + */ +struct brcmf_twt_setup_event { + u16 version; + u16 length; + u8 dialog; + u8 pad[3]; + s32 status; + /* enum brcmf_twt_sdesc sdesc; */ +}; + +/** + * struct brcmf_twt_setup_oper - TWT iovar Setup operation subcmd data to firmware TWT module + * + * @version: Structure version. + * @length: data length (starting after this field). + * @peer: TWT peer address. + * @pad: 2 byte Pad. + * @sdesc: TWT setup descriptor. + */ +struct brcmf_twt_setup_oper { + u16 version; + u16 length; + struct ether_addr peer; + u8 pad[2]; + struct brcmf_twt_sdesc sdesc; +}; + +/** + * struct brcmf_twt_teardesc - TWT Teardown descriptor. + * + * @negotiation_type: Negotiation Type: Refer enum ifx_twt_param_nego_type. + * @flow_id: Flow ID: Range 0-7. Set to 0xFF for auto assignment. + * @bid: Brodacst TWT ID: Range 0-31. Set to 0xFF for auto assignment. + * @alltwt: Teardown all TWT sessions: set to 0 or 1. + */ +struct brcmf_twt_teardesc { + u8 negotiation_type; + u8 flow_id; + u8 bid; + u8 alltwt; +}; + +/** + * struct brcmf_twt_teardown_event - TWT Teardown Completion event data from firmware TWT module. + * + * @version: structure version. + * @length: the byte count of fields from 'status' onwards. + * @status: Event status. + */ +struct brcmf_twt_teardown_event { + u16 version; + u16 length; + s32 status; + /* enum ifx_twt_teardesc teardesc; */ +}; + +/** + * struct brcmf_twt_teardown_oper - TWT iovar Teardown operation subcmd data to firmware TWT module. + * + * @version: structure version. + * @length: data length (starting after this field). + * @peer: TWT peer address. + * @teardesc: TWT Teardown descriptor. + */ +struct brcmf_twt_teardown_oper { + u16 version; + u16 length; + struct ether_addr peer; + struct brcmf_twt_teardesc teardesc; +}; + +/** + * brcmf_twt_debugfs_create() - create debugfs entries. + * + * @drvr: driver instance. + */ +void brcmf_twt_debugfs_create(struct brcmf_pub *drvr); + +/** + * brcmf_twt_cleanup_sessions - Cleanup the TWT sessions from the driver list. + * + * @ifp: interface instatnce. + */ +s32 brcmf_twt_cleanup_sessions(struct brcmf_if *ifp); + +/** + * brcmf_notify_twt_event() - Handle the TWT Event notifications from Firmware. + * + * @ifp: interface instatnce. + * @e: event message. + * @data: payload of message, contains TWT session data. + */ +int brcmf_notify_twt_event(struct brcmf_if *ifp, const struct brcmf_event_msg *e, + void *data); + +/** + * brcmf_twt_oper() - Handle the TWT Operation requests from Userspace. + * + * @wiphy: wiphy object for cfg80211 interface. + * @wdev: wireless device. + * @twt_params: TWT session parameters. + */ +int brcmf_twt_oper(struct wiphy *wiphy, struct wireless_dev *wdev, + struct brcmf_twt_params twt_params); + +#endif /* BRCMF_TWT_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c index 85e18fb9c497a..1092e02ed84d7 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c @@ -19,6 +19,7 @@ #include "core.h" #include "common.h" #include "bcdc.h" +#include "cfg80211.h" #define IOCTL_RESP_TIMEOUT msecs_to_jiffies(2000) @@ -39,7 +40,7 @@ BRCMF_FW_DEF(43143, "brcmfmac43143"); BRCMF_FW_DEF(43236B, "brcmfmac43236b"); BRCMF_FW_DEF(43242A, "brcmfmac43242a"); BRCMF_FW_DEF(43569, "brcmfmac43569"); -BRCMF_FW_DEF(4373, "brcmfmac4373"); +CY_FW_DEF(4373, "cyfmac4373"); static const struct brcmf_firmware_mapping brcmf_usb_fwnames[] = { BRCMF_FW_ENTRY(BRCM_CC_43143_CHIP_ID, 0xFFFFFFFF, 43143), @@ -1154,11 +1155,24 @@ struct brcmf_usbdev *brcmf_usb_attach(struct brcmf_usbdev_info *devinfo, return NULL; } -static int brcmf_usb_get_blob(struct device *dev, const struct firmware **fw, - enum brcmf_blob_type type) +static +int brcmf_usb_get_fwname(struct device *dev, const char *ext, u8 *fw_name) { - /* No blobs for USB devices... */ - return -ENOENT; + struct brcmf_bus *bus = dev_get_drvdata(dev); + struct brcmf_fw_request *fwreq; + struct brcmf_fw_name fwnames[] = { + { ext, fw_name }, + }; + + fwreq = brcmf_fw_alloc_request(bus->chip, bus->chiprev, + brcmf_usb_fwnames, + ARRAY_SIZE(brcmf_usb_fwnames), + fwnames, ARRAY_SIZE(fwnames)); + if (!fwreq) + return -ENOMEM; + + kfree(fwreq); + return 0; } static const struct brcmf_bus_ops brcmf_usb_bus_ops = { @@ -1167,7 +1181,7 @@ static const struct brcmf_bus_ops brcmf_usb_bus_ops = { .txdata = brcmf_usb_tx, .txctl = brcmf_usb_tx_ctlpkt, .rxctl = brcmf_usb_rx_ctlpkt, - .get_blob = brcmf_usb_get_blob, + .get_fwname = brcmf_usb_get_fwname, }; #define BRCMF_USB_FW_CODE 0 @@ -1206,8 +1220,14 @@ static void brcmf_usb_probe_phase2(struct device *dev, int ret, if (ret) goto error; + if (BRCMF_FWCON_ON()) { + ret = brcmf_fwlog_attach(devinfo->dev); + if (ret) + goto error; + } + /* Attach to the common driver interface */ - ret = brcmf_attach(devinfo->dev); + ret = brcmf_attach(devinfo->dev, true); if (ret) goto error; @@ -1282,9 +1302,17 @@ static int brcmf_usb_probe_cb(struct brcmf_usbdev_info *devinfo) ret = brcmf_alloc(devinfo->dev, devinfo->settings); if (ret) goto fail; - ret = brcmf_attach(devinfo->dev); + + if (BRCMF_FWCON_ON()) { + ret = brcmf_fwlog_attach(devinfo->dev); + if (ret) + goto fail; + } + + ret = brcmf_attach(devinfo->dev, true); if (ret) goto fail; + /* we are done */ complete(&devinfo->dev_init_done); return 0; @@ -1467,8 +1495,22 @@ static int brcmf_usb_suspend(struct usb_interface *intf, pm_message_t state) { struct usb_device *usb = interface_to_usbdev(intf); struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(&usb->dev); + struct brcmf_bus *bus; + struct brcmf_cfg80211_info *config; + int retry = BRCMF_PM_WAIT_MAXRETRY; brcmf_dbg(USB, "Enter\n"); + + bus = devinfo->bus_pub.bus; + config = bus->drvr->config; + while (retry && + config->pm_state == BRCMF_CFG80211_PM_STATE_SUSPENDING) { + usleep_range(10000, 20000); + retry--; + } + if (!retry && config->pm_state == BRCMF_CFG80211_PM_STATE_SUSPENDING) + brcmf_err("timed out wait for cfg80211 suspended\n"); + devinfo->bus_pub.state = BRCMFMAC_USB_STATE_SLEEP; brcmf_cancel_all_urbs(devinfo); device_set_wakeup_enable(devinfo->dev, true); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c index d07e7c7355d9d..c239c64dd18b6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c @@ -15,6 +15,7 @@ #include "cfg80211.h" #include "vendor.h" #include "fwil.h" +#include "vendor_ifx.h" static int brcmf_cfg80211_vndr_cmds_dcmd_handler(struct wiphy *wiphy, struct wireless_dev *wdev, @@ -64,6 +65,16 @@ static int brcmf_cfg80211_vndr_cmds_dcmd_handler(struct wiphy *wiphy, *(char *)(dcmd_buf + len) = '\0'; } + if (cmdhdr->cmd == BRCMF_C_SET_AP) { + if (*(int *)(dcmd_buf) == 1) { + ifp->vif->wdev.iftype = NL80211_IFTYPE_AP; + brcmf_net_setcarrier(ifp, true); + } else { + ifp->vif->wdev.iftype = NL80211_IFTYPE_STATION; + } + brcmf_cfg80211_update_proto_addr_mode(&vif->wdev); + } + if (cmdhdr->set) ret = brcmf_fil_cmd_data_set(ifp, cmdhdr->cmd, dcmd_buf, ret_len); @@ -104,6 +115,112 @@ static int brcmf_cfg80211_vndr_cmds_dcmd_handler(struct wiphy *wiphy, return ret; } +static int brcmf_cfg80211_vndr_cmds_int_get(struct brcmf_if *ifp, + u32 cmd, struct wiphy *wiphy) +{ + struct sk_buff *reply; + int get_value = 0; + int ret; + + ret = brcmf_fil_cmd_int_get(ifp, cmd, &get_value); + if (ret) + brcmf_err("Command %u get failure. Error : %d\n", cmd, ret); + + reply = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(int)); + nla_put_nohdr(reply, sizeof(int), &get_value); + ret = cfg80211_vendor_cmd_reply(reply); + if (ret) + brcmf_err("Command %u failure. Error : %d\n", cmd, ret); + return ret; +} + +static int brcmf_cfg80211_vndr_cmds_int_set(struct brcmf_if *ifp, int val, u32 cmd) +{ + int ret; + + ret = brcmf_fil_cmd_int_set(ifp, cmd, val); + if (ret < 0) + brcmf_err("Command %u set failure. Error : %d\n", cmd, ret); + return ret; +} + +static int brcmf_cfg80211_vndr_cmds_frameburst(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret; + int val = *(int *)data; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (val == 0x0 || val == 0x1) { + ret = brcmf_cfg80211_vndr_cmds_int_set(ifp, val, + BRCMF_C_SET_FAKEFRAG); + } else if (val == 0xff) { + ret = brcmf_cfg80211_vndr_cmds_int_get(ifp, + BRCMF_C_GET_FAKEFRAG, + wiphy); + } else { + brcmf_err("Invalid Input\n"); + ret = -EINVAL; + } + + return ret; +} + +s32 +brcmf_wiphy_phy_temp_evt_handler(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) + +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct sk_buff *skb; + struct nlattr *phy_temp_data; + u32 version, temp, tempdelta; + struct brcmf_phy_temp_evt *phy_temp_evt; + + phy_temp_evt = (struct brcmf_phy_temp_evt *)data; + + version = le32_to_cpu(phy_temp_evt->version); + temp = le32_to_cpu(phy_temp_evt->temp); + tempdelta = le32_to_cpu(phy_temp_evt->tempdelta); + + skb = cfg80211_vendor_event_alloc(wiphy, NULL, + sizeof(*phy_temp_evt), + BRCMF_VNDR_EVTS_PHY_TEMP, + GFP_KERNEL); + + if (!skb) { + brcmf_dbg(EVENT, "NO MEM: can't allocate skb for vendor PHY_TEMP_EVENT\n"); + return -ENOMEM; + } + + phy_temp_data = nla_nest_start(skb, NL80211_ATTR_VENDOR_EVENTS); + if (!phy_temp_data) { + nla_nest_cancel(skb, phy_temp_data); + kfree_skb(skb); + brcmf_dbg(EVENT, "skb could not nest vendor attributes\n"); + return -EMSGSIZE; + } + + if (nla_put_u32(skb, BRCMF_NLATTR_VERS, version) || + nla_put_u32(skb, BRCMF_NLATTR_PHY_TEMP, temp) || + nla_put_u32(skb, BRCMF_NLATTR_PHY_TEMPDELTA, tempdelta)) { + kfree_skb(skb); + brcmf_dbg(EVENT, "NO ROOM in skb for vendor PHY_TEMP_EVENT\n"); + return -EMSGSIZE; + } + + nla_nest_end(skb, phy_temp_data); + + cfg80211_vendor_event(skb, GFP_KERNEL); + return 0; +} + const struct wiphy_vendor_command brcmf_vendor_cmds[] = { { { @@ -115,4 +232,122 @@ const struct wiphy_vendor_command brcmf_vendor_cmds[] = { .policy = VENDOR_CMD_RAW_DATA, .doit = brcmf_cfg80211_vndr_cmds_dcmd_handler }, + { + { + .vendor_id = BROADCOM_OUI, + .subcmd = BRCMF_VNDR_CMDS_FRAMEBURST + }, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV, + .policy = VENDOR_CMD_RAW_DATA, + .doit = brcmf_cfg80211_vndr_cmds_frameburst + }, + { + IFX_SUBCMD(DCMD, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + brcmf_cfg80211_vndr_cmds_dcmd_handler) + }, + { + IFX_SUBCMD(FRAMEBURST, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + brcmf_cfg80211_vndr_cmds_frameburst) + }, + { + IFX_SUBCMD(MUEDCA_OPT, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_muedca_opt) + }, + { + IFX_SUBCMD(LDPC, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_ldpc_cap) + }, + { + IFX_SUBCMD(AMSDU, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_amsdu) + }, + { + IFX_SUBCMD(TWT, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + ifx_vendor_attr_twt_policy, + ifx_cfg80211_vndr_cmds_twt), + .maxattr = IFX_VENDOR_ATTR_TWT_MAX + }, + { + IFX_SUBCMD(OCE, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_oce_enable) + }, + { + IFX_SUBCMD(BSSCOLOR, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_bsscolor) + }, + { + IFX_SUBCMD(RAND_MAC, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_randmac) + }, + { + IFX_SUBCMD(MBO, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + ifx_vendor_attr_mbo_policy, + ifx_cfg80211_vndr_cmds_mbo), + .maxattr = IFX_VENDOR_ATTR_MBO_MAX + }, + { + IFX_SUBCMD(MPC, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_mpc) + }, + { + IFX_SUBCMD(GIANTRX, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + VENDOR_CMD_RAW_DATA, + ifx_cfg80211_vndr_cmds_giantrx) + }, + { + IFX_SUBCMD(WNM, + (WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_NETDEV), + ifx_vendor_attr_wnm_policy, + ifx_cfg80211_vndr_cmds_wnm), + .maxattr = IFX_VENDOR_ATTR_WNM_MAX + }, +}; + +const struct nl80211_vendor_cmd_info brcmf_vendor_events[] = { + { + .vendor_id = BROADCOM_OUI, + .subcmd = BRCMF_VNDR_EVTS_PHY_TEMP, + }, }; + +int get_brcmf_num_vndr_cmds(void) +{ + int num = ARRAY_SIZE(brcmf_vendor_cmds); + + return num; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h index 418f33ea6fd3f..adc559e12daef 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h @@ -11,9 +11,15 @@ enum brcmf_vndr_cmds { BRCMF_VNDR_CMDS_UNSPEC, BRCMF_VNDR_CMDS_DCMD, + BRCMF_VNDR_CMDS_FRAMEBURST, BRCMF_VNDR_CMDS_LAST }; +enum brcmf_vndr_evts { + BRCMF_VNDR_EVTS_PHY_TEMP, + BRCMF_VNDR_EVTS_LAST +}; + /** * enum brcmf_nlattrs - nl80211 message attributes * @@ -25,11 +31,21 @@ enum brcmf_nlattrs { BRCMF_NLATTR_LEN, BRCMF_NLATTR_DATA, + BRCMF_NLATTR_VERS, + BRCMF_NLATTR_PHY_TEMP, + BRCMF_NLATTR_PHY_TEMPDELTA, __BRCMF_NLATTR_AFTER_LAST, BRCMF_NLATTR_MAX = __BRCMF_NLATTR_AFTER_LAST - 1 }; +/* structure of event sent up by firmware: is this the right place for it? */ +struct brcmf_phy_temp_evt { + __le32 version; + __le32 temp; + __le32 tempdelta; +} __packed; + /** * struct brcmf_vndr_dcmd_hdr - message header for cfg80211 vendor command dcmd * support @@ -49,5 +65,10 @@ struct brcmf_vndr_dcmd_hdr { }; extern const struct wiphy_vendor_command brcmf_vendor_cmds[]; +extern const struct nl80211_vendor_cmd_info brcmf_vendor_events[]; +s32 brcmf_wiphy_phy_temp_evt_handler(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data); +int get_brcmf_num_vndr_cmds(void); #endif /* _vendor_h_ */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c new file mode 100644 index 0000000000000..8ca73bd5d4d6b --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.c @@ -0,0 +1,710 @@ +/* Infineon WLAN driver: vendor specific implement + * + * Copyright 2022-2023 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#include +#include "core.h" +#include "cfg80211.h" +#include "debug.h" +#include "fwil.h" +#include "vendor_ifx.h" +#include "xtlv.h" +#include "twt.h" + +static int ifx_cfg80211_vndr_send_cmd_reply(struct wiphy *wiphy, + const void *data, int len) +{ + struct sk_buff *skb; + + /* Alloc the SKB for vendor_event */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, len); + if (unlikely(!skb)) { + brcmf_err("skb alloc failed\n"); + return -ENOMEM; + } + + /* Push the data to the skb */ + nla_put_nohdr(skb, len, data); + return cfg80211_vendor_cmd_reply(skb); +} + +static void +ifx_cfgvendor_twt_parse_params(const struct nlattr *attr_iter, + struct brcmf_twt_params *twt_params) +{ + int tmp, twt_param; + const struct nlattr *twt_param_iter; + + nla_for_each_nested(twt_param_iter, attr_iter, tmp) { + twt_param = nla_type(twt_param_iter); + switch (twt_param) { + case IFX_VENDOR_ATTR_TWT_PARAM_NEGO_TYPE: + twt_params->negotiation_type = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_SETUP_CMD_TYPE: + twt_params->setup_cmd = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_DIALOG_TOKEN: + twt_params->dialog_token = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME: + twt_params->twt = nla_get_u64(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME_OFFSET: + twt_params->twt_offset = nla_get_u64(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION: + twt_params->min_twt = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_EXPONENT: + twt_params->exponent = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_MANTISSA: + twt_params->mantissa = nla_get_u16(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_REQUESTOR: + twt_params->requestor = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_TRIGGER: + twt_params->trigger = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_IMPLICIT: + twt_params->implicit = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_FLOW_TYPE: + twt_params->flow_type = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_FLOW_ID: + twt_params->flow_id = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_BCAST_TWT_ID: + twt_params->bcast_twt_id = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_PROTECTION: + twt_params->protection = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_CHANNEL: + twt_params->twt_channel = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_TWT_INFO_FRAME_DISABLED: + twt_params->twt_info_frame_disabled = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION_UNIT: + twt_params->min_twt_unit = nla_get_u8(twt_param_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAM_TEARDOWN_ALL_TWT: + twt_params->teardown_all_twt = nla_get_u8(twt_param_iter); + break; + default: + brcmf_dbg(TRACE, "Unknown TWT param %d, skipping\n", + twt_param); + break; + } + } +} + +int ifx_cfg80211_vndr_cmds_twt(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int len) +{ + int tmp, attr_type; + const struct nlattr *attr_iter; + + struct brcmf_twt_params twt_params = { + .twt_oper = 0, + .negotiation_type = IFX_TWT_PARAM_NEGO_TYPE_ITWT, + .setup_cmd = IFX_TWT_OPER_SETUP_CMD_TYPE_REQUEST, + .dialog_token = 1, + .twt = 0, + .twt_offset = 0, + .requestor = 1, + .trigger = 0, + .implicit = 1, + .flow_type = 0, + .flow_id = 0, + .bcast_twt_id = 0, + .protection = 0, + .twt_channel = 0, + .twt_info_frame_disabled = 0, + .min_twt_unit = 0, + .teardown_all_twt = 0 + }; + + nla_for_each_attr(attr_iter, data, len, tmp) { + attr_type = nla_type(attr_iter); + + switch (attr_type) { + case IFX_VENDOR_ATTR_TWT_OPER: + twt_params.twt_oper = nla_get_u8(attr_iter); + break; + case IFX_VENDOR_ATTR_TWT_PARAMS: + ifx_cfgvendor_twt_parse_params(attr_iter, &twt_params); + break; + default: + brcmf_dbg(TRACE, "Unknown TWT attribute %d, skipping\n", + attr_type); + break; + } + } + + return (int)brcmf_twt_oper(wiphy, wdev, twt_params); +} + +int ifx_cfg80211_vndr_cmds_bsscolor(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + struct bcm_xtlv *he_tlv; + u8 val = *(u8 *)data; + u8 param[8] = {0}; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + he_tlv = (struct bcm_xtlv *)param; + he_tlv->id = cpu_to_le16(IFX_HE_CMD_BSSCOLOR); + + if (val == 0xa) { + /* To get fw iovars of the form "wl he bsscolor" using iw, + * call the parent iovar "he" with the subcmd filled and + * passed along ./iw dev wlan0 vendor recv 0x000319 0x10 0xa + */ + ret = brcmf_fil_iovar_data_get(ifp, "he", param, sizeof(param)); + if (ret) { + brcmf_err("get he bss_color error:%d\n", ret); + } else { + brcmf_dbg(INFO, "get he bss_color: %d\n", *param); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, param, 1); + } + } else { + brcmf_dbg(INFO, "not support set bsscolor during runtime!\n"); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_muedca_opt(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + struct bcm_xtlv *he_tlv; + u8 val = *(u8 *)data; + u8 param[8] = {0}; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + he_tlv = (struct bcm_xtlv *)param; + he_tlv->id = cpu_to_le16(IFX_HE_CMD_MUEDCA_OPT); + + if (val == 0xa) { + /* To get fw iovars of the form "wl he muedca_opt_enable" + * using iw, call the parent iovar "he" with the subcmd + * filled and passed along + * ./iw dev wlan0 vendor recv 0x000319 0xb 0xa + */ + ret = brcmf_fil_iovar_data_get(ifp, "he", param, sizeof(param)); + if (ret) { + brcmf_err("get he muedca_opt_enable error:%d\n", ret); + } else { + brcmf_dbg(INFO, + "get he muedca_opt_enable: %d\n", *param); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, param, 1); + } + } else { + he_tlv->len = cpu_to_le16(1); + he_tlv->data[0] = val; + ret = brcmf_fil_iovar_data_set(ifp, "he", + param, sizeof(param)); + if (ret) + brcmf_err("set he muedca_opt_enable error:%d\n", ret); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_amsdu(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + int val = *(s32 *)data; + s32 get_amsdu = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (val == 0xa) { + ret = brcmf_fil_iovar_int_get(ifp, "amsdu", &get_amsdu); + if (ret) { + brcmf_err("get amsdu error:%d\n", ret); + + return ret; + } + + brcmf_dbg(INFO, "get amsdu: %d\n", get_amsdu); + ifx_cfg80211_vndr_send_cmd_reply( + wiphy, &get_amsdu, sizeof(int)); + } else { + ret = brcmf_fil_iovar_int_set(ifp, "amsdu", val); + if (ret) + brcmf_err("set amsdu error:%d\n", ret); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_ldpc_cap(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + int val = *(s32 *)data; + s32 buf = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (val == 0xa) { + ret = brcmf_fil_iovar_int_get(ifp, "ldpc_cap", &buf); + if (ret) { + brcmf_err("get ldpc_cap error:%d\n", ret); + + return ret; + } + + brcmf_dbg(INFO, "get ldpc_cap: %d\n", buf); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, &buf, sizeof(int)); + } else { + ret = brcmf_fil_iovar_int_set(ifp, "ldpc_cap", val); + if (ret) + brcmf_err("set ldpc_cap error:%d\n", ret); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_oce_enable(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + struct bcm_iov_buf *oce_iov; + struct bcm_xtlv *oce_xtlv; + u8 val = *(u8 *)data; + u8 param[16] = {0}; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + oce_iov = (struct bcm_iov_buf *)param; + oce_iov->version = cpu_to_le16(IFX_OCE_IOV_VERSION); + oce_iov->id = cpu_to_le16(IFX_OCE_CMD_ENABLE); + oce_xtlv = (struct bcm_xtlv *)oce_iov->data; + + if (val == 0xa) { + /* To get fw iovars of the form "wl oce enable" + * using iw, call the parent iovar "oce" with the subcmd + * filled and passed along + * ./iw dev wlan0 vendor recv 0x000319 0xf 0xa + */ + ret = brcmf_fil_iovar_data_get(ifp, "oce", + param, sizeof(param)); + if (ret) { + brcmf_err("get oce enable error:%d\n", ret); + } else { + brcmf_dbg(INFO, + "get oce enable: %d\n", oce_xtlv->data[0]); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, oce_xtlv->data, + sizeof(int)); + } + } else { + oce_iov->len = cpu_to_le16(8); + oce_xtlv->id = cpu_to_le16(IFX_OCE_XTLV_ENABLE); + oce_xtlv->len = cpu_to_le16(1); + oce_xtlv->data[0] = val; + ret = brcmf_fil_iovar_data_set(ifp, "oce", + param, sizeof(param)); + if (ret) + brcmf_err("set oce enable error:%d\n", ret); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_randmac(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + int ret = 0; + struct ifx_randmac iov_buf = {0}; + u8 val = *(u8 *)data; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + iov_buf.version = WL_RANDMAC_API_VERSION; + iov_buf.subcmd_id = WL_RANDMAC_SUBCMD_ENABLE; + iov_buf.len = offsetof(struct ifx_randmac, data); + + if (val == 0x1) { + /* To set fw iovars of the form "wl randmac enable" using iw, call the + * parent iovar "randmac" with the subcmd filled and passed along + * ./iw dev wlan0 vendor send 0x000319 0x11 0x1 + */ + ret = brcmf_fil_bsscfg_data_set(ifp, "randmac", (void *)&iov_buf, iov_buf.len); + if (ret) + brcmf_err("Failed to set randmac enable: %d\n", ret); + } else if (val == 0x0) { + iov_buf.subcmd_id = WL_RANDMAC_SUBCMD_DISABLE; + /* To set fw iovars of the form "wl randmac disable" using iw, call the + * parent iovar "randmac" with the subcmd filled and passed along + * ./iw dev wlan0 vendor send 0x000319 0x11 0x0 + */ + ret = brcmf_fil_bsscfg_data_set(ifp, "randmac", (void *)&iov_buf, iov_buf.len); + if (ret) + brcmf_err("Failed to set randmac disable: %d\n", ret); + } else if (val == 0xa) { + int result_data = 0; + struct ifx_randmac *iov_resp = NULL; + u8 buf[64] = {0}; + /* To get fw iovars of the form "wl randmac" using iw, call the + * parent iovar "randmac" with the subcmd filled and passed along + * ./iw dev wlan0 vendor recv 0x000319 0x11 0xa + */ + memcpy(buf, (void *)&iov_buf, iov_buf.len); + ret = brcmf_fil_iovar_data_get(ifp, "randmac", (void *)buf, sizeof(buf)); + if (ret) { + brcmf_err("Failed to get randmac enable or disable: %d\n", ret); + } else { + iov_resp = (struct ifx_randmac *)buf; + if (iov_resp->subcmd_id == WL_RANDMAC_SUBCMD_ENABLE) + result_data = 1; + ifx_cfg80211_vndr_send_cmd_reply(wiphy, &result_data, sizeof(int)); + } + } + return ret; +} + +int ifx_cfg80211_vndr_cmds_mbo(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + int tmp, attr_type, mbo_param; + const struct nlattr *attr_iter, *mbo_param_iter; + + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + struct bcm_iov_buf *mbo_iov; + struct bcm_xtlv *mbo_xtlv; + u8 param[64] = {0}; + u16 buf_len = 0, buf_len_start = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + mbo_iov = (struct bcm_iov_buf *)param; + mbo_iov->version = cpu_to_le16(IFX_MBO_IOV_VERSION); + mbo_xtlv = (struct bcm_xtlv *)mbo_iov->data; + buf_len_start = sizeof(param) - sizeof(struct bcm_iov_buf); + buf_len = buf_len_start; + + nla_for_each_attr(attr_iter, data, len, tmp) { + attr_type = nla_type(attr_iter); + + switch (attr_type) { + case IFX_VENDOR_ATTR_MBO_CMD: + mbo_iov->id = cpu_to_le16(nla_get_u8(attr_iter)); + break; + case IFX_VENDOR_ATTR_MBO_PARAMS: + nla_for_each_nested(mbo_param_iter, attr_iter, tmp) { + mbo_param = nla_type(mbo_param_iter); + + switch (mbo_param) { + case IFX_VENDOR_ATTR_MBO_PARAM_OPCLASS: + { + u8 op_class; + + op_class = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_OPCLASS, + &op_class, sizeof(op_class), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_CHAN: + { + u8 chan; + + chan = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_CHAN, + &chan, sizeof(chan), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_PREFERENCE: + { + u8 pref; + + pref = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_PREFERENCE, + &pref, sizeof(pref), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_REASON_CODE: + { + u8 reason; + + reason = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_REASON_CODE, + &reason, sizeof(reason), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_CELL_DATA_CAP: + { + u8 cell_data_cap; + + cell_data_cap = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_CELL_DATA_CAP, + &cell_data_cap, sizeof(cell_data_cap), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_COUNTERS: + break; + case IFX_VENDOR_ATTR_MBO_PARAM_ENABLE: + { + u8 enable; + + enable = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_ENABLE, + &enable, sizeof(enable), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_SUB_ELEM_TYPE: + { + u8 type; + + type = nla_get_u8(mbo_param_iter); + brcmf_pack_xtlv(IFX_VENDOR_ATTR_MBO_PARAM_SUB_ELEM_TYPE, + &type, sizeof(type), + (char **)&mbo_xtlv, &buf_len); + } + break; + case IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_START_OFFSET: + case IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_RSSI_DELTA: + case IFX_VENDOR_ATTR_MBO_PARAM_ANQP_CELL_SUPP: + case IFX_VENDOR_ATTR_MBO_PARAM_BIT_MASK: + case IFX_VENDOR_ATTR_MBO_PARAM_ASSOC_DISALLOWED: + case IFX_VENDOR_ATTR_MBO_PARAM_CELLULAR_DATA_PREF: + return -EOPNOTSUPP; + default: + brcmf_err("unknown mbo param attr:%d\n", mbo_param); + return -EINVAL; + } + } + break; + default: + brcmf_err("Unknown MBO attribute %d, skipping\n", + attr_type); + return -EINVAL; + } + } + + buf_len = buf_len_start - buf_len; + mbo_xtlv->len = cpu_to_le16(buf_len); + mbo_iov->len = cpu_to_le16(buf_len); + buf_len += sizeof(struct bcm_iov_buf); + ret = brcmf_fil_iovar_data_set(ifp, "mbo", param, buf_len); + + if (ret) + brcmf_err("set mbo enable error:%d\n", ret); + + return ret; +} + +int ifx_cfg80211_vndr_cmds_mpc(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + int val = *(s32 *)data; + s32 buf = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (val == 0xa) { + ret = brcmf_fil_iovar_int_get(ifp, "mpc", &buf); + if (ret) { + brcmf_err("get mpc error:%d\n", ret); + return ret; + } + + brcmf_dbg(INFO, "get mpc: %d\n", buf); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, &buf, sizeof(int)); + } else { + ret = brcmf_fil_iovar_int_set(ifp, "mpc", val); + if (ret) + brcmf_err("set mpc error:%d\n", ret); + } + + return ret; +} + +int ifx_cfg80211_vndr_cmds_giantrx(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len) +{ + int ret = 0; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + int val = *(s32 *)data; + s32 buf = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + + if (val == 0xa) { + ret = brcmf_fil_iovar_int_get(ifp, "giantrx", &buf); + if (ret) { + brcmf_err("get giantrx error:%d\n", ret); + return ret; + } + + brcmf_dbg(INFO, "get giantrx: %d\n", buf); + ifx_cfg80211_vndr_send_cmd_reply(wiphy, &buf, sizeof(int)); + } else { + brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); + ret = brcmf_fil_iovar_int_set(ifp, "giantrx", val); + brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); + if (ret) + brcmf_err("set giantrx error:%d\n", ret); + } + return ret; +} + +int ifx_cfg80211_vndr_cmds_wnm(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len) +{ + int tmp, attr_type = 0, wnm_param = 0, ret = 0; + const struct nlattr *attr_iter, *wnm_param_iter; + + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + u8 param[64] = {0}, get_info = 0; + u16 buf_len = 0, wnm_id = 0; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + ifp = vif->ifp; + nla_for_each_attr(attr_iter, data, len, tmp) { + attr_type = nla_type(attr_iter); + + switch (attr_type) { + case IFX_VENDOR_ATTR_WNM_CMD: + wnm_id = cpu_to_le16(nla_get_u8(attr_iter)); + break; + case IFX_VENDOR_ATTR_WNM_PARAMS: + nla_for_each_nested(wnm_param_iter, attr_iter, tmp) { + wnm_param = nla_type(wnm_param_iter); + switch (wnm_param) { + case IFX_VENDOR_ATTR_WNM_PARAM_GET_INFO: + { + get_info = (int)nla_get_u8(wnm_param_iter); + } + break; + case IFX_VENDOR_ATTR_WNM_PARAM_IDLE_PERIOD: + { + int period; + + period = (int)nla_get_u8(wnm_param_iter); + memcpy(¶m[buf_len], &period, sizeof(period)); + buf_len += sizeof(period); + } + break; + case IFX_VENDOR_ATTR_WNM_PARAM_PROTECTION_OPT: + { + int option; + + option = (int)nla_get_u8(wnm_param_iter); + memcpy(¶m[buf_len], &option, sizeof(option)); + buf_len += sizeof(option); + } + break; + default: + brcmf_err("unknown wnm param attr:%d\n", wnm_param); + return -EINVAL; + } + } + break; + default: + brcmf_err("Unknown wnm attribute %d, skipping\n", + attr_type); + return -EINVAL; + } + } + + switch (wnm_id) { + case IFX_WNM_CMD_IOV_WNM_MAXIDLE: + { + if (get_info) { + int get_period = 0; + + ret = brcmf_fil_iovar_int_get(ifp, "wnm_maxidle", &get_period); + if (!ret) + ret = ifx_cfg80211_vndr_send_cmd_reply( + wiphy, &get_period, sizeof(get_period)); + } else + ret = brcmf_fil_iovar_data_set(ifp, "wnm_maxidle", param, buf_len); + } + break; + + default: + brcmf_err("unsupport wnm cmd:%d\n", wnm_id); + return -EINVAL; + } + + if (ret) + brcmf_err("wnm %s error:%d\n", get_info?"get":"set", ret); + + return ret; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h new file mode 100644 index 0000000000000..f2fe410030cd2 --- /dev/null +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor_ifx.h @@ -0,0 +1,719 @@ +/* Infineon WLAN driver: vendor specific implement + * + * Copyright 2022-2023 Cypress Semiconductor Corporation (an Infineon company) + * or an affiliate of Cypress Semiconductor Corporation. All rights reserved. + * This software, including source code, documentation and related materials + * ("Software") is owned by Cypress Semiconductor Corporation or one of its + * affiliates ("Cypress") and is protected by and subject to + * worldwide patent protection (United States and foreign), + * United States copyright laws and international treaty provisions. + * Therefore, you may use this Software only as provided in the license agreement + * accompanying the software package from which you obtained this Software ("EULA"). + * If no EULA applies, Cypress hereby grants you a personal, non-exclusive, + * non-transferable license to copy, modify, and compile the Software source code + * solely for use in connection with Cypress's integrated circuit products. + * Any reproduction, modification, translation, compilation, or representation + * of this Software except as specified above is prohibited without + * the expresswritten permission of Cypress. + * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Cypress reserves the right to make changes to the Software without notice. + * Cypress does not assume any liability arising out of the application or + * use of the Software or any product or circuit described in the Software. + * Cypress does not authorize its products for use in any products where a malfunction + * or failure of the Cypress product may reasonably be expected to result in + * significant property damage, injury or death ("High Risk Product"). + * By including Cypress's product in a High Risk Product, the manufacturer + * of such system or application assumes all risk of such use and in doing so + * agrees to indemnify Cypress against all liability. + */ + +#ifndef IFX_VENDOR_H +#define IFX_VENDOR_H + +#include +#include + +/* This file is a registry of identifier assignments from the Infineon + * OUI 00:03:19 for purposes other than MAC address assignment. New identifiers + * can be assigned through normal review process for changes to the upstream + * hostap.git repository. + */ +#define OUI_IFX 0x000319 + +#define SCMD(_CMD) IFX_VENDOR_SCMD_##_CMD +#define IFX_SUBCMD(_CMD, _FLAGS, _POLICY, _FN) \ + { \ + .vendor_id = OUI_IFX, \ + .subcmd = SCMD(_CMD) \ + }, \ + .flags = (_FLAGS), \ + .policy = (_POLICY), \ + .doit = (_FN) + +struct bcm_iov_buf { + u16 version; + u16 len; + u16 id; + u16 data[1]; +}; + +/* + * enum ifx_nl80211_vendor_subcmds - IFX nl80211 vendor command identifiers + * + * @IFX_VENDOR_SCMD_UNSPEC: Reserved value 0 + * + * @IFX_VENDOR_SCMD_DCMD: Handle the Dongle commands triggered from the userspace utilities. + * These commands will be passed to the Dongle for processing. + * + * @IFX_VENDOR_SCMD_FRAMEBURST: Control the Frameburst feature. This feature allows more + * efficient use of the airtime between the transmitting and receiving WLAN devices. + * + * @IFX_VENDOR_SCMD_ACS: Configure the Automatic Channel Selection (ACS) feature. + * + * @IFX_VENDOR_SCMD_SET_MAC_P2P_DEV: Set MAC address for a P2P Discovery device. + * Uses Vendor attribute IFX_VENDOR_ATTR_MAC_ADDR to pass the MAC address. + * + * @IFX_VENDOR_SCMD_MUEDCA_OPT: Configure Multi User Enhanced Distrubuted Channel Access (MU-EDCA). + * + * @IFX_VENDOR_SCMD_LDPC: Enable support for handling Low Density Parity Check (LDPC) Coding + * in received payload. + * + * @IFX_VENDOR_SCMD_AMSDU: Control AMSDU aggregation for both TX & RX on all the TID queues. + * + * @IFX_VENDOR_SCMD_TWT: Configure Target Wake Time (TWT) Session with the needed parameters. + * Uses Vendor attributes defined in the enum ifx_vendor_attr_twt. + * + * @IFX_VENDOR_SCMD_OCE: Configure the Optimized Connectivity Experience (OCE) functionality + * related parameters. + * + * @IFX_VENDOR_SCMD_BSSCOLOR: Set BSS Color (1-63) for AP Mode operation in HE. + * + * @IFX_VENDOR_SCMD_RAND_MAC: Configure the Random MAC module. + * + * @IFX_VENDOR_SCMD_MBO: Configure Multi Band Operation (MBO) functionality related parameters. + * + * @IFX_VENDOR_SCMD_MPC: Control the Minimum Power Consumption (MPC) feature. + * This is a STA-only power saving feature and not related to 802.11 power save. + * + * @IFX_VENDOR_SCMD_GIANTRX: Allow handling RX MGMT Packts of size 1840 bytes. + * + * @IFX_VENDOR_SCMD_PFN_CONFIG: Send the Preferred Network (PFN) information to the Dongle + * + * @IFX_VENDOR_SCMD_PFN_STATUS: Fetch the Preferred Network (PFN) information from the Dongle + * through the driver. + * + * @IFX_VENDOR_SCMD_WNM: Configure the Wireless Network Management (WNM) 802.11v functionaltiy + * related parameters. + * + * @IFX_VENDOR_SCMD_MAX: This acts as a the tail of cmds list. + * Make sure it located at the end of the list. + */ +enum ifx_nl80211_vendor_subcmds { + SCMD(UNSPEC) = 0, + SCMD(DCMD) = 1, + SCMD(RSV2) = 2, + SCMD(RSV3) = 3, + SCMD(RSV4) = 4, + SCMD(RSV5) = 5, + SCMD(FRAMEBURST) = 6, + SCMD(RSV7) = 7, + SCMD(RSV8) = 8, + SCMD(ACS) = 9, + SCMD(SET_MAC_P2P_DEV) = 10, + SCMD(MUEDCA_OPT) = 11, + SCMD(LDPC) = 12, + SCMD(AMSDU) = 13, + SCMD(TWT) = 14, + SCMD(OCE) = 15, + SCMD(BSSCOLOR) = 16, + SCMD(RAND_MAC) = 17, + SCMD(MBO) = 18, + SCMD(MPC) = 19, + SCMD(GIANTRX) = 20, + SCMD(PFN_CONFIG) = 21, + SCMD(PFN_STATUS) = 22, + SCMD(RSV22) = 23, + SCMD(RSV24) = 24, + SCMD(WNM) = 25, + SCMD(MAX) = 26 +}; + +/* + * enum ifx_vendor_attr - IFX nl80211 vendor attributes + * + * @IFX_VENDOR_ATTR_UNSPEC: Reserved value 0 + * + * @IFX_VENDOR_ATTR_LEN: Dongle Command Message Body Length. + * + * @IFX_VENDOR_ATTR_DATA: Dongle Commend Message Body. + * + * @IFX_VENDOR_ATTR_MAC_ADDR: Medium Access Control (MAC) address. + * + * @IFX_VENDOR_ATTR_MAX: This acts as a the tail of attrs list. + * Make sure it located at the end of the list. + */ +enum ifx_vendor_attr { + IFX_VENDOR_ATTR_UNSPEC = 0, + IFX_VENDOR_ATTR_LEN = 1, + IFX_VENDOR_ATTR_DATA = 2, + IFX_VENDOR_ATTR_MAC_ADDR = 3, + /* Reserved 4-10 */ + IFX_VENDOR_ATTR_MAX +}; + +#define IFX_MBO_IOV_MAJOR_VER 1 +#define IFX_MBO_IOV_MINOR_VER 1 +#define IFX_MBO_IOV_MAJOR_VER_SHIFT 8 +#define IFX_MBO_IOV_VERSION \ + ((IFX_MBO_IOV_MAJOR_VER << IFX_MBO_IOV_MAJOR_VER_SHIFT) | \ + IFX_MBO_IOV_MINOR_VER) + +enum ifx_vendor_attr_mbo_param { + IFX_VENDOR_ATTR_MBO_PARAM_UNSPEC = 0, + IFX_VENDOR_ATTR_MBO_PARAM_OPCLASS = 1, + IFX_VENDOR_ATTR_MBO_PARAM_CHAN = 2, + IFX_VENDOR_ATTR_MBO_PARAM_PREFERENCE = 3, + IFX_VENDOR_ATTR_MBO_PARAM_REASON_CODE = 4, + IFX_VENDOR_ATTR_MBO_PARAM_CELL_DATA_CAP = 5, + IFX_VENDOR_ATTR_MBO_PARAM_COUNTERS = 6, + IFX_VENDOR_ATTR_MBO_PARAM_ENABLE = 7, + IFX_VENDOR_ATTR_MBO_PARAM_SUB_ELEM_TYPE = 8, + IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_START_OFFSET = 9, + IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_RSSI_DELTA = 10, + IFX_VENDOR_ATTR_MBO_PARAM_ANQP_CELL_SUPP = 11, + IFX_VENDOR_ATTR_MBO_PARAM_BIT_MASK = 12, + IFX_VENDOR_ATTR_MBO_PARAM_ASSOC_DISALLOWED = 13, + IFX_VENDOR_ATTR_MBO_PARAM_CELLULAR_DATA_PREF = 14, + IFX_VENDOR_ATTR_MBO_PARAM_MAX = 15 +}; + +static const struct nla_policy +ifx_vendor_attr_mbo_param_policy[IFX_VENDOR_ATTR_MBO_PARAM_MAX + 1] = { + [IFX_VENDOR_ATTR_MBO_PARAM_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_OPCLASS] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_CHAN] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_PREFERENCE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_REASON_CODE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_CELL_DATA_CAP] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_COUNTERS] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_ENABLE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_SUB_ELEM_TYPE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_START_OFFSET] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_BTQ_TRIG_RSSI_DELTA] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_ANQP_CELL_SUPP] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_BIT_MASK] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_ASSOC_DISALLOWED] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_CELLULAR_DATA_PREF] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAM_MAX] = {.type = NLA_U8}, +}; + +enum ifx_vendor_attr_mbo { + IFX_VENDOR_ATTR_MBO_UNSPEC, + IFX_VENDOR_ATTR_MBO_CMD, + IFX_VENDOR_ATTR_MBO_PARAMS, + IFX_VENDOR_ATTR_MBO_MAX +}; + +static const struct nla_policy ifx_vendor_attr_mbo_policy[IFX_VENDOR_ATTR_MBO_MAX + 1] = { + [IFX_VENDOR_ATTR_MBO_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_CMD] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_MBO_PARAMS] = + NLA_POLICY_NESTED(ifx_vendor_attr_mbo_param_policy), + [IFX_VENDOR_ATTR_MBO_MAX] = {.type = NLA_U8}, +}; + +enum { + IFX_MBO_CMD_ADD_CHAN_PREF = 1, + IFX_MBO_CMD_DEL_CHAN_PREF = 2, + IFX_MBO_CMD_LIST_CHAN_PREF = 3, + IFX_MBO_CMD_CELLULAR_DATA_CAP = 4, + IFX_MBO_CMD_DUMP_COUNTERS = 5, + IFX_MBO_CMD_CLEAR_COUNTERS = 6, + IFX_MBO_CMD_FORCE_ASSOC = 7, + IFX_MBO_CMD_BSSTRANS_REJECT = 8, + IFX_MBO_CMD_SEND_NOTIF = 9, + IFX_MBO_CMD_LAST +}; + +enum { + IFX_MBO_XTLV_OPCLASS = 0x1, + IFX_MBO_XTLV_CHAN = 0x2, + IFX_MBO_XTLV_PREFERENCE = 0x3, + IFX_MBO_XTLV_REASON_CODE = 0x4, + IFX_MBO_XTLV_CELL_DATA_CAP = 0x5, + IFX_MBO_XTLV_COUNTERS = 0x6, + IFX_MBO_XTLV_ENABLE = 0x7, + IFX_MBO_XTLV_SUB_ELEM_TYPE = 0x8, + IFX_MBO_XTLV_BTQ_TRIG_START_OFFSET = 0x9, + IFX_MBO_XTLV_BTQ_TRIG_RSSI_DELTA = 0xa, + IFX_MBO_XTLV_ANQP_CELL_SUPP = 0xb, + IFX_MBO_XTLV_BIT_MASK = 0xc, + IFX_MBO_XTLV_ASSOC_DISALLOWED = 0xd, + IFX_MBO_XTLV_CELLULAR_DATA_PREF = 0xe +}; + +/* + * enum ifx_vendor_attr_twt - Attributes for the TWT vendor command + * + * @IFX_VENDOR_ATTR_TWT_UNSPEC: Reserved value 0 + * + * @IFX_VENDOR_ATTR_TWT_OPER: To specify the type of TWT operation + * to be performed. Uses attributes defined in enum ifx_twt_oper. + * + * @IFX_VENDOR_ATTR_TWT_PARAMS: Nester attributes representing the + * parameters configured for TWT. These parameters are defined in + * the enum ifx_vendor_attr_twt_param. + * + * @IFX_VENDOR_ATTR_TWT_MAX: This acts as a the tail of cmds list. + * Make sure it located at the end of the list. + */ +enum ifx_vendor_attr_twt { + IFX_VENDOR_ATTR_TWT_UNSPEC, + IFX_VENDOR_ATTR_TWT_OPER, + IFX_VENDOR_ATTR_TWT_PARAMS, + IFX_VENDOR_ATTR_TWT_MAX +}; + +/* + * enum ifx_twt_oper - TWT operation to be specified using the vendor + * attribute IFX_VENDOR_ATTR_TWT_OPER + * + * @IFX_TWT_OPER_UNSPEC: Reserved value 0 + * + * @IFX_TWT_OPER_SETUP: Setup a TWT session. Required parameters are + * obtained through the nested attrs under %IFX_VENDOR_ATTR_TWT_PARAMS. + * + * @IFX_TWT_OPER_TEARDOWN: Teardown the already negotiated TWT session. + * Required parameters are obtained through the nested attrs under + * IFX_VENDOR_ATTR_TWT_PARAMS. + * + * @IFX_TWT_OPER_MAX: This acts as a the tail of the list. + * Make sure it located at the end of the list. + */ +enum ifx_twt_oper { + IFX_TWT_OPER_UNSPEC, + IFX_TWT_OPER_SETUP, + IFX_TWT_OPER_TEARDOWN, + IFX_TWT_OPER_MAX +}; + +/* + * enum ifx_vendor_attr_twt_param - TWT parameters + * + * @IFX_VENDOR_ATTR_TWT_PARAM_UNSPEC: Reserved value 0 + * + * @IFX_VENDOR_ATTR_TWT_PARAM_NEGO_TYPE: Specifies the type of Negotiation to be + * done during Setup. The four possible types are + * 0 - Individual TWT Negotiation + * 1 - Wake TBTT Negotiation + * 2 - Broadcast TWT in Beacon + * 3 - Broadcast TWT Membership Negotiation + * + * The possible values are defined in the enum ifx_twt_param_nego_type + * + * @IFX_VENDOR_ATTR_TWT_PARAM_SETUP_CMD_TYPE: Specifies the type of TWT Setup frame + * when sent by the TWT Requesting STA + * 0 - Request + * 1 - Suggest + * 2 - Demand + * + * when sent by the TWT Responding STA. + * 3 - Grouping + * 4 - Accept + * 5 - Alternate + * 6 - Dictate + * 7 - Reject + * + * The possible values are defined in the enum ifx_twt_oper_setup_cmd_type. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_DIALOG_TOKEN: Dialog Token used by the TWT Requesting STA to + * identify the TWT Setup request/response transaction. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME: Target Wake Time TSF at which the STA has to wake up. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME_OFFSET: Target Wake Time TSF Offset from current TSF + * in microseconds. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION: Nominal Minimum TWT Wake Duration. + * Used along with %IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION_UNIT to derive Wake Duration. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_EXPONENT: TWT Wake Interval Exponent. + * Used along with %IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_MANTISSA to derive Wake Interval. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_MANTISSA: TWT Wake Interval Mantissa. + * Used along with %IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_EXPONENT to derive Wake Interval. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_REQUESTOR: Specify this is a TWT Requesting / Responding STA. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_TRIGGER: Specify Trigger based / Non-Trigger based TWT Session. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_IMPLICIT: Specify Implicit / Explicit TWT session. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_FLOW_TYPE: Specify Un-Announced / Announced TWT session. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_FLOW_ID: Flow ID is the unique identifier of an iTWT session. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_BCAST_TWT_ID: Broadcast TWT ID is the unique identifier of a + * bTWT session. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_PROTECTION: Specifies whether Tx within SP is protected. + * Set to 1 to indicate that TXOPs within the TWT SPs shall be initiated + * with a NAV protection mechanism, such as (MU) RTS/CTS or CTS-to-self frame; + * otherwise, it shall set it to 0. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_CHANNEL: TWT channel field which is set to 0, unless + * the HE STA sets up a subchannel selective transmission operation. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_TWT_INFO_FRAME_DISABLED: TWT Information frame RX handing + * disabled / enabled. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION_UNIT: Nominal Minimum TWT Wake Duration + * Unit. 0 represents unit in "256 usecs" and 1 represents unit in "TUs". + * + * @IFX_VENDOR_ATTR_TWT_PARAM_TEARDOWN_ALL_TWT: Teardown all negotiated TWT sessions. + * + * @IFX_VENDOR_ATTR_TWT_PARAM_MAX: This acts as a the tail of the list. + * Make sure it located at the end of the list. + */ +enum ifx_vendor_attr_twt_param { + IFX_VENDOR_ATTR_TWT_PARAM_UNSPEC, + IFX_VENDOR_ATTR_TWT_PARAM_NEGO_TYPE, + IFX_VENDOR_ATTR_TWT_PARAM_SETUP_CMD_TYPE, + IFX_VENDOR_ATTR_TWT_PARAM_DIALOG_TOKEN, + IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME, + IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME_OFFSET, + IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION, + IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_EXPONENT, + IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_MANTISSA, + IFX_VENDOR_ATTR_TWT_PARAM_REQUESTOR, + IFX_VENDOR_ATTR_TWT_PARAM_TRIGGER, + IFX_VENDOR_ATTR_TWT_PARAM_IMPLICIT, + IFX_VENDOR_ATTR_TWT_PARAM_FLOW_TYPE, + IFX_VENDOR_ATTR_TWT_PARAM_FLOW_ID, + IFX_VENDOR_ATTR_TWT_PARAM_BCAST_TWT_ID, + IFX_VENDOR_ATTR_TWT_PARAM_PROTECTION, + IFX_VENDOR_ATTR_TWT_PARAM_CHANNEL, + IFX_VENDOR_ATTR_TWT_PARAM_TWT_INFO_FRAME_DISABLED, + IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION_UNIT, + IFX_VENDOR_ATTR_TWT_PARAM_TEARDOWN_ALL_TWT, + IFX_VENDOR_ATTR_TWT_PARAM_MAX +}; + +/* + * enum ifx_twt_param_nego_type - TWT Session Negotiation Type Parameters + * + * @IFX_TWT_PARAM_NEGO_TYPE_ITWT: Individual TWT negotiation between TWT requesting STA + * and TWT responding STA or individual TWT announcement by TWT Responder + * + * @IFX_TWT_PARAM_NEGO_TYPE_WAKE_TBTT: Wake TBTT and Wake interval negotiation between + * TWT scheduled STA and TWT scheduling AP. + * + * @IFX_TWT_PARAM_NEGO_TYPE_BTWT_IE_BCN: Provide Broadcast TWT schedules to TWT scheduled + * STAs by including the TWT element in broadcast Managemnet frames sent by TWT + * scheduling AP. + * + * @IFX_TWT_PARAM_NEGO_TYPE_BTWT: Broadcast TWT negotiation between TWT requesting STA + * and TWT responding STA. Manage Memberships in broadcast TWT schedules by including + * the TWT element in individually addressed Management frames sent by either a TWT + * scheduled STA or a TWT scheduling AP. + * + * @IFX_TWT_PARAM_NEGO_TYPE_MAX: This acts as a the tail of the list. + * Make sure it located at the end of the list. + */ +enum ifx_twt_param_nego_type { + IFX_TWT_PARAM_NEGO_TYPE_INVALID = -1, + IFX_TWT_PARAM_NEGO_TYPE_ITWT = 0, + IFX_TWT_PARAM_NEGO_TYPE_WAKE_TBTT = 1, + IFX_TWT_PARAM_NEGO_TYPE_BTWT_IE_BCN = 2, + IFX_TWT_PARAM_NEGO_TYPE_BTWT = 3, + IFX_TWT_PARAM_NEGO_TYPE_MAX = 4 +}; + +/* + * enum ifx_vendor_attr_twt_param - TWT Session setup command types + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_REQUEST: A TWT requesting or TWT scheduled STA + * requests to join a TWT without specifying a target wake time. This type needs to + * be used only by the TWT requesting STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_SUGGEST: A TWT requesting or TWT scheduled STA requests to + * join a TWT without specifying a target wake time. This type needs to be used only + * by the TWT requesting STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_DEMAND: A TWT requesting or TWT scheduled STA requests to + * join a TWT and specifies a demanded set of TWT parameters. If the demanded set of + * TWT parameters is not accommodated by the responding STA or TWT scheduling AP, then + * the TWT requesting STA or TWT scheduled STA will reject the TWT setup. This type + * needs to be used only by the TWT requesting STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_GROUPING: The TWT responding STA suggests TWT group + * parameters that are different from the suggested or demanded TWT parameters of the + * TWT requesting STA. This type needs to be used only by the S1G TWT Responding STA in + * case of ITWT Setup Negotiation. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_ACCEPT: A TWT responding STA or TWT scheduling AP accepts + * the TWT request with the TWT parameters (see NOTE) indicated in the TWT element + * transmitted by the TWT requesting STA or TWT scheduled STA. This value is also used + * in unsolicited TWT responses. This needs type needs to be used only by the TWT + * responding STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_ALTERNATE: A TWT responding STA or TWT scheduling AP suggests + * TWT parameters that are different from those suggested by the TWT requesting STA or + * TWT scheduled STA. This needs type needs to be used only by the TWT reponding STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_DICTATE: A TWT responding STA or TWT scheduling AP indicates + * TWT parameters that are different from those suggested by the TWT requesting STA or + * TWT scheduled STA. This needs type needs to be used only by the TWT responding STA. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_REJECT: A TWT responding STA or TWT scheduling AP rejects + * setup, or a TWT scheduling AP terminates an existing broadcast TWT, or a TWT + * scheduled STA terminates its membership in a broadcast TWT. + * + * @IFX_TWT_OPER_SETUP_CMD_TYPE_MAX: This acts as a the tail of the list. + * Make sure it located at the end of the list. + */ +enum ifx_twt_oper_setup_cmd_type { + IFX_TWT_OPER_SETUP_CMD_TYPE_INVALID = -1, + IFX_TWT_OPER_SETUP_CMD_TYPE_REQUEST = 0, + IFX_TWT_OPER_SETUP_CMD_TYPE_SUGGEST = 1, + IFX_TWT_OPER_SETUP_CMD_TYPE_DEMAND = 2, + IFX_TWT_OPER_SETUP_CMD_TYPE_GROUPING = 3, + IFX_TWT_OPER_SETUP_CMD_TYPE_ACCEPT = 4, + IFX_TWT_OPER_SETUP_CMD_TYPE_ALTERNATE = 5, + IFX_TWT_OPER_SETUP_CMD_TYPE_DICTATE = 6, + IFX_TWT_OPER_SETUP_CMD_TYPE_REJECT = 7, + IFX_TWT_OPER_SETUP_CMD_TYPE_MAX = 8 +}; + +/** + * HE top level command IDs + */ +enum { + IFX_HE_CMD_ENAB = 0, + IFX_HE_CMD_FEATURES = 1, + IFX_HE_CMD_TWT_SETUP = 2, + IFX_HE_CMD_TWT_TEARDOWN = 3, + IFX_HE_CMD_TWT_INFO = 4, + IFX_HE_CMD_BSSCOLOR = 5, + IFX_HE_CMD_PARTIAL_BSSCOLOR = 6, + IFX_HE_CMD_CAP = 7, + IFX_HE_CMD_STAID = 8, + IFX_HE_CMD_RTSDURTHRESH = 10, + IFX_HE_CMD_PEDURATION = 11, + IFX_HE_CMD_TESTBED_MODE = 12, + IFX_HE_CMD_OMI = 13, + IFX_HE_CMD_MAC_PAD_DUR = 14, + IFX_HE_CMD_MUEDCA = 15, + IFX_HE_CMD_MACCAP = 16, + IFX_HE_CMD_PHYCAP = 17, + IFX_HE_CMD_DISPLAY = 18, + IFX_HE_CMD_ACTION = 19, + IFX_HE_CMD_OFDMATX = 20, + IFX_HE_CMD_20IN80_MODE = 21, + IFX_HE_CMD_SMPS = 22, + IFX_HE_CMD_PPETHRESH = 23, + IFX_HE_CMD_HTC_OMI_EN = 24, + IFX_HE_CMD_ERSU_EN = 25, + IFX_HE_CMD_PREPUNCRX_EN = 26, + IFX_HE_CMD_MIMOCAP_EN = 27, + IFX_HE_CMD_MUEDCA_OPT = 28, + IFX_HE_CMD_LAST +}; + +#define IFX_OCE_IOV_MAJOR_VER 1 +#define IFX_OCE_IOV_MINOR_VER 1 +#define IFX_OCE_IOV_MAJOR_VER_SHIFT 8 +#define IFX_OCE_IOV_VERSION \ + ((IFX_OCE_IOV_MAJOR_VER << IFX_OCE_IOV_MAJOR_VER_SHIFT) | \ + IFX_OCE_IOV_MINOR_VER) + +enum { + IFX_OCE_CMD_ENABLE = 1, + IFX_OCE_CMD_PROBE_DEF_TIME = 2, + IFX_OCE_CMD_FD_TX_PERIOD = 3, + IFX_OCE_CMD_FD_TX_DURATION = 4, + IFX_OCE_CMD_RSSI_TH = 5, + IFX_OCE_CMD_RWAN_LINKS = 6, + IFX_OCE_CMD_CU_TRIGGER = 7, + IFX_OCE_CMD_LAST +}; + +enum { + IFX_OCE_XTLV_ENABLE = 0x1, + IFX_OCE_XTLV_PROBE_DEF_TIME = 0x2, + IFX_OCE_XTLV_FD_TX_PERIOD = 0x3, + IFX_OCE_XTLV_FD_TX_DURATION = 0x4, + IFX_OCE_XTLV_RSSI_TH = 0x5, + IFX_OCE_XTLV_RWAN_LINKS = 0x6, + IFX_OCE_XTLV_CU_TRIGGER = 0x7 +}; + +static const struct nla_policy +ifx_vendor_attr_twt_param_policy[IFX_VENDOR_ATTR_TWT_PARAM_MAX + 1] = { + [IFX_VENDOR_ATTR_TWT_PARAM_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_NEGO_TYPE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_SETUP_CMD_TYPE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_DIALOG_TOKEN] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME] = {.type = NLA_U64}, + [IFX_VENDOR_ATTR_TWT_PARAM_WAKE_TIME_OFFSET] = {.type = NLA_U64}, + [IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_EXPONENT] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_WAKE_INTVL_MANTISSA] = {.type = NLA_U16}, + [IFX_VENDOR_ATTR_TWT_PARAM_REQUESTOR] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_TRIGGER] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_IMPLICIT] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_FLOW_TYPE] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_FLOW_ID] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_BCAST_TWT_ID] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_PROTECTION] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_CHANNEL] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_TWT_INFO_FRAME_DISABLED] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_MIN_WAKE_DURATION_UNIT] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_TEARDOWN_ALL_TWT] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAM_MAX] = {.type = NLA_U8}, +}; + +static const struct nla_policy ifx_vendor_attr_twt_policy[IFX_VENDOR_ATTR_TWT_MAX + 1] = { + [IFX_VENDOR_ATTR_TWT_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_OPER] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_TWT_PARAMS] = + NLA_POLICY_NESTED(ifx_vendor_attr_twt_param_policy), + [IFX_VENDOR_ATTR_TWT_MAX] = {.type = NLA_U8}, +}; + +/* randmac define/enum/struct + */ +#define WL_RANDMAC_API_VERSION 0x0100 /**< version 1.0 */ +#define WL_RANDMAC_API_MIN_VERSION 0x0100 /**< version 1.0 */ + +/** subcommands that can apply to randmac */ +enum { + WL_RANDMAC_SUBCMD_NONE = 0, + WL_RANDMAC_SUBCMD_GET_VERSION = 1, + WL_RANDMAC_SUBCMD_ENABLE = 2, + WL_RANDMAC_SUBCMD_DISABLE = 3, + WL_RANDMAC_SUBCMD_CONFIG = 4, + WL_RANDMAC_SUBCMD_STATS = 5, + WL_RANDMAC_SUBCMD_CLEAR_STATS = 6, + WL_RANDMAC_SUBCMD_MAX +}; + +struct ifx_randmac { + u16 version; + u16 len; /* total length */ + u16 subcmd_id; /* subcommand id */ + u8 data[0]; /* subcommand data */ +}; + +enum ifx_vendor_attr_wnm_param { + IFX_VENDOR_ATTR_WNM_PARAM_UNSPEC, + IFX_VENDOR_ATTR_WNM_PARAM_GET_INFO, + IFX_VENDOR_ATTR_WNM_PARAM_IDLE_PERIOD, + IFX_VENDOR_ATTR_WNM_PARAM_PROTECTION_OPT, + IFX_VENDOR_ATTR_WNM_PARAM_MAX +}; + +static const struct nla_policy +ifx_vendor_attr_wnm_param_policy[IFX_VENDOR_ATTR_WNM_PARAM_MAX + 1] = { + [IFX_VENDOR_ATTR_WNM_PARAM_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_PARAM_GET_INFO] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_PARAM_IDLE_PERIOD] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_PARAM_PROTECTION_OPT] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_PARAM_MAX] = {.type = NLA_U8}, +}; + +enum ifx_vendor_attr_wnm { + IFX_VENDOR_ATTR_WNM_UNSPEC, + IFX_VENDOR_ATTR_WNM_CMD, + IFX_VENDOR_ATTR_WNM_PARAMS, + IFX_VENDOR_ATTR_WNM_MAX +}; + +static const struct nla_policy ifx_vendor_attr_wnm_policy[IFX_VENDOR_ATTR_WNM_MAX + 1] = { + [IFX_VENDOR_ATTR_WNM_UNSPEC] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_CMD] = {.type = NLA_U8}, + [IFX_VENDOR_ATTR_WNM_PARAMS] = + NLA_POLICY_NESTED(ifx_vendor_attr_wnm_param_policy), + [IFX_VENDOR_ATTR_WNM_MAX] = {.type = NLA_U8}, +}; + +enum { + IFX_WNM_CMD_IOV_WNM = 1, + IFX_WNM_CMD_IOV_WNM_MAXIDLE = 2, + IFX_WNM_CMD_IOV_WNM_TIMBC_OFFSET = 3, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_URL = 4, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_REQ = 5, + IFX_WNM_CMD_IOV_WNM_TFS_TCLASTYPE = 6, + IFX_WNM_CMD_IOV_WNM_PARP_DISCARD = 7, + IFX_WNM_CMD_IOV_WNM_PARP_ALLNODE = 8, + IFX_WNM_CMD_IOV_WNM_TIMBC_SET = 9, + IFX_WNM_CMD_IOV_WNM_TIMBC_STATUS = 10, + IFX_WNM_CMD_IOV_WNM_DMS_SET = 11, + IFX_WNM_CMD_IOV_WNM_DMS_TERM = 12, + IFX_WNM_CMD_IOV_WNM_SERVICE_TERM = 13, + IFX_WNM_CMD_IOV_WNM_SLEEP_INTV = 14, + IFX_WNM_CMD_IOV_WNM_SLEEP_MODE = 15, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_QUERY = 16, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_RESP = 17, + IFX_WNM_CMD_IOV_WNM_TCLAS_ADD = 18, + IFX_WNM_CMD_IOV_WNM_TCLAS_DEL = 19, + IFX_WNM_CMD_IOV_WNM_TCLAS_LIST = 20, + IFX_WNM_CMD_IOV_WNM_DMS_STATUS = 21, + IFX_WNM_CMD_IOV_WNM_KEEPALIVES_MAX_IDLE = 22, + IFX_WNM_CMD_IOV_WNM_PM_IGNORE_BCMC = 23, + IFX_WNM_CMD_IOV_WNM_DMS_DEPENDENCY = 24, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_ROAMTHROTTLE = 25, + IFX_WNM_CMD_IOV_WNM_TFS_SET = 26, + IFX_WNM_CMD_IOV_WNM_TFS_TERM = 27, + IFX_WNM_CMD_IOV_WNM_TFS_STATUS = 28, + IFX_WNM_CMD_IOV_WNM_BTQ_NBR_ADD = 29, + IFX_WNM_CMD_IOV_WNM_BTQ_NBR_DEL = 30, + IFX_WNM_CMD_IOV_WNM_BTQ_NBR_LIST = 31, + IFX_WNM_CMD_IOV_WNM_BSSTRANS_RSSI_RATE_MAP = 32, + IFX_WNM_CMD_IOV_WNM_KEEPALIVE_PKT_TYPE = 33, + IFX_WNM_CONFIG_CMD_IOV_WNM_TYPE_MAX +}; + +struct ifx_maxidle_wnm { + u8 get_info; + int period; + int protect; +}; + +int ifx_cfg80211_vndr_cmds_twt(struct wiphy *wiphy, + struct wireless_dev *wdev, const void *data, int len); +int ifx_cfg80211_vndr_cmds_bsscolor(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_muedca_opt(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_amsdu(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_ldpc_cap(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_oce_enable(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_randmac(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_mbo(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_mpc(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_giantrx(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); +int ifx_cfg80211_vndr_cmds_wnm(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int len); + +#endif /* IFX_VENDOR_H */ + diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.c index 2f89080743037..1d3761e96b2c1 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.c @@ -82,3 +82,22 @@ void brcmf_xtlv_pack_header(struct brcmf_xtlv *xtlv, u16 id, u16 len, memcpy(data_buf, data, len); } +u32 brcmf_pack_xtlv(u16 id, char *data, u32 len, + char **buf, u16 *buflen) +{ + u32 iolen; + + iolen = brcmf_xtlv_data_size(len, BRCMF_XTLV_OPTION_ALIGN32); + + if (iolen > *buflen) { + WARN(true, "xtlv buffer is too short"); + return 0; + } + + brcmf_xtlv_pack_header((void *)*buf, id, len, data, + BRCMF_XTLV_OPTION_ALIGN32); + + *buf = *buf + iolen; + *buflen -= iolen; + return iolen; +} diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.h index b2c7ae8966a10..b713c6caf91a4 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/xtlv.h @@ -27,5 +27,7 @@ enum brcmf_xtlv_option { int brcmf_xtlv_data_size(int dlen, u16 opts); void brcmf_xtlv_pack_header(struct brcmf_xtlv *xtlv, u16 id, u16 len, const u8 *data, u16 opts); +u32 brcmf_pack_xtlv(u16 id, char *data, u32 len, + char **buf, u16 *buflen); #endif /* __BRCMF_XTLV_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c index 1e2b1e487eb76..5effa27542e95 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c @@ -87,10 +87,20 @@ static void brcmu_d11ac_encchspec(struct brcmu_chan *ch) 0, d11ac_bw(ch->bw)); ch->chspec &= ~BRCMU_CHSPEC_D11AC_BND_MASK; - if (ch->chnum <= CH_MAX_2G_CHANNEL) - ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G; - else + switch (ch->band) { + case BRCMU_CHAN_BAND_6G: + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_6G; + break; + case BRCMU_CHAN_BAND_5G: ch->chspec |= BRCMU_CHSPEC_D11AC_BND_5G; + break; + case BRCMU_CHAN_BAND_2G: + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G; + break; + default: + WARN_ONCE(1, "Invalid band 0x%04x\n", ch->band); + break; + } } static void brcmu_d11n_decchspec(struct brcmu_chan *ch) @@ -222,6 +232,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) } switch (ch->chspec & BRCMU_CHSPEC_D11AC_BND_MASK) { + case BRCMU_CHSPEC_D11AC_BND_6G: + ch->band = BRCMU_CHAN_BAND_6G; + break; case BRCMU_CHSPEC_D11AC_BND_5G: ch->band = BRCMU_CHAN_BAND_5G; break; diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h index f4939cf627672..94632ab53d6c2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h @@ -14,6 +14,7 @@ #define BRCM_USB_VENDOR_ID_LINKSYS 0x13b1 #define CY_USB_VENDOR_ID_CYPRESS 0x04b4 #define BRCM_PCIE_VENDOR_ID_BROADCOM PCI_VENDOR_ID_BROADCOM +#define CY_PCIE_VENDOR_ID_CYPRESS 0x12be /* Chipcommon Core Chip IDs */ #define BRCM_CC_43143_CHIP_ID 43143 @@ -52,11 +53,14 @@ #define BRCM_CC_43666_CHIP_ID 43666 #define BRCM_CC_4371_CHIP_ID 0x4371 #define BRCM_CC_4378_CHIP_ID 0x4378 +#define CY_CC_43430_CHIP_ID 43430 #define CY_CC_4373_CHIP_ID 0x4373 #define CY_CC_43012_CHIP_ID 43012 #define CY_CC_43439_CHIP_ID 43439 #define CY_CC_43752_CHIP_ID 43752 #define CY_CC_89459_CHIP_ID 0x4355 +#define CY_CC_55500_CHIP_ID 0xD8CC +#define CY_CC_55572_CHIP_ID 0xd908 /* USB Device IDs */ #define BRCM_USB_43143_DEVICE_ID 0xbd1e @@ -93,6 +97,14 @@ #define BRCM_PCIE_4378_DEVICE_ID 0x4425 #define CY_PCIE_89459_DEVICE_ID 0x4415 #define CY_PCIE_89459_RAW_DEVICE_ID 0x4355 +#define CY_PCIE_54591_DEVICE_ID 0x4417 +#define CY_PCIE_54590_DEVICE_ID 0x4416 +#define CY_PCIE_54594_DEVICE_ID 0x441a +#define CY_PCIE_55572_DEVICE_ID 0xbd31 +#define CY_PCIE_4373_RAW_DEVICE_ID 0x4373 +#define CY_PCIE_4373_DUAL_DEVICE_ID 0x4418 +#define CY_PCIE_4373_2G_DEVICE_ID 0x4419 +#define CY_PCIE_4373_5G_DEVICE_ID 0x441a /* brcmsmac IDs */ #define BCM4313_D11N2G_ID 0x4727 /* 4313 802.11n 2.4G device */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h index f6344023855c3..3d7655c9c0586 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h @@ -82,11 +82,18 @@ #define BRCMU_CHSPEC_D11AC_BND_SHIFT 14 #define BRCMU_CHSPEC_D11AC_BND_2G 0x0000 #define BRCMU_CHSPEC_D11AC_BND_3G 0x4000 -#define BRCMU_CHSPEC_D11AC_BND_4G 0x8000 +#define BRCMU_CHSPEC_D11AC_BND_6G 0x8000 #define BRCMU_CHSPEC_D11AC_BND_5G 0xc000 - -#define BRCMU_CHAN_BAND_2G 0 -#define BRCMU_CHAN_BAND_5G 1 +#define BRCMU_CHSPEC_IS5G(chspec) \ + (((chspec) & BRCMU_CHSPEC_D11AC_BND_MASK) == BRCMU_CHSPEC_D11AC_BND_5G) +#define BRCMU_CHSPEC_IS6G(chspec) \ + (((chspec) & BRCMU_CHSPEC_D11AC_BND_MASK) == BRCMU_CHSPEC_D11AC_BND_6G) +#define BRCMU_CHAN_BAND_2G 1 +#define BRCMU_CHAN_BAND_5G 2 +#define BRCMU_CHAN_BAND_6G 3 +#define BRCMU_CHAN_BAND_TO_NL80211(band) \ + ((band) == BRCMU_CHAN_BAND_2G ? NL80211_BAND_2GHZ : \ + ((band) == BRCMU_CHAN_BAND_5G ? NL80211_BAND_5GHZ : NL80211_BAND_6GHZ)) enum brcmu_chan_bw { BRCMU_CHAN_BW_20, diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_utils.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_utils.h index 9465323286673..48791ac87496d 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_utils.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_utils.h @@ -21,6 +21,19 @@ } \ } +/* Spin at most 'ms' milliseconds with polling interval 'interval' milliseconds + * while 'exp' is true. Caller should explicitly test 'exp' when this completes + * and take appropriate error action if 'exp' is still true. + */ +#define SPINWAIT_MS(exp, ms, interval) { \ + typeof(interval) interval_ = (interval); \ + uint countdown = (ms) + (interval_ - 1U); \ + while ((exp) && (countdown >= interval_)) { \ + msleep(interval_); \ + countdown -= interval_; \ + } \ +} + /* osl multi-precedence packet queue */ #define PKTQ_LEN_DEFAULT 128 /* Max 128 packets */ #define PKTQ_MAX_PREC 16 /* Maximum precedence levels */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h index 7552bdb91991c..43991b6420eec 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h @@ -92,7 +92,8 @@ #define WLC_BAND_AUTO 0 /* auto-select */ #define WLC_BAND_5G 1 /* 5 Ghz */ #define WLC_BAND_2G 2 /* 2.4 Ghz */ -#define WLC_BAND_ALL 3 /* all bands */ +#define WLC_BAND_6G 3 /* 6 Ghz */ +#define WLC_BAND_ALL 4 /* all bands */ #define CHSPEC_CHANNEL(chspec) ((u8)((chspec) & WL_CHANSPEC_CHAN_MASK)) #define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) @@ -201,6 +202,13 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define CRYPTO_ALGO_AES_RESERVED2 6 #define CRYPTO_ALGO_NALG 7 +#define CRYPTO_ALGO_AES_GCM 14 /* 128 bit GCM */ +#define CRYPTO_ALGO_AES_CCM256 15 /* 256 bit CCM */ +#define CRYPTO_ALGO_AES_GCM256 16 /* 256 bit GCM */ +#define CRYPTO_ALGO_BIP_CMAC256 17 /* 256 bit BIP CMAC */ +#define CRYPTO_ALGO_BIP_GMAC 18 /* 128 bit BIP GMAC */ +#define CRYPTO_ALGO_BIP_GMAC256 19 /* 256 bit BIP GMAC */ + /* wireless security bitvec */ #define WEP_ENABLED 0x0001 @@ -232,6 +240,12 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec) #define WPA2_AUTH_PSK_SHA256 0x8000 /* PSK with SHA256 key derivation */ #define WPA3_AUTH_SAE_PSK 0x40000 /* SAE with 4-way handshake */ +#define WPA3_AUTH_OWE 0x100000 /* OWE */ +#define WFA_AUTH_DPP 0x200000 /* WFA DPP AUTH */ +#define WPA3_AUTH_1X_SUITE_B_SHA384 0x400000 /* Suite B-192 SHA384 */ + +#define WFA_OUI "\x50\x6F\x9A" /* WFA OUI */ +#define DPP_VER 0x1A /* WFA DPP v1.0 */ #define DOT11_DEFAULT_RTS_LEN 2347 #define DOT11_DEFAULT_FRAG_LEN 2346 diff --git a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h index 0340bba968688..39cd34c226281 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h @@ -214,8 +214,197 @@ struct chipcregs { u32 PAD[3]; u32 retention_grpidx; /* 0x680 */ u32 retention_grpctl; /* 0x684 */ - u32 PAD[94]; - u16 sromotp[768]; + u32 mac_res_req_timer; /* 0x688 */ + u32 mac_res_req_mask; /* 0x68c */ + u32 PAD[18]; + u32 pmucontrol_ext; /* 0x6d8 */ + u32 slowclkperiod; /* 0x6dc */ + u32 PAD[8]; + u32 pmuintmask0; /* 0x700 */ + u32 pmuintmask1; /* 0x704 */ + u32 PAD[14]; + u32 pmuintstatus; /* 0x740 */ + u32 extwakeupstatus; /* 0x744 */ + u32 watchdog_res_mask; /* 0x748 */ + u32 swscratch; /* 0x750 */ + u32 PAD[3]; + u32 extwakemask[2]; /* 0x760-0x764 */ + u32 PAD[2]; + u32 extwakereqmask[2]; /* 0x770-0x774 */ + u32 PAD[2]; + u32 pmuintctrl0; /* 0x780 */ + u32 pmuintctrl1; /* 0x784 */ + u32 PAD[2]; + u32 extwakectrl[2]; /* 0x790 */ +}; + +#define CHIPGCIREGOFFS(field) offsetof(struct chipgciregs, field) + +struct chipgciregs { + u32 gci_corecaps0; /* 0x000 */ + u32 gci_corecaps1; /* 0x004 */ + u32 gci_corecaps2; /* 0x008 */ + u32 gci_corectrl; /* 0x00c */ + u32 gci_corestat; /* 0x010 */ + u32 gci_intstat; /* 0x014 */ + u32 gci_intmask; /* 0x018 */ + u32 gci_wakemask; /* 0x01c */ + u32 gci_levelintstat; /* 0x020 */ + u32 gci_eventintstat; /* 0x024 */ + u32 gci_wakelevelintstat; /* 0x028 */ + u32 gci_wakeeventintstat; /* 0x02c */ + u32 semaphoreintstatus; /* 0x030 */ + u32 semaphoreintmask; /* 0x034 */ + u32 semaphorerequest; /* 0x038 */ + u32 semaphorereserve; /* 0x03c */ + u32 gci_indirect_addr; /* 0x040 */ + u32 gci_gpioctl; /* 0x044 */ + u32 gci_gpiostatus; /* 0x048 */ + u32 gci_gpiomask; /* 0x04c */ + u32 eventsummary; /* 0x050 */ + u32 gci_miscctl; /* 0x054 */ + u32 gci_gpiointmask; /* 0x058 */ + u32 gci_gpiowakemask; /* 0x05c */ + u32 gci_input[32]; /* 0x060 */ + u32 gci_event[32]; /* 0x0e0 */ + u32 gci_output[4]; /* 0x160 */ + u32 gci_control_0; /* 0x170 */ + u32 gci_control_1; /* 0x174 */ + u32 gci_intpolreg; /* 0x178 */ + u32 gci_levelintmask; /* 0x17c */ + u32 gci_eventintmask; /* 0x180 */ + u32 wakelevelintmask; /* 0x184 */ + u32 wakeeventintmask; /* 0x188 */ + u32 hwmask; /* 0x18c */ + u32 PAD; + u32 gci_inbandeventintmask; /* 0x194 */ + u32 PAD; + u32 gci_inbandeventstatus; /* 0x19c */ + u32 gci_seciauxtx; /* 0x1a0 */ + u32 gci_seciauxrx; /* 0x1a4 */ + u32 gci_secitx_datatag; /* 0x1a8 */ + u32 gci_secirx_datatag; /* 0x1ac */ + u32 gci_secitx_datamask; /* 0x1b0 */ + u32 gci_seciusef0tx_reg; /* 0x1b4 */ + u32 gci_secif0tx_offset; /* 0x1b8 */ + u32 gci_secif0rx_offset; /* 0x1bc */ + u32 gci_secif1tx_offset; /* 0x1c0 */ + u32 gci_rxfifo_common_ctrl; /* 0x1c4 */ + u32 gci_rxfifoctrl; /* 0x1c8 */ + u32 gci_hw_sema_status; /* 0x1cc */ + u32 gci_seciuartescval; /* 0x1d0 */ + u32 gic_seciuartautobaudctr; /* 0x1d4 */ + u32 gci_secififolevel; /* 0x1d8 */ + u32 gci_seciuartdata; /* 0x1dc */ + u32 gci_secibauddiv; /* 0x1e0 */ + u32 gci_secifcr; /* 0x1e4 */ + u32 gci_secilcr; /* 0x1e8 */ + u32 gci_secimcr; /* 0x1ec */ + u32 gci_secilsr; /* 0x1f0 */ + u32 gci_secimsr; /* 0x1f4 */ + u32 gci_baudadj; /* 0x1f8 */ + u32 gci_inbandintmask; /* 0x1fc */ + u32 gci_chipctrl; /* 0x200 */ + u32 gci_chipsts; /* 0x204 */ + u32 gci_gpioout; /* 0x208 */ + u32 gci_gpioout_read; /* 0x20C */ + u32 gci_mpwaketx; /* 0x210 */ + u32 gci_mpwakedetect; /* 0x214 */ + u32 gci_seciin_ctrl; /* 0x218 */ + u32 gci_seciout_ctrl; /* 0x21C */ + u32 gci_seciin_auxfifo_en; /* 0x220 */ + u32 gci_seciout_txen_txbr; /* 0x224 */ + u32 gci_seciin_rxbrstatus; /* 0x228 */ + u32 gci_seciin_rxerrstatus; /* 0x22C */ + u32 gci_seciin_fcstatus; /* 0x230 */ + u32 gci_seciout_txstatus; /* 0x234 */ + u32 gci_seciout_txbrstatus; /* 0x238 */ + u32 wlan_mem_info; /* 0x23C */ + u32 wlan_bankxinfo; /* 0x240 */ + u32 bt_smem_select; /* 0x244 */ + u32 bt_smem_stby; /* 0x248 */ + u32 bt_smem_status; /* 0x24C */ + u32 wlan_bankxactivepda; /* 0x250 */ + u32 wlan_bankxsleeppda; /* 0x254 */ + u32 wlan_bankxkill; /* 0x258 */ + u32 PAD[41]; + u32 gci_chipid; /* 0x300 */ + u32 PAD[3]; + u32 otpstatus; /* 0x310 */ + u32 otpcontrol; /* 0x314 */ + u32 otpprog; /* 0x318 */ + u32 otplayout; /* 0x31c */ + u32 otplayoutextension; /* 0x320 */ + u32 otpcontrol1; /* 0x324 */ + u32 otpprogdata; /* 0x328 */ + u32 PAD[52]; + u32 otpECCstatus; /* 0x3FC */ + u32 PAD[512]; + u32 lhl_core_capab_adr; /* 0xC00 */ + u32 lhl_main_ctl_adr; /* 0xC04 */ + u32 lhl_pmu_ctl_adr; /* 0xC08 */ + u32 lhl_extlpo_ctl_adr; /* 0xC0C */ + u32 lpo_ctl_adr; /* 0xC10 */ + u32 lhl_lpo2_ctl_adr; /* 0xC14 */ + u32 lhl_osc32k_ctl_adr; /* 0xC18 */ + u32 lhl_clk_status_adr; /* 0xC1C */ + u32 lhl_clk_det_ctl_adr; /* 0xC20 */ + u32 lhl_clk_sel_adr; /* 0xC24 */ + u32 hidoff_cnt_adr[2]; /* 0xC28-0xC2C */ + u32 lhl_autoclk_ctl_adr; /* 0xC30 */ + u32 PAD; + u32 lhl_hibtim_adr; /* 0xC38 */ + u32 lhl_wl_ilp_val_adr; /* 0xC3C */ + u32 lhl_wl_armtim0_intrp_adr; /* 0xC40 */ + u32 lhl_wl_armtim0_st_adr; /* 0xC44 */ + u32 lhl_wl_armtim0_adr; /* 0xC48 */ + u32 PAD[9]; + u32 lhl_wl_mactim0_intrp_adr; /* 0xC70 */ + u32 lhl_wl_mactim0_st_adr; /* 0xC74 */ + u32 lhl_wl_mactim_int0_adr; /* 0xC78 */ + u32 lhl_wl_mactim_frac0_adr; /* 0xC7C */ + u32 lhl_wl_mactim1_intrp_adr; /* 0xC80 */ + u32 lhl_wl_mactim1_st_adr; /* 0xC84 */ + u32 lhl_wl_mactim_int1_adr; /* 0xC88 */ + u32 lhl_wl_mactim_frac1_adr; /* 0xC8C */ + u32 PAD[8]; + u32 gpio_int_en_port_adr[4]; /* 0xCB0-0xCBC */ + u32 gpio_int_st_port_adr[4]; /* 0xCC0-0xCCC */ + u32 gpio_ctrl_iocfg_p_adr[64]; /* 0xCD0-0xDCC */ + u32 gpio_gctrl_iocfg_p0_p39_adr; /* 0xDD0 */ + u32 gpio_gdsctrl_iocfg_p0_p25_p30_p39_adr; /* 0xDD4 */ + u32 gpio_gdsctrl_iocfg_p26_p29_adr; /* 0xDD8 */ + u32 PAD[8]; + u32 lhl_gpio_din0_adr; /* 0xDFC */ + u32 lhl_gpio_din1_adr; /* 0xE00 */ + u32 lhl_wkup_status_adr; /* 0xE04 */ + u32 lhl_ctl_adr; /* 0xE08 */ + u32 lhl_adc_ctl_adr; /* 0xE0C */ + u32 lhl_qdxyz_in_dly_adr; /* 0xE10 */ + u32 lhl_optctl_adr; /* 0xE14 */ + u32 lhl_optct2_adr; /* 0xE18 */ + u32 lhl_scanp_cntr_init_val_adr; /* 0xE1C */ + u32 lhl_opt_togg_val_adr[6]; /* 0xE20-0xE34 */ + u32 lhl_optx_smp_val_adr; /* 0xE38 */ + u32 lhl_opty_smp_val_adr; /* 0xE3C */ + u32 lhl_optz_smp_val_adr; /* 0xE40 */ + u32 lhl_hidoff_keepstate_adr[3]; /* 0xE44-0xE4C */ + u32 lhl_bt_slmboot_ctl0_adr[4]; /* 0xE50-0xE5C */ + u32 lhl_wl_fw_ctl; /* 0xE60 */ + u32 lhl_wl_hw_ctl_adr[2]; /* 0xE64-0xE68 */ + u32 lhl_bt_hw_ctl_adr; /* 0xE6C */ + u32 lhl_top_pwrseq_en_adr; /* 0xE70 */ + u32 lhl_top_pwrdn_ctl_adr; /* 0xE74 */ + u32 lhl_top_pwrup_ctl_adr; /* 0xE78 */ + u32 lhl_top_pwrseq_ctl_adr; /* 0xE7C */ + u32 lhl_top_pwrdn2_ctl_adr; /* 0xE80 */ + u32 lhl_top_pwrup2_ctl_adr; /* 0xE84 */ + u32 wpt_regon_intrp_cfg_adr; /* 0xE88 */ + u32 bt_regon_intrp_cfg_adr; /* 0xE8C */ + u32 wl_regon_intrp_cfg_adr; /* 0xE90 */ + u32 regon_intrp_st_adr; /* 0xE94 */ + u32 regon_intrp_en_adr; /* 0xE98 */ + }; /* chipid */ @@ -308,4 +497,6 @@ struct chipcregs { */ #define PMU_MAX_TRANSITION_DLY 15000 +#define DEFAULT_43012_MIN_RES_MASK 0x0f8bfe77 + #endif /* _SBCHIPC_H */ diff --git a/drivers/net/wireless/broadcom/brcm80211/include/defs.h b/drivers/net/wireless/broadcom/brcm80211/include/defs.h index 9e7e6116eb74e..fb25bdf54c668 100644 --- a/drivers/net/wireless/broadcom/brcm80211/include/defs.h +++ b/drivers/net/wireless/broadcom/brcm80211/include/defs.h @@ -35,6 +35,44 @@ #define MAXPRIO 7 #define NUMPRIO (MAXPRIO + 1) +/* DSCP type definitions for RFC4594 */ +/* DF: Standard (RFC2474) */ +#define DSCP_DF 0x00u +/* AF1x: High-Throughput Data (RFC2597) */ +#define DSCP_AF11 0x0Au +#define DSCP_AF12 0x0Cu +#define DSCP_AF13 0x0Eu +/* CS1: Low-Priority Data (RFC3662) */ +#define DSCP_CS1 0x08u +/* AF2x: Low-Latency Data (RFC2597) */ +#define DSCP_AF21 0x12u +#define DSCP_AF22 0x14u +#define DSCP_AF23 0x16u +/* CS2: OAM (RFC2474) */ +#define DSCP_CS2 0x10u +/* AF3x: Multimedia Streaming (RFC2597) */ +#define DSCP_AF31 0x1Au +#define DSCP_AF32 0x1Cu +#define DSCP_AF33 0x1Eu +/* CS3: Broadcast Video (RFC2474) */ +#define DSCP_CS3 0x18u +/* AF4x: Multimedia Conferencing (RFC2597) */ +#define DSCP_AF41 0x22u +#define DSCP_AF42 0x24u +#define DSCP_AF43 0x26u +/* CS4: Real-Time Interactive (RFC2474) */ +#define DSCP_CS4 0x20u +/* CS5: Signaling (RFC2474) */ +#define DSCP_CS5 0x28u +/* VA: VOCIE-ADMIT (RFC5865) */ +#define DSCP_VA 0x2Cu +/* EF: Telephony (RFC3246) */ +#define DSCP_EF 0x2Eu +/* CS6: Network Control (RFC2474) */ +#define DSCP_CS6 0x30u +/* CS7: Network Control (RFC2474) */ +#define DSCP_CS7 0x38u + #define WL_NUMRATES 16 /* max # of rates in a rateset */ #define BRCM_CNTRY_BUF_SZ 4 /* Country string is 3 bytes + NUL */ diff --git a/drivers/net/wireless/laird/Kconfig b/drivers/net/wireless/laird/Kconfig new file mode 100644 index 0000000000000..54e99c5222662 --- /dev/null +++ b/drivers/net/wireless/laird/Kconfig @@ -0,0 +1,86 @@ +config WLAN_VENDOR_LAIRD + bool "Laird Connectivity WLAN devices" + default y + help + If you have a wireless card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about cards. If you say Y, you will be asked for + your specific card in the following questions. + +if WLAN_VENDOR_LAIRD + +config LRDMWL + tristate "60 Series driver (mac80211 compatible)" + depends on MAC80211 + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module + + 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 lrdmwl. + +if LRDMWL + +config LRDMWL_PCIE + tristate "PCIe Bus" + depends on PCI + depends on FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for PCIE + + 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 lrdmwl_pcie. + +config LRDMWL_SDIO + tristate "SDIO Bus" + depends on MMC + depends on FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for SDIO + + 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 lrdmwl_sdio. + +config LRDMWL_USB + tristate "USB Bus" + depends on USB + depends on FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for USB + + 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 lrdmwl_usb. + +config LRDMWL_FIPS + bool "FIPS support for 60 Series based SOMs" + help + Enables FIPS operation for SOM60 + +endif # LRDMWL + +endif # WLAN_VENDOR_LAIRD diff --git a/drivers/net/wireless/laird/Makefile b/drivers/net/wireless/laird/Makefile new file mode 100644 index 0000000000000..e98ec801d4ec8 --- /dev/null +++ b/drivers/net/wireless/laird/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_LRDMWL) += lrdmwl/ diff --git a/drivers/net/wireless/laird/lrdmwl/Kconfig b/drivers/net/wireless/laird/lrdmwl/Kconfig new file mode 100644 index 0000000000000..c64373ba7f84b --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/Kconfig @@ -0,0 +1,68 @@ +config LRDMWL + tristate "Laird Connectivity wireless support based on Marvell 8997 chipset (mac80211 compatible)" + depends on MAC80211 + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module + + 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 lrdmwl. + +config LRDMWL_PCIE + tristate "Laird wireless support based on Marvell chipsets for PCIE" + depends on LRDMWL && PCI + select FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for PCIE + + 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 lrdmwl_pcie. + +config LRDMWL_SDIO + tristate "Laird wireless support based on Marvell chipsets for SDIO" + depends on LRDMWL && MMC + select FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for SDIO + + 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 lrdmwl_sdio. + +config LRDMWL_USB + tristate "Laird wireless support based on Marvell chipsets for USB" + depends on LRDMWL + select FW_LOADER + help + Select to build the driver supporting the: + + Laird Connectivity 60 Series Wi-Fi module for USB + + 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 lrdmwl_usb. + +config LRDMWL_FIPS + bool "Laird wireless FIPS support for SOM60" + depends on LRDMWL + help + Enables FIPS operation for SOM60 diff --git a/drivers/net/wireless/laird/lrdmwl/Makefile b/drivers/net/wireless/laird/lrdmwl/Makefile new file mode 100644 index 0000000000000..9297cc124bfc3 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/Makefile @@ -0,0 +1,23 @@ +obj-$(CONFIG_LRDMWL) += lrdmwl.o +lrdmwl-y += main.o +lrdmwl-y += mac80211.o +lrdmwl-y += fwcmd.o +lrdmwl-y += tx.o +lrdmwl-y += rx.o +lrdmwl-y += isr.o +lrdmwl-y += vendor_cmd.o +lrdmwl-$(CONFIG_THERMAL) += thermal.o +lrdmwl-$(CONFIG_DEBUG_FS) += debugfs.o +lrdmwl-$(CONFIG_SYSFS) += sysfs.o + +obj-$(CONFIG_LRDMWL_PCIE) += lrdmwl_pcie.o +lrdmwl_pcie-y += pcie.o +lrdmwl_pcie-y += pfu.o + +obj-$(CONFIG_LRDMWL_SDIO) += lrdmwl_sdio.o +lrdmwl_sdio-y += sdio.o + +obj-$(CONFIG_LRDMWL_USB) += lrdmwl_usb.o +lrdmwl_usb-y += usb.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/laird/lrdmwl/debugfs.c b/drivers/net/wireless/laird/lrdmwl/debugfs.c new file mode 100644 index 0000000000000..e6f242929aab3 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/debugfs.c @@ -0,0 +1,941 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements debug fs related functions. */ + +#include + +#include "sysadpt.h" +#include "dev.h" +#include "hostcmd.h" +#include "fwcmd.h" +#include "thermal.h" +#include "debugfs.h" + +#include "pcie.h" +#include "sdio.h" + +#define MWLWIFI_DEBUGFS_ADD_FILE(name) do { \ + if (!debugfs_create_file(#name, 0644, priv->debugfs_phy, \ + priv, &mwl_debugfs_##name##_fops)) \ + return; \ +} while (0) + +#define MWLWIFI_DEBUGFS_FILE_OPS(name) \ +static const struct file_operations mwl_debugfs_##name##_fops = { \ + .read = mwl_debugfs_##name##_read, \ + .write = mwl_debugfs_##name##_write, \ + .open = simple_open, \ +} + +#define MWLWIFI_DEBUGFS_FILE_READ_OPS(name) \ +static const struct file_operations mwl_debugfs_##name##_fops = { \ + .read = mwl_debugfs_##name##_read, \ + .open = simple_open, \ +} + +#define MWLWIFI_DEBUGFS_FILE_WRITE_OPS(name) \ +static const struct file_operations mwl_debugfs_##name##_fops = { \ + .write = mwl_debugfs_##name##_write, \ + .open = simple_open, \ +} + +static const char chipname[MWLUNKNOWN][8] = { + "88W8864", + "88W8897", + "88W8964", + "88W8997", +}; + +static const char chipbus[5][5] = { + "?", + "?", + "SDIO", + "PCIE", + "USB" +}; + +static void dump_data(char *p, int size, int *len, u8 *data, + int data_len, char *title) +{ + int cur_byte = 0; + int i; + + *len += scnprintf(p + *len, size - *len, "%s\n", title); + + for (cur_byte = 0; cur_byte < data_len; cur_byte += 8) { + if ((cur_byte + 8) < data_len) { + for (i = 0; i < 8; i++) { + *len += scnprintf(p + *len, size - *len, "0x%02x ", *(data + cur_byte + i)); + } + *len += scnprintf(p + *len, size - *len, "\n"); + } else { + for (i = 0; i < (data_len - cur_byte); i++) { + *len += scnprintf(p + *len, size - *len, "0x%02x ", *(data + cur_byte + i)); + } + *len += scnprintf(p + *len, size - *len, "\n"); + break; + } + } +} + +static ssize_t mwl_debugfs_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int i = 0; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + len += scnprintf(p + len, size - len, "\n"); + len += scnprintf(p + len, size - len, "Driver name: %s\n", MWL_DRV_NAME); + len += scnprintf(p + len, size - len, "Chip type: %s-%s\n", chipname[priv->chip_type], chipbus[priv->host_if]); + len += scnprintf(p + len, size - len, "HW version: %X\n", priv->hw_data.hw_version); + len += scnprintf(p + len, size - len, "DRV version: %s\n", LRD_BLD_VERSION); + len += scnprintf(p + len, size - len, "FW version: %d.%d.%d.%d\n", + ((priv->hw_data.fw_release_num >> 24) & 0xff), + ((priv->hw_data.fw_release_num >> 16) & 0xff), + ((priv->hw_data.fw_release_num >> 8) & 0xff), + ((priv->hw_data.fw_release_num >> 0) & 0xff)); + len += scnprintf(p + len, size - len, "OTP version: %d\n", priv->radio_caps.version); + len += scnprintf(p + len, size - len, "OTP region: 0x%x\n", priv->reg.otp.region); + len += scnprintf(p + len, size - len, "OTP cap: 0x%x\n", priv->radio_caps.capability); + len += scnprintf(p + len, size - len, "OTP mac: %d\n", priv->radio_caps.num_mac); + len += scnprintf(p + len, size - len, "Radio Type: %s%s%s\n", + (priv->radio_caps.capability & LRD_CAP_SU60) ? "SU":"ST", + (priv->radio_caps.capability & LRD_CAP_440) ? "-440":"", + (priv->radio_caps.capability & LRD_CAP_SOM8MP) ? "-SOM8MP":""); + len += scnprintf(p + len, size - len, "BT Offset : %s\n", + (priv->radio_caps.capability & LRD_CAP_BT_SEQ) ? "1": + (priv->radio_caps.capability & LRD_CAP_BT_DUP) ? "0":"Default"); + len += scnprintf(p + len, size - len, "MAC address: %pM\n", priv->hw_data.mac_addr); + len += scnprintf(p + len, size - len, "Region code: 0x%02x (0x%02x)\n", priv->reg.cc.region, priv->reg.otp.region); + len += scnprintf(p + len, size - len, "Country code: '%c%c' ('%c%c')\n", + priv->reg.cc.alpha2[0],priv->reg.cc.alpha2[1], + priv->reg.otp.alpha2[0],priv->reg.otp.alpha2[1]); + len += scnprintf(p + len, size - len, "Radio: %s\n", priv->radio_on ? "enable" : "disable"); + + len += scnprintf(p + len, size - len, "2g: %s\n", priv->disable_2g ? "disable" : "enable"); + len += scnprintf(p + len, size - len, "5g: %s\n", priv->disable_5g ? "disable" : "enable"); + len += scnprintf(p + len, size - len, "TX antenna: %d\n", priv->ant_tx_num); + len += scnprintf(p + len, size - len, "RX antenna: %d\n", priv->ant_rx_num); + len += scnprintf(p + len, size - len, "Antenna Gain: 0x%08x\n", priv->ant_gain_adjust); + len += scnprintf(p + len, size - len, "Deep Sleep: 0x%x\n", priv->ds_enable); + + //Card Specific debug info + if (priv->if_ops.dbg_info != NULL) { + len += priv->if_ops.dbg_info(priv , p, size, len); + } + + len += scnprintf(p + len, size - len, "TX limit: %d\n", priv->txq_limit); + len += scnprintf(p + len, size - len, "RX limit: %d\n", priv->recv_limit); + len += scnprintf(p + len, size - len, "AP macid support: %08x\n", priv->ap_macids_supported); + len += scnprintf(p + len, size - len, "STA macid support: %08x\n", priv->sta_macids_supported); + len += scnprintf(p + len, size - len, "macid used: %08x\n", priv->macids_used); + len += scnprintf(p + len, size - len, "qe trigger number: %d\n", priv->qe_trigger_num); + + len += scnprintf(p + len, size - len,"OS TxQ status = [ %d:%d %d:%d %d:%d %d:%d ]\n", + ieee80211_queue_stopped(priv->hw, 0), skb_queue_len(&priv->txq[0]), + ieee80211_queue_stopped(priv->hw, 1), skb_queue_len(&priv->txq[1]), + ieee80211_queue_stopped(priv->hw, 2), skb_queue_len(&priv->txq[2]), + ieee80211_queue_stopped(priv->hw, 3), skb_queue_len(&priv->txq[3])); + + len += scnprintf(p + len, size - len,"tx_mgmt_cnt=%lu, tx_data_cnt=%lu\n", priv->tx_mgmt_cnt, priv->tx_data_cnt); + if (priv->host_if == MWL_IF_PCIE) { + len += scnprintf(p + len, size - len,"num_valid_interrupts = %lu\n", priv->valid_interrupt_cnt); + } + else if (priv->host_if == MWL_IF_SDIO) { + struct mwl_sdio_card *card = priv->intf; + len += scnprintf(p + len, size - len, "tx_pkt_unaligned_cnt: %d\n",card->tx_pkt_unaligned_cnt); + } + + // Dump PFU regs + if (((priv->host_if == MWL_IF_PCIE) && IS_PFU_ENABLED(priv->chip_type))) { + struct mwl_pcie_card *card = priv->intf; + + len += scnprintf(p + len, size - len, "PCIe Tx Rd/Wr Ptr (Sw) = (0x%x / 0x%x)\n", + priv->txbd_rdptr, priv->txbd_wrptr); + + len += scnprintf(p + len, size - len, "PCIe Tx Rd/Wr Ptr (Hw) = (0x%x / 0x%x)\n", + readl(card->iobase1 + REG_TXBD_RDPTR), + readl(card->iobase1 + REG_TXBD_WRPTR)); + + len += scnprintf(p + len, size - len, "PCIe IntMask = 0x%x\n", + readl(card->iobase1 + + MACREG_REG_A2H_INTERRUPT_STATUS_MASK)); + } + + // Dump WMM regs + for (i=0; i<7; i++){ + u32 cwmin, cwmax, txop, aifsn; + if(mwl_fwcmd_reg_mac(priv->hw, WL_GET, MAC_REG_CW0_MIN+(i*8), &cwmin)) + cwmin = 0xdead; + if(mwl_fwcmd_reg_mac(priv->hw, WL_GET, MAC_REG_CW0_MAX+(i*8), &cwmax)) + cwmax = 0xdead; + if(mwl_fwcmd_reg_mac(priv->hw, WL_GET, MAC_REG_TXOP0+(i*4), &txop)) + cwmax = 0xdead; + if(mwl_fwcmd_reg_mac(priv->hw, WL_GET, MAC_REG_AIFSN0+(i*4), &aifsn)) + cwmax = 0xdead; + len += scnprintf(p + len, size - len,"TCQ%d : cwmin=%d cwmax=%d txop=%d aifsn=%d\n", i, cwmin, cwmax, txop, aifsn); + } + + len += scnprintf(p + len, size - len, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_vif_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + struct mwl_vif *mwl_vif; + struct ieee80211_vif *vif; + char ssid[IEEE80211_MAX_SSID_LEN + 1]; + + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + len += scnprintf(p + len, size - len, "\n"); + + spin_lock_bh(&priv->vif_lock); + list_for_each_entry(mwl_vif, &priv->vif_list, list) { + vif = container_of((void *)mwl_vif, struct ieee80211_vif, drv_priv); + len += scnprintf(p + len, size - len, "macid: %d\n", mwl_vif->macid); + switch (vif->type) { + case NL80211_IFTYPE_AP: + len += scnprintf(p + len, size - len, "type: ap\n"); + memcpy(ssid, vif->cfg.ssid, vif->cfg.ssid_len); + ssid[vif->cfg.ssid_len] = 0; + len += scnprintf(p + len, size - len, "ssid: %s\n", ssid); + len += scnprintf(p + len, size - len, "mac address: %pM\n", mwl_vif->bssid); + break; + case NL80211_IFTYPE_STATION: + len += scnprintf(p + len, size - len, "type: sta\n"); + len += scnprintf(p + len, size - len, "mac address: %pM\n", mwl_vif->sta_mac); + break; + default: + len += scnprintf(p + len, size - len, "type: unknown\n"); + break; + } + len += scnprintf(p + len, size - len, "hw_crypto_enabled: %s\n", mwl_vif->is_hw_crypto_enabled ? "true" : "false"); + len += scnprintf(p + len, size - len, "key idx: %d\n", mwl_vif->keyidx); + len += scnprintf(p + len, size - len, "IV: %08x%04x\n", mwl_vif->iv32, mwl_vif->iv16); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_wmm_ptr, mwl_vif->beacon_info.ie_wmm_len, "WMM:"); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_rsn_ptr, mwl_vif->beacon_info.ie_rsn_len, "RSN:"); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_rsn48_ptr, mwl_vif->beacon_info.ie_rsn48_len, "RSN48:"); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_ht_ptr, mwl_vif->beacon_info.ie_ht_len, "HT:"); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_vht_ptr, mwl_vif->beacon_info.ie_vht_len, "VHT:"); + dump_data(p, size, &len, mwl_vif->beacon_info.ie_11k_ptr, mwl_vif->beacon_info.ie_11k_len, "11K:"); + len += scnprintf(p + len, size - len, "\n"); + } + spin_unlock_bh(&priv->vif_lock); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_sta_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + struct mwl_sta *sta_info; + struct ieee80211_sta *sta; + + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + len += scnprintf(p + len, size - len, "\n"); + + spin_lock_bh(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + sta = container_of((void *)sta_info, struct ieee80211_sta, drv_priv); + len += scnprintf(p + len, size - len, "mac address: %pM\n", sta->addr); + len += scnprintf(p + len, size - len, "aid: %u\n", sta->aid); + len += scnprintf(p + len, size - len, "ampdu: %s\n", sta_info->is_ampdu_allowed ? "true" : "false"); + len += scnprintf(p + len, size - len, "amsdu: %s\n", sta_info->is_amsdu_allowed ? "true" : "false"); + if (sta_info->is_amsdu_allowed) { + len += scnprintf(p + len, size - len, "amsdu cap: 0x%02x\n", sta_info->amsdu_ctrl.cap); + } + if (sta->deflink.ht_cap.ht_supported) { + len += scnprintf(p + len, size - len, "ht_cap: 0x%04x, ampdu: %02x, %02x\n", + sta->deflink.ht_cap.cap, + sta->deflink.ht_cap.ampdu_factor, + sta->deflink.ht_cap.ampdu_density); + len += scnprintf(p + len, size - len, "rx_mask: 0x%02x, %02x, %02x, %02x\n", + sta->deflink.ht_cap.mcs.rx_mask[0], + sta->deflink.ht_cap.mcs.rx_mask[1], + sta->deflink.ht_cap.mcs.rx_mask[2], + sta->deflink.ht_cap.mcs.rx_mask[3]); + } + if (sta->deflink.vht_cap.vht_supported) { + len += scnprintf(p + len, size - len, "vht_cap: 0x%08x, mcs: %02x, %02x\n", + sta->deflink.vht_cap.cap, + sta->deflink.vht_cap.vht_mcs.rx_mcs_map, + sta->deflink.vht_cap.vht_mcs.tx_mcs_map); + } + len += scnprintf(p + len, size - len, "rx_bw: %d, rx_nss: %d\n", sta->deflink.bandwidth, sta->deflink.rx_nss); + len += scnprintf(p + len, size - len, "tdls: %d, tdls_init: %d\n", sta->tdls, sta->tdls_initiator); + len += scnprintf(p + len, size - len, "wme: %d, mfp: %d\n", sta->wme, sta->mfp); + len += scnprintf(p + len, size - len, "IV: %08x%04x\n", sta_info->iv32, sta_info->iv16); + len += scnprintf(p + len, size - len, "\n"); + } + spin_unlock_bh(&priv->sta_lock); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_ampdu_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + struct mwl_ampdu_stream *stream; + struct mwl_sta *sta_info; + struct ieee80211_sta *sta; + + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int i = 0; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + len += scnprintf(p + len, size - len, "\n"); + spin_lock_bh(&priv->stream_lock); + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + if (!stream->state) + continue; + len += scnprintf(p + len, size - len, "stream: %d\n", i); + len += scnprintf(p + len, size - len, "idx: %u\n", stream->idx); + len += scnprintf(p + len, size - len, "state: %u\n", stream->state); + if (stream->sta) { + len += scnprintf(p + len, size - len, "mac address: %pM\n", stream->sta->addr); + len += scnprintf(p + len, size - len, "tid: %u\n", stream->tid); + } + } + spin_unlock_bh(&priv->stream_lock); + + spin_lock_bh(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + for (i = 0; i < MWL_MAX_TID; i++) { + if (sta_info->check_ba_failed[i]) { + sta = container_of((void *)sta_info, struct ieee80211_sta, drv_priv); + len += scnprintf(p + len, size - len, "%pM(%d): %d\n", sta->addr, i, sta_info->check_ba_failed[i]); + } + } + } + spin_unlock_bh(&priv->sta_lock); + + len += scnprintf(p + len, size - len, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_dfs_channel_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *channel; + + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int i = 0; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ]; + if (!sband) { + ret = -EINVAL; + goto err; + } + + len += scnprintf(p + len, size - len, "\n"); + for (i = 0; i < sband->n_channels; i++) { + channel = &sband->channels[i]; + if (channel->flags & IEEE80211_CHAN_RADAR) { + len += scnprintf(p + len, size - len, "%d(%d): flags: %08x dfs_state: %d\n", + channel->hw_value, + channel->center_freq, + channel->flags, channel->dfs_state); + len += scnprintf(p + len, size - len, "cac timer: %d ms\n", channel->dfs_cac_ms); + } + } + len += scnprintf(p + len, size - len, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + +err: + free_page(page); + return ret; +} + +static ssize_t mwl_debugfs_dfs_channel_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *channel; + + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int i = 0; + int dfs_state = 0; + int cac_time = -1; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ]; + if (!sband) { + ret = -EINVAL; + goto err; + } + + if (copy_from_user(p, ubuf, min_t(size_t, count, PAGE_SIZE - 1))) { + ret = -EFAULT; + goto err; + } + + ret = sscanf(p, "%d %d", &dfs_state, &cac_time); + + if ((ret < 1) || (ret > 2)) { + ret = -EINVAL; + goto err; + } + + for (i = 0; i < sband->n_channels; i++) { + channel = &sband->channels[i]; + if (channel->flags & IEEE80211_CHAN_RADAR) { + channel->dfs_state = dfs_state; + if (cac_time != -1) { + channel->dfs_cac_ms = cac_time * 1000; + } + } + } + ret = count; + +err: + free_page(page); + return ret; +} + +static ssize_t mwl_debugfs_dfs_radar_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret; + + if (!p) { + return -ENOMEM; + } + + len += scnprintf(p + len, size - len, "\n"); + len += scnprintf(p + len, size - len, "csa_active: %d\n", priv->csa_active); + len += scnprintf(p + len, size - len, "dfs_region: %d\n", priv->reg.dfs_region); + len += scnprintf(p + len, size - len, "pw_filter: %d\n", priv->dfs_pw_filter); + len += scnprintf(p + len, size - len, "min_num_radar: %d\n", priv->dfs_min_num_radar); + len += scnprintf(p + len, size - len, "min_pri_count: %d\n", priv->dfs_min_pri_count); + len += scnprintf(p + len, size - len, "chirp_count_min: %d\n", priv->dfs_chirp_count_min); + len += scnprintf(p + len, size - len, "chirp_time_interval: %d\n", priv->dfs_chirp_time_interval); + len += scnprintf(p + len, size - len, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_dfs_radar_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + + wiphy_info(priv->hw->wiphy, "simulate radar detected\n"); + ieee80211_radar_detected(priv->hw); + + return count; +} + +static ssize_t mwl_debugfs_thermal_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + mwl_fwcmd_get_temp(priv->hw, &priv->temperature); + + len += scnprintf(p + len, size - len, "\n"); + len += scnprintf(p + len, size - len, "quiet period: %d\n", priv->quiet_period); + len += scnprintf(p + len, size - len, "throttle state: %d\n", priv->throttle_state); + len += scnprintf(p + len, size - len, "temperature: %d\n", priv->temperature); + len += scnprintf(p + len, size - len, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + free_page(page); + + return ret; +} + +static ssize_t mwl_debugfs_thermal_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + int throttle_state = 0; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + if (copy_from_user(p, ubuf, min_t(size_t, count, PAGE_SIZE - 1))) { + ret = -EFAULT; + goto err; + } + + if (kstrtoint(p, 0, &throttle_state)) { + ret = -EINVAL; + goto err; + } + + if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) { + wiphy_warn(priv->hw->wiphy,"throttle state %d is exceeding the limit %d\n", + throttle_state, SYSADPT_THERMAL_THROTTLE_MAX); + ret = -EINVAL; + goto err; + } + + priv->throttle_state = throttle_state; + mwl_thermal_set_throttling(priv); + ret = count; + +err: + free_page(page); + return ret; +} + +static int mwl_debugfs_reg_access(struct mwl_priv *priv, bool write) +{ + struct ieee80211_hw *hw = priv->hw; + u8 set = 0; + int ret = -EINVAL; + + set = write ? WL_SET : WL_GET; + + switch (priv->reg_type) { + case MWL_ACCESS_MAC: + ret = mwl_fwcmd_reg_mac(hw, set, priv->reg_offset, &priv->reg_value); + break; + case MWL_ACCESS_RF: + ret = mwl_fwcmd_reg_rf(hw, set, priv->reg_offset, &priv->reg_value); + break; + case MWL_ACCESS_BBP: + ret = mwl_fwcmd_reg_bb(hw, set, priv->reg_offset, &priv->reg_value); + break; + case MWL_ACCESS_CAU: + ret = mwl_fwcmd_reg_cau(hw, set, priv->reg_offset, &priv->reg_value); + break; + default: + // Interface specific + if (priv->if_ops.dbg_reg_access != NULL) { + ret = priv->if_ops.dbg_reg_access(priv, write); + } + } + + return ret; +} + +static ssize_t mwl_debugfs_regrdwr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page; + char *p; + int len = 0; + int size = PAGE_SIZE; + ssize_t ret; + + if (*ppos) { + return len; + } + + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + return -ENOMEM; + } + p = (char *)page; + + if (!priv->reg_type) { + /* No command has been given */ + len += scnprintf(p + len, size - len, "0"); + ret = -EINVAL; + goto none; + } + + /* Get command has been given */ + ret = mwl_debugfs_reg_access(priv, false); + +//done: + if (!ret) + len += scnprintf(p + len, size - len, "%u 0x%08x 0x%08x\n", + priv->reg_type, priv->reg_offset, priv->reg_value); + else + len += scnprintf(p + len, size - len, "error: %lu(%u 0x%08x 0x%08x)\n", + (long unsigned int)ret, priv->reg_type, priv->reg_offset, priv->reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, p, len); + +none: + free_page(page); + return ret; +} + +static ssize_t mwl_debugfs_regrdwr_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *)page; + u32 reg_type = 0; + u32 reg_offset = 0; + u32 reg_value = UINT_MAX; + ssize_t ret = 0; + + if (!p) { + return -ENOMEM; + } + + if (copy_from_user(p, ubuf, min_t(size_t, count, PAGE_SIZE - 1))) { + ret = -EFAULT; + goto err; + } + + ret = sscanf(p, "%u %x %x", ®_type, ®_offset, ®_value); + + if ((ret != 2) && (ret != 3)) { + ret = -EINVAL; + goto err; + } + + if (!reg_type) { + ret = -EINVAL; + goto err; + } else { + priv->reg_type = reg_type; + priv->reg_offset = reg_offset; + + if (ret == 3) { + priv->reg_value = reg_value; + ret = mwl_debugfs_reg_access(priv, true); + if (ret) { + ret = -EINVAL; + goto err; + } + } + + ret = count; + } + +err: + free_page(page); + return ret; +} + +static ssize_t mwl_debugfs_otp_data_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + + return simple_read_from_buffer(ubuf, count, ppos, priv->otp_data.buf, priv->otp_data.len); +} + +static ssize_t mwl_debugfs_device_recovery_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret = 0; + char ctrl[2]; + + ctrl[0]='0'; + ctrl[1]='\n'; + + ret = simple_read_from_buffer(ubuf, count, ppos, ctrl, sizeof(ctrl)); + return ret; +} + + +static ssize_t mwl_debugfs_device_recovery_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char recover = 0; + + if (count > 2) { + pr_err("Invalid Arguments\n\n"); + return -EINVAL; + } + + if (copy_from_user(&recover, ubuf, 1)) { + ret = -EFAULT; + return ret; + } + + switch (recover) + { + case '1': + lrd_radio_recovery(priv); + break; + default : + pr_err("Invalid argument : 1 needed\n"); + return -EINVAL; + break; + } + + return count; +} + +static ssize_t mwl_debugfs_pwrtable_dump_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char dump = 0; + + if(count > 2) { + pr_err("Invalid Arguments\n\n"); + return -EINVAL; + } + + if (copy_from_user(&dump, ubuf, 1)) { + ret = -EFAULT; + return ret; + } + + switch(dump) + { + case '1': + lrd_dump_max_pwr_table(priv); + break; + default : + pr_err("Invalid argument : 1 needed\n"); + return -EINVAL; + break; + } + + return count; +} + +static ssize_t mwl_debugfs_restart_required_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char ctrl[2]; + + if (priv->recovery_in_progress) + ctrl[0]='1'; + else + ctrl[0]='0'; + + ctrl[1]='\n'; + + ret = simple_read_from_buffer(ubuf, count, ppos, ctrl, sizeof(ctrl)); + return ret; +} + +static ssize_t mwl_debugfs_ds_status_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char ds_status[2]; + + ds_status[0]= priv->if_ops.is_deepsleep(priv) ? '1' :'0'; + ds_status[1]='\n'; + + ret = simple_read_from_buffer(ubuf, count, ppos, ds_status, sizeof(ds_status)); + return ret; +} + +static ssize_t mwl_debugfs_ds_ctrl_read(struct file *file, + char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char ctrl[2]; + + ctrl[0]= priv->ds_enable ? '1' :'0'; + ctrl[1]='\n'; + + ret = simple_read_from_buffer(ubuf, count, ppos, ctrl, sizeof(ctrl)); + return ret; +} + +static ssize_t mwl_debugfs_ds_ctrl_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwl_priv *priv = (struct mwl_priv *)file->private_data; + int ret = 0; + char deepsleep; + + if (count > 2) { + pr_err("Invalid Arguments\n\n"); + return -EINVAL; + } + + if (priv->host_if != MWL_IF_SDIO ) { + ret = -EACCES; + return ret; + } + + if (copy_from_user(&deepsleep, ubuf, 1)) { + ret = -EFAULT; + return ret; + } + + switch(deepsleep) { + case '1' : mwl_enable_ds(priv); + break; + case '0' : mwl_disable_ds(priv); + break; + default : pr_err("Invalid argument : 1/0 needed\n"); + return -EINVAL; + break; + } + + return count; +} + + +MWLWIFI_DEBUGFS_FILE_READ_OPS(info); +MWLWIFI_DEBUGFS_FILE_READ_OPS(vif); +MWLWIFI_DEBUGFS_FILE_READ_OPS(sta); +MWLWIFI_DEBUGFS_FILE_READ_OPS(ds_status); +MWLWIFI_DEBUGFS_FILE_OPS(ds_ctrl); +MWLWIFI_DEBUGFS_FILE_READ_OPS(ampdu); +MWLWIFI_DEBUGFS_FILE_OPS(dfs_channel); +MWLWIFI_DEBUGFS_FILE_OPS(dfs_radar); +MWLWIFI_DEBUGFS_FILE_OPS(thermal); +MWLWIFI_DEBUGFS_FILE_OPS(regrdwr); +MWLWIFI_DEBUGFS_FILE_READ_OPS(otp_data); +MWLWIFI_DEBUGFS_FILE_OPS(device_recovery); +MWLWIFI_DEBUGFS_FILE_READ_OPS(restart_required); +MWLWIFI_DEBUGFS_FILE_WRITE_OPS(pwrtable_dump); + +void mwl_debugfs_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + if (!priv->debugfs_phy) + priv->debugfs_phy = debugfs_create_dir("lrdwifi", hw->wiphy->debugfsdir); + + if (!priv->debugfs_phy) + return; + + MWLWIFI_DEBUGFS_ADD_FILE(info); + MWLWIFI_DEBUGFS_ADD_FILE(ds_status); + MWLWIFI_DEBUGFS_ADD_FILE(ds_ctrl); + MWLWIFI_DEBUGFS_ADD_FILE(vif); + MWLWIFI_DEBUGFS_ADD_FILE(sta); + MWLWIFI_DEBUGFS_ADD_FILE(ampdu); + MWLWIFI_DEBUGFS_ADD_FILE(dfs_channel); + MWLWIFI_DEBUGFS_ADD_FILE(dfs_radar); + MWLWIFI_DEBUGFS_ADD_FILE(thermal); + MWLWIFI_DEBUGFS_ADD_FILE(regrdwr); + MWLWIFI_DEBUGFS_ADD_FILE(otp_data); + MWLWIFI_DEBUGFS_ADD_FILE(device_recovery); + MWLWIFI_DEBUGFS_ADD_FILE(restart_required); + MWLWIFI_DEBUGFS_ADD_FILE(pwrtable_dump); +} + +void mwl_debugfs_remove(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + debugfs_remove_recursive(priv->debugfs_phy); + priv->debugfs_phy = NULL; +} diff --git a/drivers/net/wireless/laird/lrdmwl/debugfs.h b/drivers/net/wireless/laird/lrdmwl/debugfs.h new file mode 100644 index 0000000000000..29d6b8d2ffd98 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/debugfs.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines debug fs related functions. */ + +#ifndef _MWL_DEBUGFS_H_ +#define _MWL_DEBUGFS_H_ + +void mwl_debugfs_init(struct ieee80211_hw *hw); +void mwl_debugfs_remove(struct ieee80211_hw *hw); + +#endif /* _MWL_DEBUGFS_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/dev.h b/drivers/net/wireless/laird/lrdmwl/dev.h new file mode 100644 index 0000000000000..70d2b1750c5c0 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/dev.h @@ -0,0 +1,927 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines device related information. */ + +#ifndef _DEV_H_ +#define _DEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "port.h" +#include "pfu.h" + +#define MWL_DRV_NAME KBUILD_MODNAME +#define MWL_DRV_VERSION "P39-20190123" + +#define MWL_FW_ROOT "lrdmwl" + +#define LRD_DESC "Laird Connectivity 60 Series Wireless Network Driver" +#define LRD_AUTHOR "Laird Connectivity" +#define LRD_BLD_VERSION "11.39.0.23" +#define LRD_DRV_VERSION LRD_BLD_VERSION "-" MWL_DRV_VERSION + +/* 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 /* cmd addr lo */ +#define MACREG_REG_INT_CODE 0x00000C14 /* cmd addr hi */ + +#define MACREG_REG_CMD_SIZE 0x00000C40 +#define MACREG_REG_FW_STATUS 0x00000C44 +#define MACREG_REG_CMDRSP_BUF_LO 0x00000CD0 +#define MACREG_REG_CMDRSP_BUF_HI 0x00000CD4 +#define MACREG_REG_DRV_READY 0x00000CF0 + +#define PCIE_SCRATCH_13_REG 0xCF4 + +/* Bit definition for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) */ +#define MACREG_A2HRIC_BIT_NUM_TX_DONE (0) +#define MACREG_A2HRIC_BIT_NUM_RX_RDY (1) +#define MACREG_A2HRIC_BIT_NUM_QUE_EMPTY (10) + +#define MACREG_A2HRIC_BIT_TX_DONE BIT(0) +#define MACREG_A2HRIC_BIT_RX_RDY BIT(1) +#define MACREG_A2HRIC_BIT_OPC_DONE BIT(2) +#define MACREG_A2HRIC_BIT_MAC_EVENT BIT(3) +#define MACREG_A2HRIC_BIT_RX_PROBLEM BIT(4) +#define MACREG_A2HRIC_BIT_RADIO_OFF BIT(5) +#define MACREG_A2HRIC_BIT_RADIO_ON BIT(6) +#define MACREG_A2HRIC_BIT_RADAR_DETECT BIT(7) +#define MACREG_A2HRIC_BIT_ICV_ERROR BIT(8) +#define MACREG_A2HRIC_BIT_WEAKIV_ERROR BIT(9) +#define MACREG_A2HRIC_BIT_QUE_EMPTY BIT(10) +#define MACREG_A2HRIC_BIT_QUE_FULL BIT(11) +#define MACREG_A2HRIC_BIT_CHAN_SWITCH BIT(12) +#define MACREG_A2HRIC_BIT_TX_WATCHDOG BIT(13) +#define MACREG_A2HRIC_BA_WATCHDOG BIT(14) +/* 15 taken by ISR_TXACK */ +#define MACREG_A2HRIC_BIT_SSU_DONE BIT(16) +#define MACREG_A2HRIC_CONSEC_TXFAIL BIT(17) + +#define ISR_SRC_BITS (MACREG_A2HRIC_BIT_RX_RDY | \ + MACREG_A2HRIC_BIT_TX_DONE | \ + MACREG_A2HRIC_BIT_OPC_DONE | \ + MACREG_A2HRIC_BIT_MAC_EVENT | \ + MACREG_A2HRIC_BIT_WEAKIV_ERROR | \ + MACREG_A2HRIC_BIT_ICV_ERROR | \ + MACREG_A2HRIC_BIT_SSU_DONE | \ + MACREG_A2HRIC_BIT_RADAR_DETECT | \ + MACREG_A2HRIC_BIT_CHAN_SWITCH | \ + MACREG_A2HRIC_BIT_TX_WATCHDOG | \ + MACREG_A2HRIC_BIT_QUE_EMPTY | \ + MACREG_A2HRIC_BA_WATCHDOG | \ + MACREG_A2HRIC_CONSEC_TXFAIL) + +#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS + +/* Bit definition for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) */ +#define MACREG_H2ARIC_BIT_PPA_READY BIT(0) +#define MACREG_H2ARIC_BIT_DOOR_BELL BIT(1) +#define MACREG_H2ARIC_BIT_PS BIT(2) +#define MACREG_H2ARIC_BIT_PSPOLL BIT(3) +#define ISR_RESET BIT(15) +#define ISR_RESET_AP33 BIT(26) + +/* Data descriptor related constants */ +#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 +#define EAGLE_RXD_CTRL_DMA_OWN 0x80 + +#define EAGLE_RXD_STATUS_OK 0x01 + +#define EAGLE_TXD_STATUS_IDLE 0x00000000 +#define EAGLE_TXD_STATUS_OK 0x00000001 +#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 + +/* Antenna control */ + +#define MWL_8997_DEF_TX_ANT_BMP (0x3) +#define MWL_8997_DEF_RX_ANT_BMP (0x3) + +#define ANTENNA_TX_4_AUTO 0 +#define ANTENNA_TX_1 1 +#define ANTENNA_TX_2 3 +#define ANTENNA_TX_3 7 +#define ANTENNA_RX_4_AUTO 0 +#define ANTENNA_RX_1 1 +#define ANTENNA_RX_2 3 +#define ANTENNA_RX_3 7 + +/* Band related constants */ +#define BAND_24_CHANNEL_NUM 14 +#define BAND_24_RATE_NUM 12 +#define BAND_50_CHANNEL_NUM 25 +#define BAND_50_RATE_NUM 8 + +/* vif and station */ +#define NUM_WEP_KEYS 4 +#define MWL_MAX_TID 8 +#define MWL_AMSDU_SIZE_4K 0 +#define MWL_AMSDU_SIZE_8K 1 + +/* power init */ +#define MWL_POWER_INIT_1 1 +#define MWL_POWER_INIT_2 2 + +enum { + MWL8864 = 0, + MWL8897, + MWL8964, + MWL8997, + MWLUNKNOWN, +}; + +enum { + AP_MODE_11AC = 0x10, /* generic 11ac indication mode */ + AP_MODE_2_4GHZ_11AC_MIXED = 0x17, +}; + +enum { + AMPDU_NO_STREAM, + AMPDU_STREAM_NEW, + AMPDU_STREAM_IN_PROGRESS, + AMPDU_STREAM_ACTIVE, +}; + +enum { + MWL_IF_USB = 0x04, + MWL_IF_PCIE = 0x03, + MWL_IF_SDIO = 0x02, +}; + +enum { + RX_PAYLOAD_TYPE_FRAME_DATA, + RX_PAYLOAD_TYPE_EVENT_INFO, +}; + +#define CMD_BUF_SIZE 0x4000 + +struct mwl_region_mapping { + u8 cc[2]; +}; + +struct mwl_chip_info { + const char *part_name; + const char *fw_image; + const char *mfg_image; + int antenna_tx; + int antenna_rx; +}; + +struct mwl_device_pwr_tbl { + u8 channel; + u8 tx_pwr[SYSADPT_TX_PWR_LEVEL_TOTAL_SC4]; + u8 dfs_capable; + u8 ax_ant; + u8 cdd; +}; + +struct mwl_tx_pwr_tbl { + u8 channel; + u8 setcap; + u16 txantenna2; + u16 tx_power[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; + bool cdd; +}; + +struct mwl_hw_data { + u32 fw_release_num; /* MajNbr:MinNbr:SubMin:PatchLevel */ + u8 hw_version; /* plain number indicating version */ + u8 host_interface; /* plain number of interface */ + u16 max_num_tx_desc; /* max number of TX descriptors */ + u16 max_num_mc_addr; /* max number multicast addresses */ + u16 num_antennas; /* number antennas used */ + u16 region_code; /* region (eg. 0x10 for USA FCC) */ + unsigned char mac_addr[ETH_ALEN]; /* well known -> AA:BB:CC:DD:EE:FF */ +}; + +#define MWL_TX_RATE_FORMAT_MASK 0x00000003 +#define MWL_TX_RATE_BANDWIDTH_MASK 0x00000030 +#define MWL_TX_RATE_BANDWIDTH_SHIFT 4 +#define MWL_TX_RATE_SHORTGI_MASK 0x00000040 +#define MWL_TX_RATE_SHORTGI_SHIFT 6 +#define MWL_TX_RATE_RATEIDMCS_MASK 0x00007F00 +#define MWL_TX_RATE_RATEIDMCS_SHIFT 8 +#define MWL_TX_RATE_NSS_MASK 0x00030000 +#define MWL_TX_RATE_NSS_SHIFT 16 + +/* Transmit rate information constants */ +#define TX_RATE_FORMAT_LEGACY 0 +#define TX_RATE_FORMAT_11N 1 +#define TX_RATE_FORMAT_11AC 2 + +#define TX_RATE_BANDWIDTH_20 0 +#define TX_RATE_BANDWIDTH_40 1 +#define TX_RATE_BANDWIDTH_80 2 +#define TX_RATE_BANDWIDTH_160 3 + +#define TX_RATE_INFO_STD_GI 0 +#define TX_RATE_INFO_SHORT_GI 1 + + +#define MWL_TX_WCB_FLAGS_DONT_ENCRYPT 0x00000001 +#define MWL_TX_WCB_FLAGS_NO_CCK_RATE 0x00000002 + +struct mwl_tx_desc { + u8 data_rate; + u8 tx_priority; + __le16 qos_ctrl; + __le32 pkt_ptr; + __le16 pkt_len; + u8 dest_addr[ETH_ALEN]; + __le32 pphys_next; + __le32 sap_pkt_info; + __le32 rate_info; + u8 type; + u8 xmit_control; /* bit 0: use rateinfo, bit 1: disable ampdu */ + __le16 reserved; + __le32 tcpack_sn; + __le32 tcpack_src_dst; + __le32 reserved1; + __le32 reserved2; + u8 reserved3[2]; + u8 packet_info; + u8 packet_id; + __le16 packet_len_and_retry; + __le16 packet_rate_info; + __le32 flags; + __le32 status; +} __packed; + +struct mwl_tx_hndl { + struct sk_buff *psk_buff; + struct mwl_tx_desc *pdesc; + struct mwl_tx_hndl *pnext; +}; + +#define MWL_RX_RATE_FORMAT_MASK 0x0007 +#define MWL_RX_RATE_NSS_MASK 0x0018 +#define MWL_RX_RATE_NSS_SHIFT 3 +#define MWL_RX_RATE_BW_MASK 0x0060 +#define MWL_RX_RATE_BW_SHIFT 5 +#define MWL_RX_RATE_GI_MASK 0x0080 +#define MWL_RX_RATE_GI_SHIFT 7 +#define MWL_RX_RATE_RT_MASK 0xFF00 +#define MWL_RX_RATE_RT_SHIFT 8 + +struct mwl_rx_desc { + __le16 pkt_len; /* total length of received data */ + __le16 rate; /* receive rate information */ + __le32 pphys_buff_data; /* physical address of payload data */ + __le32 pphys_next; /* physical address of next RX desc */ + __le16 qos_ctrl; /* received QosCtrl field variable */ + __le16 ht_sig2; /* like name states */ + __le32 hw_rssi_info; + __le32 hw_noise_floor_info; + s8 noise_floor; + u8 reserved[2]; + u8 payldType; + s8 rssi; /* received signal strengt indication */ + u8 status; /* status field containing USED bit */ + u8 channel; /* channel this pkt was received on */ + u8 rx_control; /* the control element of the desc */ + __le32 reserved1[3]; +} __packed; + +struct mwl_rx_hndl { + struct sk_buff *psk_buff; /* associated sk_buff for Linux */ + struct mwl_rx_desc *pdesc; + struct mwl_rx_hndl *pnext; +}; + +#define MWL_RX_EVNT_RADAR_DETECT 0x2 +#define MWL_RX_EVENT_LINKLOSS_DETECT 0x3 + +#define MWL_RX_EVENT_WOW_LINKLOSS_DETECT 0x14 +#define MWL_RX_EVENT_WOW_AP_DETECT 0x15 +#define MWL_RX_EVENT_WOW_RX_DETECT 0x16 +#define MWL_RX_EVENT_REG 0x17 + +struct mwl_rx_event_data { + u16 event_id; + u16 length; + u32 event_info; +}__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_hndl *tx_hndl; + struct mwl_tx_hndl *pnext_tx_hndl; /* next TX handle that can be used */ + struct mwl_tx_hndl *pstale_tx_hndl;/* the staled TX handle */ + + dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */ + struct mwl_rx_desc *prx_ring; /* ptr to first RX desc (virt.) */ + struct mwl_rx_hndl *rx_hndl; + struct mwl_rx_hndl *pnext_rx_hndl; /* next RX handle that can be used */ + u32 wcb_base; /* FW base offset for registers */ + u32 rx_desc_write; /* FW descriptor write position */ + u32 rx_desc_read; /* FW descriptor read position */ + u32 rx_buf_size; /* length of the RX buffers */ +}; + +struct mwl_ampdu_stream { + struct ieee80211_sta *sta; + u8 tid; + u8 state; + u8 idx; +}; + +struct mwl_survey_info { + struct ieee80211_channel channel; + u32 filled; + u32 time_period; + u32 time_busy; + u32 time_tx; + s8 noise; +}; + +struct mwl_roc_info { + bool in_progress; + bool tmr_running; + u8 type; + u8 duration; + u32 chan; + struct timer_list roc_timer; +}; + +#ifdef CONFIG_PM +/* Wakeup conditions */ +#define MWL_WOW_CND_DISCONNECT 0x0001 +#define MWL_WOW_CND_AP_INRANGE 0x0002 +#define MWL_WOW_CND_RX_DATA 0x0100 + +struct mwl_wowlan_apinrange_addrIe { + u8 address[ETH_ALEN]; +}; + +struct mwl_wowlan_apinrange_ssidIe { + u8 ssidLen; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + + +/* WOW states */ +#define WOWLAN_STATE_ENABLED 0x01 +#define WOWLAN_STATE_HS_SENT 0x80 + +#define WOWLAN_JIFFIES 1000 /* msec */ + +struct mwl_wowlan_result { + u32 reason; /* reason for waking */ + struct mwl_vif *mwl_vif; /* interface which generated wakeup*/ + u16 n_channels; + u32 channels[SYSADPT_MAX_NUM_CHANNELS]; +}; + +struct mwl_wowlan_cfg { + u8 capable; /* Indicates host controller is capable of + supporting wow */ + u8 state; /* indicates state of wow */ + u8 wakeSigType; /* Data sheet indicate this is to be active low */ + u32 wowlanCond; /* Conditions to wake for */ + unsigned long jiffies; /* time resume called */ + int irq_wakeup; + + /* Configuration data */ + u16 addrListCnt; + u16 ssidListCnt; + u16 channelCnt; + u8 channels[SYSADPT_MAX_NUM_CHANNELS]; + struct mwl_wowlan_apinrange_addrIe addrList[1]; + struct mwl_wowlan_apinrange_ssidIe ssidList[1]; + + /* Result data */ + struct mwl_wowlan_result results; +}; +#endif + +#ifdef CONFIG_DEBUG_FS +#define MWL_ACCESS_MAC 1 +#define MWL_ACCESS_RF 2 +#define MWL_ACCESS_BBP 3 +#define MWL_ACCESS_CAU 4 +#define MWL_ACCESS_ADDR0 5 +#define MWL_ACCESS_ADDR1 6 +#define MWL_ACCESS_ADDR 7 +#endif + +struct mwl_priv; +#define INTF_CMDHEADER_LEN(hd_len) (hd_len/sizeof(unsigned short)) + +struct mwl_if_ops { + unsigned short inttf_head_len; + struct tasklet_struct *ptx_task; + struct tasklet_struct *ptx_done_task; + struct tasklet_struct *pqe_task; + struct workqueue_struct *ptx_workq; + struct work_struct *ptx_work; + struct mwl_chip_info mwl_chip_tbl; + + int (*init_if) (struct mwl_priv *); + int (*init_if_post) (struct mwl_priv *); + void (*cleanup_if) (struct mwl_priv *); + bool (*check_card_status) (struct mwl_priv *); + int (*prog_fw) (struct mwl_priv *); + int (*register_dev) (struct mwl_priv *); + void (*unregister_dev) (struct mwl_priv *); + void (*enable_int) (struct mwl_priv *); + void (*disable_int) (struct mwl_priv *); + void (*tx_done) (unsigned long); + int (*host_to_card) (struct mwl_priv *, int, struct sk_buff *); + int (*cmd_resp_wait_completed) (struct mwl_priv *, unsigned short); + int (*wakeup) (struct mwl_priv *); + void (*wakeup_complete) (struct mwl_priv *); + void (*enter_deepsleep) (struct mwl_priv *); + void (*flush_amsdu)(unsigned long); + +#ifdef CONFIG_DEBUG_FS + int (*dbg_info)(struct mwl_priv *, char*, int, int); + int (*dbg_reg_access)(struct mwl_priv *, bool); +#endif + + /* Interface specific functions */ + int (*send_cmd) (struct mwl_priv *); + bool (*is_tx_available) (struct mwl_priv *, int); + void (*read_reg) (struct mwl_priv *, int, int, u32 *); + void (*write_reg) (struct mwl_priv *, int, int, u32); + void (*update_mp_end_port) (struct mwl_priv *, u16); + void (*cleanup_mpa_buf) (struct mwl_priv *); + int (*cmdrsp_complete) (struct mwl_priv *, struct sk_buff *); + int (*event_complete) (struct mwl_priv *, struct sk_buff *); + int (*init_fw_port) (struct mwl_priv *); + void (*card_reset) (struct mwl_priv *); + int (*reg_dump)(struct mwl_priv *, char *); + void (*device_dump)(struct mwl_priv *); + int (*clean_pcie_ring) (struct mwl_priv *); + void (*deaggr_pkt)(struct mwl_priv *, struct sk_buff *); + void (*multi_port_resync)(struct mwl_priv *); + int (*hardware_restart)(struct mwl_priv *); + int (*wakeup_card)(struct mwl_priv *); + int (*is_deepsleep)(struct mwl_priv *); + void (*down_dev)(struct mwl_priv *); + void (*up_dev)(struct mwl_priv *); + void (*down_pwr)(struct mwl_priv *); + int (*up_pwr)(struct mwl_priv *); +}; + +#define MWL_OTP_BUF_SIZE (256*8) //258 lines * 8 bytes + +struct otp_data { + u8 buf[MWL_OTP_BUF_SIZE]; + u32 len; // Actual size of data in buf[] +}; + +#define DS_SLEEP 1 +#define DS_AWAKE 0 + +#define PS_SLEEP 1 +#define PS_AWAKE 0 + +#define DS_ENABLE_OFF 0 +#define DS_ENABLE_ON 1 +#define DS_ENABLE_PAUSE 2 + +//Laird radio capability bits +#define LRD_CAP_SU60 BIT(0) +#define LRD_CAP_440 BIT(1) +#define LRD_CAP_SOM8MP BIT(2) +#define LRD_CAP_BT_SEQ BIT(3) +#define LRD_CAP_BT_DUP BIT(4) + +struct lrd_radio_caps { + __le16 capability; + __le16 num_mac; + __le16 version; +}; + +#define CC_AWM_TIMER 3600000 /* mS */ + +#define MIN_AWM_SIZE 26 + +#define REG_CODE_ETSI 0x30 +#define REG_CODE_ETSI0 0X60 +#define REG_CODE_WW 0xFF + +struct cc_info +{ + u32 region; + u8 alpha2[2]; +}; + +struct lrd_regulatory { + struct timer_list timer_awm; + struct work_struct event; + struct work_struct awm; + struct mutex mutex; + + struct cc_info otp; + struct cc_info cc; + struct cc_info hint; + u32 pn; + + bool regulatory_set; + enum nl80211_dfs_regions dfs_region; + + u8 *db; + int db_size; +}; + +struct mwl_priv { + struct ieee80211_hw *hw; + struct firmware *fw_ucode; + struct lrd_regulatory reg; + u8 number_of_channels; + int chip_type; + + struct device_node *dt_node; + struct device_node *pwr_node; + bool disable_2g; + bool disable_5g; + + int ant_tx_bmp; + int ant_tx_num; // Num of ant in use + + int ant_rx_bmp; + int ant_rx_num; // Num of ant in use + + struct mwl_tx_pwr_tbl tx_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS]; + bool cdd; + u16 txantenna2; + u8 powinited; + u16 max_tx_pow[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; /* max tx power (dBm) */ + u16 target_powers[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; /* target powers */ + u16 target_power; + + void *intf; + struct mwl_if_ops if_ops; + struct device *dev; + u8 host_if; + + struct mutex fwcmd_mutex; /* for firmware command */ + unsigned short *pcmd_buf; /* pointer to CmdBuf (virtual) */ + dma_addr_t pphys_cmd_buf; /* pointer to CmdBuf (physical) */ + unsigned short *pcmd_event_buf; /* pointer to EvtBuf (virtual) */ + u8 cmd_seq_num; /* CMD Seq Number */ + bool cmd_timeout; + int irq; + struct mwl_hw_data hw_data; /* Adapter HW specific info */ + u8 tc_2_txq_map[SYSADPT_TX_WMM_QUEUES]; + u8 txq_2_tc_map[SYSADPT_TX_WMM_QUEUES]; + + /* various descriptor data */ + /* for tx descriptor data */ + spinlock_t tx_desc_lock ____cacheline_aligned_in_smp; + struct mwl_desc_data desc_data[SYSADPT_NUM_OF_DESC_DATA]; + /* number of descriptors owned by fw at any one time */ + int fw_desc_cnt[SYSADPT_NUM_OF_DESC_DATA]; + + bool is_tx_done_schedule; + u32 qe_trigger_num; + unsigned long qe_trigger_time; + + struct sk_buff_head txq[SYSADPT_NUM_OF_DESC_DATA]; + + struct tasklet_struct rx_task; + int txq_limit; + int recv_limit; + + struct timer_list period_timer; + bool shutdown; + + struct timer_list ds_timer; + u8 ds_enable; + u16 ps_mode; + +#ifdef CONFIG_PM + struct mwl_wowlan_cfg wow; +#endif + + /* Remain on channel info */ + struct mwl_roc_info roc; + + /* keep survey information */ + bool sw_scanning; + int survey_info_idx; + struct mwl_survey_info survey_info[SYSADPT_MAX_NUM_CHANNELS]; + struct mwl_survey_info cur_survey_info; + bool cur_survey_valid; + + s8 noise; /* Most recently reported noise in dBm */ + + struct ieee80211_supported_band band_24; + struct ieee80211_channel channels_24[BAND_24_CHANNEL_NUM]; + struct ieee80211_rate rates_24[BAND_24_RATE_NUM]; + struct ieee80211_supported_band band_50; + struct ieee80211_channel channels_50[BAND_50_CHANNEL_NUM]; + struct ieee80211_rate rates_50[BAND_50_RATE_NUM]; + + u32 ap_macids_supported; + u32 sta_macids_supported; + u32 adhoc_macids_supported; + u32 macids_used; + u32 running_bsses; /* bitmap of running BSSes */ + + struct { + spinlock_t vif_lock; /* for private interface info */ + struct list_head vif_list; /* List of interfaces. */ + } ____cacheline_aligned_in_smp; + + struct { + spinlock_t sta_lock; /* for private sta info */ + struct list_head sta_list; /* List of stations */ + } ____cacheline_aligned_in_smp; + + bool radio_on; + bool radio_short_preamble; + bool wmm_enabled; + + /* 0=BK, 1=BE, 2=VI, 3=VO */ + struct ieee80211_tx_queue_params wmm_params[SYSADPT_TX_WMM_QUEUES]; + + /* ampdu stream information */ + /* for ampdu stream */ + struct { + spinlock_t stream_lock; /* for BA stream */ + struct mwl_ampdu_stream ampdu[SYSADPT_TX_AMPDU_QUEUES]; + } ____cacheline_aligned_in_smp; + struct work_struct watchdog_ba_handle; + + bool csa_active; + struct work_struct chnl_switch_handle; + u16 dfs_chirp_count_min; + u16 dfs_chirp_time_interval; + u16 dfs_pw_filter; + u16 dfs_min_num_radar; + u16 dfs_min_pri_count; + + struct thermal_cooling_device *cdev; + u32 throttle_state; + u32 quiet_period; + s32 temperature; + u64 LastBeaconTime; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_phy; + u32 reg_type; + u32 reg_offset; + u32 reg_value; + + unsigned long valid_interrupt_cnt; + + /** Combined tx stats */ + unsigned long tx_mgmt_cnt; + unsigned long tx_data_cnt; +#endif + + struct firmware *cal_data; + + /** Write pointer for TXBD ring */ + unsigned int txbd_wrptr; + /** Shadow copy of TXBD read pointer */ + unsigned int txbd_rdptr; + /** TXBD ring size */ + unsigned int txbd_ring_size; + /** Lock for protecting the TX ring */ + void *tx_ring_lock; + /** Virtual base address of txbd_ring */ + unsigned char *txbd_ring_vbase; + /** Physical base address of txbd_ring */ + unsigned long long txbd_ring_pbase; + /** Ring of buffer descriptors for TX */ + struct _mlan_pcie_data_buf *txbd_ring[MLAN_MAX_TXRX_BD]; + + struct sk_buff *tx_buf_list[MLAN_MAX_TXRX_BD]; + /** Flush indicator for txbd_ring */ + unsigned char txbd_flush; + + struct workqueue_struct *rx_defer_workq; + struct work_struct rx_defer_work; + struct workqueue_struct *lrd_workq; + struct work_struct ds_work; + struct sk_buff_head rx_defer_skb_q; + bool is_rx_defer_schedule; + + struct otp_data otp_data; + + bool mfg_mode; + + bool recovery_in_progress; + bool recovery_in_progress_full; + struct task_struct *recovery_owner; + struct workqueue_struct *restart_workq; + struct work_struct restart_work; + + struct lrd_radio_caps radio_caps; + bool monitor_mode; + bool init_complete; + bool tx_amsdu_enable; + bool mac_started; + + bool host_crypto; + bool stop_shutdown; + bool mac_init_complete; + struct delayed_work stop_shutdown_work; + + /** Work around for startup failure */ + atomic_t null_scan_count; + + unsigned int ant_gain_adjust; + wait_queue_head_t tx_flush_wq; +}; + +struct beacon_info { + bool valid; + u16 cap_info; + u8 power_constraint; + u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + //Buffer sizes should match hostcmd_cmd_set_ies sizes + u8 ie_list_ht[148]; + u8 ie_list_vht[24]; + u8 ie_list_11k[106]; + u8 *ie_ssid_ptr; + u8 *ie_wmm_ptr; + u8 *ie_wsc_ptr; + u8 *ie_rsn_ptr; + u8 *ie_rsn48_ptr; + u8 *ie_ht_ptr; + u8 *ie_vht_ptr; + u8 *ie_country_ptr; + u8 *ie_wfd_ptr; + u8 *ie_ibss_parms_ptr; + u8 *ie_11k_ptr; + u8 ie_ssid_len; + u8 ie_wmm_len; + u8 ie_wsc_len; + u8 ie_rsn_len; + u8 ie_rsn48_len; + u8 ie_ht_len; + u8 ie_vht_len; + u8 ie_country_len; + u8 ie_wfd_len; + u8 ie_ibss_parms_len; + u8 ie_11k_len; +}; + +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) + WLAN_KEY_LEN_WEP104]; + } wep_key_conf[NUM_WEP_KEYS]; + u8 bssid[ETH_ALEN]; /* BSSID */ + u8 sta_mac[ETH_ALEN]; /* Station mac address */ + /* A flag to indicate is HW crypto is enabled for this bssid */ + bool is_hw_crypto_enabled; + + /* A flag to force host crypto for a mix of HW/host crypto */ + bool force_host_crypto; + + /* Indicate if this is station mode */ + struct beacon_info beacon_info; + u16 iv16; + u32 iv32; + s8 keyidx; + s8 tx_key_idx; /*static WEP tx key index */ +}; + +struct mwl_tx_info { + unsigned long start_time; + u32 pkts; +}; + +struct mwl_amsdu_frag { + struct sk_buff *skb; + u8 *cur_pos; + unsigned long jiffies; + u8 pad; + u8 num; +}; + +struct mwl_amsdu_ctrl { + struct mwl_amsdu_frag frag[SYSADPT_TX_WMM_QUEUES]; + u8 cap; +}; + +struct mwl_sta { + struct list_head list; + struct ieee80211_sta * sta; + struct ieee80211_vif * vif; + bool is_mesh_node; + bool is_ampdu_allowed; + struct mwl_tx_info tx_stats[MWL_MAX_TID]; + u32 check_ba_failed[MWL_MAX_TID]; + bool is_amsdu_allowed; + /* for amsdu aggregation */ + struct { + spinlock_t amsdu_lock; /* for amsdu */ + struct mwl_amsdu_ctrl amsdu_ctrl; + } ____cacheline_aligned_in_smp; + u16 iv16; + u32 iv32; + + struct work_struct rc_update_work; + struct mwl_priv *mwl_private; +}; + +/* DMA header used by firmware and hardware. */ + +struct mwl_dma_data { + __le16 fwlen; + struct ieee80211_hdr wh; + char data[0]; +} __packed; + +struct mwl_tx_pfu_dma_data { + struct mwl_tx_desc tx_desc; + __le16 fwlen; + struct ieee80211_hdr wh; + char data[0]; +} __packed; + +/* Transmission information to transmit a socket buffer. */ +struct mwl_tx_ctrl { + void *vif; + void *sta; + void *k_conf; + void *amsdu_pkts; + u8 tx_priority; + u8 type; + u16 qos_ctrl; + u8 xmit_control; +}; + +static inline struct mwl_vif *mwl_dev_get_vif(const struct ieee80211_vif *vif) +{ + return (struct mwl_vif *)&vif->drv_priv; +} + +static inline struct mwl_sta *mwl_dev_get_sta(const struct ieee80211_sta *sta) +{ + return (struct mwl_sta *)&sta->drv_priv; +} + +void mwl_enable_ds(struct mwl_priv *); +void mwl_disable_ds(struct mwl_priv *); +void mwl_resume_ds(struct mwl_priv *); +void mwl_pause_ds(struct mwl_priv *); + + +/* Defined in mac80211.c. */ +extern const struct ieee80211_ops mwl_mac80211_ops; + + +struct mwifiex_fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_length; + __le32 crc; +} __packed; + +struct mwifiex_fw_data { + struct mwifiex_fw_header header; + __le32 seq_num; + u8 data[1]; +} __packed; + +#define MWIFIEX_FW_DNLD_CMD_1 0x1 +#define MWIFIEX_FW_DNLD_CMD_5 0x5 +#define MWIFIEX_FW_DNLD_CMD_6 0x6 +#define MWIFIEX_FW_DNLD_CMD_7 0x7 + + +#endif /* _DEV_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/fwcmd.c b/drivers/net/wireless/laird/lrdmwl/fwcmd.c new file mode 100644 index 0000000000000..082c20d810a24 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/fwcmd.c @@ -0,0 +1,4515 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements firmware host command related + * functions. + */ + +#include +#include + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "hostcmd.h" +#include "debugfs.h" + +#include "sdio.h" +#include "pcie.h" + +#define MAX_WAIT_FW_COMPLETE_ITERATIONS 2000 +#define MAX_WAIT_GET_HW_SPECS_ITERATONS 3 + +extern int lrd_debug; + +static bool mwl_fwcmd_chk_adapter(struct mwl_priv *priv) +{ + bool rc; + + rc = priv->if_ops.check_card_status(priv); + + return rc; +} + +static int mwl_fwcmd_send_cmd(struct mwl_priv *priv) +{ + return priv->if_ops.send_cmd(priv); +} + +#define ENTRY(a) { case a: ptr= #a; break; } + +char *mwl_fwcmd_get_cmd_string(unsigned short cmd) +{ + static char buf[2 * sizeof(unsigned short) + 3]; + char *ptr = buf; + + cmd &= ~HOSTCMD_RESP_BIT; + + switch (cmd) { + ENTRY(HOSTCMD_CMD_GET_HW_SPEC) + ENTRY(HOSTCMD_CMD_SET_HW_SPEC) + ENTRY(HOSTCMD_CMD_802_11_GET_STAT) + ENTRY(HOSTCMD_CMD_MAC_REG_ACCESS) + ENTRY(HOSTCMD_CMD_BBP_REG_ACCESS) + ENTRY(HOSTCMD_CMD_RF_REG_ACCESS) + ENTRY(HOSTCMD_CMD_802_11_RADIO_CONTROL) + ENTRY(HOSTCMD_CMD_MEM_ADDR_ACCESS) + ENTRY(HOSTCMD_CMD_802_11_TX_POWER) + ENTRY(HOSTCMD_CMD_802_11_RF_ANTENNA) + ENTRY(HOSTCMD_CMD_802_11_PS_MODE) + ENTRY(HOSTCMD_CMD_802_11_RF_ANTENNA_V2) + ENTRY(HOSTCMD_CMD_BROADCAST_SSID_ENABLE) + ENTRY(HOSTCMD_CMD_MFG) + ENTRY(HOSTCMD_CMD_SET_CFG) + ENTRY(HOSTCMD_CMD_SET_PRE_SCAN) + ENTRY(HOSTCMD_CMD_SET_POST_SCAN) + ENTRY(HOSTCMD_CMD_SET_RF_CHANNEL) + ENTRY(HOSTCMD_CMD_SET_AID) + ENTRY(HOSTCMD_CMD_SET_INFRA_MODE) + ENTRY(HOSTCMD_CMD_802_11_RTS_THSD) + ENTRY(HOSTCMD_CMD_SET_EDCA_PARAMS) + ENTRY(HOSTCMD_CMD_802_11H_DETECT_RADAR) + ENTRY(HOSTCMD_CMD_SET_WMM_MODE) + ENTRY(HOSTCMD_CMD_HT_GUARD_INTERVAL) + ENTRY(HOSTCMD_CMD_SET_FIXED_RATE) + ENTRY(HOSTCMD_CMD_SET_IES) + ENTRY(HOSTCMD_CMD_SET_LINKADAPT_CS_MODE) + ENTRY(HOSTCMD_CMD_SET_MAC_ADDR) + ENTRY(HOSTCMD_CMD_SET_RATE_ADAPT_MODE) + ENTRY(HOSTCMD_CMD_GET_WATCHDOG_BITMAP) + ENTRY(HOSTCMD_CMD_DEL_MAC_ADDR) + ENTRY(HOSTCMD_CMD_BSS_START) + ENTRY(HOSTCMD_CMD_AP_BEACON) + ENTRY(HOSTCMD_CMD_SET_NEW_STN) + ENTRY(HOSTCMD_CMD_SET_APMODE) + ENTRY(HOSTCMD_CMD_SET_SWITCH_CHANNEL) + ENTRY(HOSTCMD_CMD_UPDATE_ENCRYPTION) + ENTRY(HOSTCMD_CMD_BASTREAM) + ENTRY(HOSTCMD_CMD_SET_SPECTRUM_MGMT) + ENTRY(HOSTCMD_CMD_SET_POWER_CONSTRAINT) + ENTRY(HOSTCMD_CMD_SET_COUNTRY_CODE) + ENTRY(HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL) + ENTRY(HOSTCMD_CMD_SET_MIMOPSHT) + ENTRY(HOSTCMD_CMD_SET_WSC_IE) + ENTRY(HOSTCMD_CMD_DWDS_ENABLE) + ENTRY(HOSTCMD_CMD_FW_FLUSH_TIMER) + ENTRY(HOSTCMD_CMD_SET_CDD) + ENTRY(HOSTCMD_CMD_CAU_REG_ACCESS) + ENTRY(HOSTCMD_CMD_GET_TEMP) + ENTRY(HOSTCMD_CMD_GET_FW_REGION_CODE) + ENTRY(HOSTCMD_CMD_GET_DEVICE_PWR_TBL) + ENTRY(HOSTCMD_CMD_GET_FW_REGION_CODE_SC4) + ENTRY(HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4) + ENTRY(HOSTCMD_CMD_QUIET_MODE) + ENTRY(HOSTCMD_CMD_802_11_SLOT_TIME) + ENTRY(HOSTCMD_CMD_EDMAC_CTRL) + ENTRY(HOSTCMD_CMD_DUMP_OTP_DATA) + ENTRY(HOSTCMD_CMD_HOSTSLEEP_CTRL) + ENTRY(HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG) + ENTRY(HOSTCMD_CMD_MONITOR_MODE) + ENTRY(HOSTCMD_CMD_DEEPSLEEP) + ENTRY(HOSTCMD_CMD_CONFIRM_PS) + ENTRY(HOSTCMD_CMD_IBSS_START) + ENTRY(HOSTCMD_LRD_CMD) + ENTRY(HOSTCMD_LRD_MFG) + ENTRY(HOSTCMD_LRD_REGION_MAPPING) + + default: + sprintf(buf, "0x%02x", cmd); + break; + } + + return ptr; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_get_cmd_string); + +void mwl_hex_dump(const void *buf, size_t len) +{ + const char *level = ""; + const char *prefix_str = ""; + int prefix_type = DUMP_PREFIX_OFFSET; + int rowsize = 16; + int groupsize = 1; + bool ascii = false; + const u8 *ptr = buf; + int i, linelen, remaining = len; + unsigned char linebuf[32 * 3 + 2 + 32 + 1]; + + for (i = 0; i < len; i += rowsize) { + linelen = min(remaining, rowsize); + remaining -= rowsize; + + hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, + linebuf, sizeof(linebuf), ascii); + + switch (prefix_type) { + case DUMP_PREFIX_ADDRESS: + pr_info("%s%s%p: %s\n", + level, prefix_str, ptr + i, linebuf); + break; + case DUMP_PREFIX_OFFSET: + pr_info("%s%s%.8x: %s\n", + level, prefix_str, i, linebuf); + break; + default: + pr_info("%s%s%s\n", + level, prefix_str, linebuf); + break; + } + } + return; +} +EXPORT_SYMBOL_GPL(mwl_hex_dump); + +/*Note: When calling this function, it is expected that the fwcmd_mutex is already held */ +static int mwl_fwcmd_exec_cmd(struct mwl_priv *priv, unsigned short cmd) +{ + int rc = -EIO; + struct hostcmd_header *presp; + struct hostcmd_header *pcmd; + + might_sleep(); + + // If recovery is in progress, firmware is hung or device is restarting + // Don't attempt to access it unless coming from the restart owner + if ((priv->recovery_in_progress) && (priv->recovery_owner != current)) + return -ENETDOWN; + + if(priv->if_ops.is_deepsleep(priv)) { + priv->if_ops.wakeup_card(priv); + } + + if (!mwl_fwcmd_chk_adapter(priv)) { + wiphy_err(priv->hw->wiphy, "adapter does not exist\n"); + return -EIO; + } + + if (priv->cmd_timeout) { + if (lrd_debug) { + wiphy_debug(priv->hw->wiphy, + "Skip CMD(%04xh, %s) - due to prev cmd_timeout\n", + cmd, mwl_fwcmd_get_cmd_string(cmd)); + } + return -EIO; + } + + pcmd = (struct hostcmd_header *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + priv->cmd_seq_num++; + pcmd->seq_num = priv->cmd_seq_num; + + if (lrd_debug) { + wiphy_debug(priv->hw->wiphy, "DNLD_CMD(# %02x)=> (%04xh, %s)\n", + pcmd->seq_num, cmd, mwl_fwcmd_get_cmd_string(cmd)); + mwl_hex_dump((char*)pcmd, le16_to_cpu(pcmd->len)); + } + + rc = mwl_fwcmd_send_cmd(priv); + if (rc) { + if (priv->cmd_timeout) { + wiphy_err(priv->hw->wiphy, "DNLD_CMD (# %02x)=> (%04xh, %s) timeout\n", + pcmd->seq_num, cmd, mwl_fwcmd_get_cmd_string(cmd)); + + mwl_hex_dump((char*)pcmd, le16_to_cpu(pcmd->len)); + // Fatal error, initiate firmware recovery + lrd_radio_recovery(priv); + } + + return rc; + } + + if (priv->if_ops.cmd_resp_wait_completed) { + rc = priv->if_ops.cmd_resp_wait_completed(priv, + HOSTCMD_RESP_BIT | cmd); + } + + if (rc) { + if (rc == -ENETDOWN) + return rc; + + wiphy_err(priv->hw->wiphy, "CMD_RESP (# %02x)=> (%04xh, %s) timeout\n", + pcmd->seq_num, cmd, mwl_fwcmd_get_cmd_string(cmd)); + + mwl_hex_dump((char*)pcmd, le16_to_cpu(pcmd->len)); + + if (cmd != HOSTCMD_CMD_GET_HW_SPEC) { + priv->cmd_timeout = true; + + // Fatal error, initiate firmware recovery + lrd_radio_recovery(priv); + } + return -EIO; + } + presp = (struct hostcmd_header *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + if (lrd_debug) { + wiphy_debug(priv->hw->wiphy, " CMD_RESP(# %02x)=> (%04xh)\n", + presp->seq_num, le16_to_cpu(presp->cmd)); + mwl_hex_dump((char*)pcmd, le16_to_cpu(pcmd->len)); + } + + if(cmd != HOSTCMD_CMD_DEEPSLEEP) { + mwl_restart_ds_timer(priv, false); + } + + return 0; +} + +int mwl_fwcmd_set_slot_time(struct ieee80211_hw *hw, bool short_slot) +{ + struct hostcmd_cmd_802_11_slot_time *pcmd; + struct mwl_priv *priv = hw->priv; + +// wiphy_err(priv->hw->wiphy, "%s(): short_slot_time=%d\n", __FUNCTION__, short_slot); + + pcmd = (struct hostcmd_cmd_802_11_slot_time *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_SLOT_TIME); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->short_slot = cpu_to_le16(short_slot?1:0); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_SLOT_TIME)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +/*Note: When calling this function, it is expected that the fwcmd_mutex is already held */ +int mwl_fwcmd_confirm_ps(struct ieee80211_hw *hw) +{ + struct hostcmd_cmd_confirm_ps *pcmd; + struct mwl_priv *priv = hw->priv; + + if(priv->if_ops.is_deepsleep(priv)) + return 0; + + pcmd = (struct hostcmd_cmd_confirm_ps *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_CONFIRM_PS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_CONFIRM_PS)) { + wiphy_err(hw->wiphy, " %s failed execution\n", + mwl_fwcmd_get_cmd_string(HOSTCMD_CMD_CONFIRM_PS)); + return -EIO; + } + + if (pcmd->status == 0) { + priv->if_ops.enter_deepsleep(priv); + return 0; + } + + return -1; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_confirm_ps); + +int mwl_fwcmd_enter_deepsleep(struct ieee80211_hw *hw) +{ + struct hostcmd_cmd_deepsleep *pcmd; + struct mwl_priv *priv = hw->priv; + + if (priv->mfg_mode) { + return 0; + } + + pcmd = (struct hostcmd_cmd_deepsleep *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + if(priv->if_ops.is_deepsleep(priv)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(priv->hw->wiphy,"radio already asleep\n"); + return 0; + } + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DEEPSLEEP); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enableFlag = 1; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DEEPSLEEP)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, " %s failed execution\n", + mwl_fwcmd_get_cmd_string(HOSTCMD_CMD_DEEPSLEEP)); + return -EIO; + } + + priv->if_ops.enter_deepsleep(priv); + + mutex_unlock(&priv->fwcmd_mutex); + + wiphy_dbg(priv->hw->wiphy, "Entered deepsleep\n"); + return 0; +} + +int mwl_fwcmd_exit_deepsleep(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + + mutex_lock(&priv->fwcmd_mutex); + + if (priv->if_ops.is_deepsleep(priv)) { + rc = priv->if_ops.wakeup_card(priv); + } + + mutex_unlock(&priv->fwcmd_mutex); + + return rc; +} + +int mwl_fwcmd_config_EDMACCtrl(struct ieee80211_hw *hw, int EDMAC_Ctrl) +{ + struct hostcmd_cmd_edmac_ctrl *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_edmac_ctrl *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_EDMAC_CTRL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->ed_ctrl_2g = ((EDMAC_Ctrl & EDMAC_2G_ENABLE_MASK) + >> EDMAC_2G_ENABLE_SHIFT); + pcmd->ed_ctrl_5g = ((EDMAC_Ctrl & EDMAC_5G_ENABLE_MASK) + >> EDMAC_5G_ENABLE_SHIFT); + pcmd->ed_offset_2g = (s8)((EDMAC_Ctrl & EDMAC_2G_THRESHOLD_OFFSET_MASK) + >> EDMAC_2G_THRESHOLD_OFFSET_SHIFT); + pcmd->ed_offset_5g = (s8)((EDMAC_Ctrl & EDMAC_5G_THRESHOLD_OFFSET_MASK) + >> EDMAC_5G_THRESHOLD_OFFSET_SHIFT); + pcmd->ed_bitmap_txq_lock = cpu_to_le16((EDMAC_Ctrl & EDMAC_QLOCK_BITMAP_MASK) + >> EDMAC_QLOCK_BITMAP_SHIFT); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_EDMAC_CTRL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_getEDMAC(struct mwl_priv *priv, int * pEdmac) +{ + struct hostcmd_cmd_edmac_ctrl *pcmd; + int rc = 0; + + if (!pEdmac) { + rc = -EINVAL; + goto error_return; + } + + pcmd = (struct hostcmd_cmd_edmac_ctrl *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_EDMAC_CTRL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_GET); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_EDMAC_CTRL)) { + mutex_unlock(&priv->fwcmd_mutex); + rc = -EIO; + goto error_return; + } + + if (pcmd->cmd_hdr.result == HOSTCMD_RESULT_NOT_SUPPORT) { + mutex_unlock(&priv->fwcmd_mutex); + rc = -ENOTSUPP; + goto error_return; + } + + *pEdmac = pcmd->ed_ctrl_2g << EDMAC_2G_ENABLE_SHIFT; + *pEdmac |= pcmd->ed_ctrl_5g << EDMAC_5G_ENABLE_SHIFT; + + mutex_unlock(&priv->fwcmd_mutex); + +error_return: + return rc; +} + +static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv, + bool enable, bool force) +{ + struct hostcmd_cmd_802_11_radio_control *pcmd; + + if (enable == priv->radio_on && !force) + return 0; + + pcmd = (struct hostcmd_cmd_802_11_radio_control *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RADIO_CONTROL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->control = cpu_to_le16(priv->radio_short_preamble ? + WL_AUTO_PREAMBLE : WL_LONG_PREAMBLE); + pcmd->radio_on = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RADIO_CONTROL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + priv->radio_on = enable; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, + u8 action, u16 ch, u16 band, u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + int i; + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(action); + pcmd->ch = cpu_to_le16(ch); + pcmd->bw = cpu_to_le16(width); + pcmd->band = cpu_to_le16(band); + pcmd->sub_ch = cpu_to_le16(sub_ch); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + for (i = 0; i < SYSADPT_TX_GRP_PWR_LEVEL_TOTAL; i++) + powlist[i] = le16_to_cpu(pcmd->power_level_list[i]); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[], + u8 action, u16 ch, u16 band, + u16 width, u16 sub_ch) +{ + struct hostcmd_cmd_802_11_tx_power *pcmd; + int i; + + pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(action); + pcmd->ch = cpu_to_le16(ch); + pcmd->bw = cpu_to_le16(width); + pcmd->band = cpu_to_le16(band); + pcmd->sub_ch = cpu_to_le16(sub_ch); + + for (i = 0; i < SYSADPT_TX_GRP_PWR_LEVEL_TOTAL; i++) + pcmd->power_level_list[i] = cpu_to_le16(txpow[i]); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_TX_POWER)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static u8 mwl_fwcmd_get_80m_pri_chnl(u8 channel) +{ + u8 act_primary = ACT_PRIMARY_CHAN_0; + + switch (channel) { + case 36: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 40: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 44: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 48: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 52: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 56: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 60: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 64: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 100: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 104: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 108: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 112: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 116: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 120: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 124: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 128: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 132: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 136: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 140: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 144: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 149: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 153: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 157: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 161: + act_primary = ACT_PRIMARY_CHAN_3; + break; + } + + return act_primary; +} + +static u8 mwl_fwcmd_get_160m_pri_chnl(u8 channel) +{ + u8 act_primary = ACT_PRIMARY_CHAN_0; + + switch (channel) { + case 36: + act_primary = ACT_PRIMARY_CHAN_0; + break; + case 40: + act_primary = ACT_PRIMARY_CHAN_1; + break; + case 44: + act_primary = ACT_PRIMARY_CHAN_2; + break; + case 48: + act_primary = ACT_PRIMARY_CHAN_3; + break; + case 52: + act_primary = ACT_PRIMARY_CHAN_4; + break; + case 56: + act_primary = ACT_PRIMARY_CHAN_5; + break; + case 60: + act_primary = ACT_PRIMARY_CHAN_6; + break; + case 64: + act_primary = ACT_PRIMARY_CHAN_7; + 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_4; + break; + case 120: + act_primary = ACT_PRIMARY_CHAN_5; + break; + case 124: + act_primary = ACT_PRIMARY_CHAN_6; + break; + case 128: + act_primary = ACT_PRIMARY_CHAN_7; + 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; + case 165: + act_primary = ACT_PRIMARY_CHAN_4; + break; + case 169: + act_primary = ACT_PRIMARY_CHAN_5; + break; + case 173: + act_primary = ACT_PRIMARY_CHAN_6; + break; + case 177: + act_primary = ACT_PRIMARY_CHAN_7; + break; + } + + return act_primary; +} + +static int laird_is_host_cipher(const u8 *buf) +{ + u32 cipher; + cipher = get_unaligned_be32(buf); + switch (cipher) { + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_GCMP_256: + case WLAN_CIPHER_SUITE_CCMP_256: + return 1; + default: + break; + } + return 0; +} + +// returns true if the beacon rsnie is present and any ciphers use host crypto +static int laird_ap_select_host_crypto(struct mwl_vif *mwl_vif) +{ + const u8 *rsn_ie; + size_t rsn_ie_len; + int cnt; + + rsn_ie = mwl_vif->beacon_info.ie_rsn48_ptr; + rsn_ie_len = mwl_vif->beacon_info.ie_rsn48_len; + if (!rsn_ie) + return 0; + + /* skip tag, len, version */ + if (rsn_ie_len < 4) + return 0; + rsn_ie += 4; + rsn_ie_len -= 4; + + /* group cipher suite */ + if (rsn_ie_len < 4) + return 0; + if (laird_is_host_cipher(rsn_ie)) + return 1; + rsn_ie += 4; + rsn_ie_len -= 4; + + /* pairwise cipher suite(s) */ + if (rsn_ie_len < 2) + return 0; + cnt = get_unaligned_le16(rsn_ie); + rsn_ie += 2; + rsn_ie_len -= 2; + if (rsn_ie_len < (cnt * 4)) + return 0; + while (cnt--) { + if (laird_is_host_cipher(rsn_ie)) + return 1; + rsn_ie += 4; + rsn_ie_len -= 4; + } + return 0; +} + +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 = false; + bool elem_11k_failed = false; + + mgmt = (struct ieee80211_mgmt *)beacon; + + memcpy(vif->bssid, mgmt->bssid, ETH_ALEN); + + 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->ie_11k_ptr = &beacon_info->ie_list_11k[0]; + + beacon_info->cap_info = le16_to_cpu(mgmt->u.beacon.capab_info); + beacon_info->power_constraint = 0; + + pos = (u8 *)mgmt->u.beacon.variable; + left = len - baselen; + + 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_SSID: + beacon_info->ie_ssid_len = (elen + 2); + beacon_info->ie_ssid_ptr = (pos - 2); + break; + case WLAN_EID_IBSS_PARAMS: + beacon_info->ie_ibss_parms_len = (elen + 2); + beacon_info->ie_ibss_parms_ptr = (pos - 2); + break; + case WLAN_EID_COUNTRY: + beacon_info->ie_country_len = (elen + 2); + beacon_info->ie_country_ptr = (pos - 2); + break; + case WLAN_EID_SUPP_RATES: + case WLAN_EID_EXT_SUPP_RATES: + { + int idx, bi, oi; + u8 rate; + + for (bi = 0; bi < SYSADPT_MAX_DATA_RATES_G; + bi++) { + if (beacon_info->b_rate_set[bi] == 0) + break; + } + + for (oi = 0; oi < SYSADPT_MAX_DATA_RATES_G; + oi++) { + if (beacon_info->op_rate_set[oi] == 0) + break; + } + + for (idx = 0; idx < elen; idx++) { + rate = pos[idx]; + if ((rate & 0x80) != 0) { + if (bi < SYSADPT_MAX_DATA_RATES_G) + beacon_info->b_rate_set[bi++] + = rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + if (oi < SYSADPT_MAX_DATA_RATES_G) + beacon_info->op_rate_set[oi++] = + rate & 0x7f; + else { + elem_parse_failed = true; + break; + } + } + } + break; + case WLAN_EID_PWR_CONSTRAINT: + if (elen == 1) + beacon_info->power_constraint = *pos; + break; + case WLAN_EID_RSN: + beacon_info->ie_rsn48_len = (elen + 2); + beacon_info->ie_rsn48_ptr = (pos - 2); + break; + case WLAN_EID_HT_CAPABILITY: + case WLAN_EID_HT_OPERATION: + case WLAN_EID_OVERLAP_BSS_SCAN_PARAM: + case WLAN_EID_EXT_CAPABILITY: + beacon_info->ie_ht_len += (elen + 2); + if (beacon_info->ie_ht_len > + sizeof(beacon_info->ie_list_ht)) { + elem_parse_failed = true; + } else { + *beacon_info->ie_ht_ptr++ = id; + *beacon_info->ie_ht_ptr++ = elen; + memcpy(beacon_info->ie_ht_ptr, pos, elen); + beacon_info->ie_ht_ptr += elen; + } + break; + case WLAN_EID_VHT_CAPABILITY: + case WLAN_EID_VHT_OPERATION: + case WLAN_EID_OPMODE_NOTIF: + beacon_info->ie_vht_len += (elen + 2); + if (beacon_info->ie_vht_len > + sizeof(beacon_info->ie_list_vht)) { + elem_parse_failed = true; + } else { + *beacon_info->ie_vht_ptr++ = id; + *beacon_info->ie_vht_ptr++ = elen; + memcpy(beacon_info->ie_vht_ptr, pos, elen); + beacon_info->ie_vht_ptr += elen; + } + break; + + case WLAN_EID_AP_CHAN_REPORT: + case WLAN_EID_BSS_AVG_ACCESS_DELAY: + case WLAN_EID_ANTENNA_INFO: + case WLAN_EID_MEASUREMENT_PILOT_TX_INFO: + case WLAN_EID_BSS_AVAILABLE_CAPACITY: + case WLAN_EID_BSS_AC_ACCESS_DELAY: + case WLAN_EID_RRM_ENABLED_CAPABILITIES: + case WLAN_EID_MULTIPLE_BSSID: + beacon_info->ie_11k_len += (elen + 2); + if (beacon_info->ie_11k_len > sizeof(beacon_info->ie_list_11k)) { + elem_11k_failed = true; + } + else { + *beacon_info->ie_11k_ptr++ = id; + *beacon_info->ie_11k_ptr++ = elen; + memcpy(beacon_info->ie_11k_ptr, pos, elen); + beacon_info->ie_11k_ptr += elen; + } + break; + case WLAN_EID_VENDOR_SPECIFIC: + if ((pos[0] == 0x00) && (pos[1] == 0x50) && + (pos[2] == 0xf2)) { + if (pos[3] == 0x01) { + beacon_info->ie_rsn_len = (elen + 2); + beacon_info->ie_rsn_ptr = (pos - 2); + } + + if (pos[3] == 0x02) { + beacon_info->ie_wmm_len = (elen + 2); + beacon_info->ie_wmm_ptr = (pos - 2); + } + + if (pos[3] == 0x04) { + beacon_info->ie_wsc_len = (elen + 2); + beacon_info->ie_wsc_ptr = (pos - 2); + } + } + + if ((pos[0] == 0x50) && (pos[1] == 0x6F) && + (pos[2] == 0x9A)) { + if (pos[3] == 0x09) { + beacon_info->ie_wfd_len = (elen + 2); + beacon_info->ie_wfd_ptr = (pos - 2); + } + } + break; + default: + break; + } + + left -= elen; + pos += elen; + } + + if (!elem_parse_failed) { + beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; + beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; + beacon_info->valid = true; + } + + if (!elem_11k_failed) { + beacon_info->ie_11k_ptr = &beacon_info->ie_list_11k[0]; + } + else { + wiphy_err(priv->hw->wiphy, "11k IEs will not fit in remaining buffer, dropping."); + beacon_info->ie_11k_len = 0; + } + + /* force host crypto if any rsnie ciphers require host crypto */ + vif->force_host_crypto = laird_ap_select_host_crypto(vif); +} + +static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif) +{ + struct hostcmd_cmd_set_ies *pcmd; + struct beacon_info *beacon = &mwl_vif->beacon_info; + + if (beacon->ie_ht_len > sizeof(pcmd->ie_list_ht)) + goto einval; + + if (beacon->ie_vht_len > sizeof(pcmd->ie_list_vht)) + goto einval; + + if (beacon->ie_11k_len > sizeof(pcmd->ie_list_11k)) + goto einval; + + pcmd = (struct hostcmd_cmd_set_ies *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_IES); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); + + memcpy(pcmd->ie_list_ht, beacon->ie_ht_ptr, beacon->ie_ht_len); + pcmd->ie_list_len_ht = cpu_to_le16(beacon->ie_ht_len); + + memcpy(pcmd->ie_list_vht, beacon->ie_vht_ptr, beacon->ie_vht_len); + pcmd->ie_list_len_vht = cpu_to_le16(beacon->ie_vht_len); + + memcpy(pcmd->ie_list_11k, beacon->ie_11k_ptr, beacon->ie_11k_len); + pcmd->ie_list_len_11k = cpu_to_le16(beacon->ie_11k_len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_IES)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; + +einval: + + wiphy_err(priv->hw->wiphy, "length of IE is too long\n"); + + return -EINVAL; +} + +static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv, + struct ieee80211_vif *vif) +{ + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_ap_beacon *pcmd; + struct ds_params *phy_ds_param_set; + + + mwl_vif = mwl_dev_get_vif(vif); + /* 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)) { + wiphy_err(priv->hw->wiphy, + "WMM IE is longer than radio supports"); + goto ielenerr; + } + + if (mwl_vif->beacon_info.ie_rsn48_len > sizeof(pcmd->start_cmd.rsn_data)) { + wiphy_err(priv->hw->wiphy, + "RSN IE is longer than radio supports"); + goto ielenerr; + } + + if (mwl_vif->beacon_info.ie_ibss_parms_len > + sizeof(pcmd->start_cmd.ss_param_set.ibss_param_set)) { + wiphy_err(priv->hw->wiphy, + "IBSS Parameter Set IE is longer than radio supports"); + goto ielenerr; + } + + pcmd = (struct hostcmd_cmd_ap_beacon *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_AP_BEACON); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if(vif->type == NL80211_IFTYPE_ADHOC) { + ether_addr_copy(pcmd->start_cmd.sta_mac_addr, mwl_vif->sta_mac); + }else { + ether_addr_copy(pcmd->start_cmd.sta_mac_addr, mwl_vif->bssid); + } + + ether_addr_copy(pcmd->start_cmd.Bssid, mwl_vif->bssid); + memcpy(pcmd->start_cmd.ssid, vif->cfg.ssid, vif->cfg.ssid_len); + pcmd->start_cmd.bss_type = 1; + pcmd->start_cmd.bcn_period = cpu_to_le16(vif->bss_conf.beacon_int); + pcmd->start_cmd.dtim_period = vif->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 = vif->bss_conf.chandef.chan->hw_value; + + pcmd->start_cmd.probe_delay = cpu_to_le16(10); + pcmd->start_cmd.cap_info = cpu_to_le16(mwl_vif->beacon_info.cap_info); + + memcpy(&pcmd->start_cmd.ss_param_set.ibss_param_set, mwl_vif->beacon_info.ie_ibss_parms_ptr, + mwl_vif->beacon_info.ie_ibss_parms_len); + + memcpy(&pcmd->start_cmd.wmm_param, mwl_vif->beacon_info.ie_wmm_ptr, + mwl_vif->beacon_info.ie_wmm_len); + + //rsn IE + if (mwl_vif->beacon_info.ie_rsn48_len) { + memcpy(pcmd->start_cmd.rsn_data, + mwl_vif->beacon_info.ie_rsn48_ptr, + mwl_vif->beacon_info.ie_rsn48_len); + + pcmd->start_cmd.offset_StaRsnIE48 = pcmd->start_cmd.rsn_data - (u8*)&pcmd->start_cmd; + } + + //wpa IE + if (mwl_vif->beacon_info.ie_rsn_len) { + + if ( mwl_vif->beacon_info.ie_rsn48_len + mwl_vif->beacon_info.ie_rsn_len <= sizeof(pcmd->start_cmd.rsn_data)){ + + pcmd->start_cmd.offset_StaRsnIE = pcmd->start_cmd.rsn_data - (u8*)&pcmd->start_cmd; + + if (pcmd->start_cmd.offset_StaRsnIE48) { + pcmd->start_cmd.offset_StaRsnIE += mwl_vif->beacon_info.ie_rsn48_len; + } + + memcpy(((u8*)&pcmd->start_cmd) + pcmd->start_cmd.offset_StaRsnIE, + mwl_vif->beacon_info.ie_rsn_ptr, + mwl_vif->beacon_info.ie_rsn_len); + } + else { + wiphy_err(priv->hw->wiphy, "WPA IE will not fit in remaining buffer, dropping."); + pcmd->start_cmd.offset_StaRsnIE = 0; + } + } + + //Convert WPA/RSN IE offsets to little endian + pcmd->start_cmd.offset_StaRsnIE = cpu_to_le16(pcmd->start_cmd.offset_StaRsnIE); + pcmd->start_cmd.offset_StaRsnIE48 = cpu_to_le16(pcmd->start_cmd.offset_StaRsnIE48); + + memcpy(pcmd->start_cmd.b_rate_set, mwl_vif->beacon_info.b_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + memcpy(pcmd->start_cmd.op_rate_set, mwl_vif->beacon_info.op_rate_set, + SYSADPT_MAX_DATA_RATES_G); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_AP_BEACON)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; + +ielenerr: + + return -EINVAL; +} + +static int mwl_fwcmd_set_spectrum_mgmt(struct mwl_priv *priv, bool enable) +{ + struct hostcmd_cmd_set_spectrum_mgmt *pcmd; + + pcmd = (struct hostcmd_cmd_set_spectrum_mgmt *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SPECTRUM_MGMT); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->spectrum_mgmt = cpu_to_le32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_SPECTRUM_MGMT)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static int mwl_fwcmd_set_power_constraint(struct mwl_priv *priv, + u32 power_constraint) +{ + struct hostcmd_cmd_set_power_constraint *pcmd; + + pcmd = (struct hostcmd_cmd_set_power_constraint *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_POWER_CONSTRAINT); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->power_constraint = cpu_to_le32(power_constraint); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_POWER_CONSTRAINT)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static int mwl_fwcmd_set_country_code(struct mwl_priv *priv, + struct mwl_vif *mwl_vif, + struct ieee80211_bss_conf *bss_conf) +{ + struct hostcmd_cmd_set_country_code *pcmd; + struct beacon_info *b_inf = &mwl_vif->beacon_info; + u8 chnl_len; + bool a_band; + bool enable = false; + + if (b_inf->ie_country_ptr) { + if (bss_conf->chandef.chan->band == NL80211_BAND_2GHZ) + a_band = false; + else if (bss_conf->chandef.chan->band == NL80211_BAND_5GHZ) + a_band = true; + else + return -EINVAL; + + chnl_len = b_inf->ie_country_len - 5; + if (a_band) { + if (chnl_len > sizeof(pcmd->domain_info.domain_entry_a)) + return -EINVAL; + } else { + if (chnl_len > sizeof(pcmd->domain_info.domain_entry_g)) + return -EINVAL; + } + + enable = true; + } + + pcmd = (struct hostcmd_cmd_set_country_code *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_COUNTRY_CODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le32(enable); + if (enable) { + memcpy(pcmd->domain_info.country_string, + b_inf->ie_country_ptr + 2, 3); + if (a_band) { + pcmd->domain_info.g_chnl_len = 0; + pcmd->domain_info.a_chnl_len = chnl_len; + memcpy(pcmd->domain_info.domain_entry_a, + b_inf->ie_country_ptr + 5, chnl_len); + } else { + pcmd->domain_info.a_chnl_len = 0; + pcmd->domain_info.g_chnl_len = chnl_len; + memcpy(pcmd->domain_info.domain_entry_g, + b_inf->ie_country_ptr + 5, chnl_len); + } + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_COUNTRY_CODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd, + struct ieee80211_vif *vif, + u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_vif *mwl_vif; + + mwl_vif = mwl_dev_get_vif(vif); + + cmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + cmd->cmd_hdr.len = cpu_to_le16(sizeof(*cmd)); + cmd->key_param.length = cpu_to_le16(sizeof(*cmd) - + offsetof(struct hostcmd_cmd_set_key, key_param)); + cmd->key_param.key_index = cpu_to_le32(key->keyidx); + cmd->key_param.key_len = cpu_to_le16(key->keylen); + ether_addr_copy(cmd->key_param.mac_addr, addr); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_WEP); + if ( (key->flags & IEEE80211_KEY_FLAG_PAIRWISE)|| + (key->keyidx == mwl_vif->tx_key_idx) ) { + cmd->key_param.key_info = cpu_to_le32(ENCR_KEY_FLAG_WEP_TXKEY); + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + mwl_vif->tx_key_idx = key->keyidx; + } + } + break; + case WLAN_CIPHER_SUITE_TKIP: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_TKIP); + cmd->key_param.key_info = + (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : + cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); + cmd->key_param.key_info |= + cpu_to_le32(ENCR_KEY_FLAG_MICKEY_VALID | + ENCR_KEY_FLAG_TSC_VALID); + break; + case WLAN_CIPHER_SUITE_CCMP: + cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_AES); + cmd->key_param.key_info = + (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : + cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + return 1; + default: + return -ENOTSUPP; + } + + return 0; +} + +void mwl_fwcmd_reset(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + if (priv->if_ops.card_reset) + priv->if_ops.card_reset(priv); +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_reset); + +void mwl_fwcmd_int_enable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + if (priv->if_ops.enable_int) + priv->if_ops.enable_int(priv); +} + +void mwl_fwcmd_int_disable(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + if (priv->if_ops.disable_int) + priv->if_ops.disable_int(priv); +} + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_hw_spec *pcmd; + int retry; + int i; + + pcmd = (struct hostcmd_cmd_get_hw_spec *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + for (retry = 0; retry < MAX_WAIT_GET_HW_SPECS_ITERATONS; retry++) + { + memset(pcmd, 0x00, sizeof(*pcmd)); + eth_broadcast_addr(pcmd->permanent_addr); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_HW_SPEC); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->fw_awake_cookie = cpu_to_le32(priv->pphys_cmd_buf + 2048); + + if (!mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_HW_SPEC)) + break; + + msleep(1000); + } + + if (retry == MAX_WAIT_GET_HW_SPECS_ITERATONS) { + wiphy_err(hw->wiphy, "can't get hw specs\n"); + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (!priv->mfg_mode) { + //MFG firmware does not return consistent permanent mac, so only + //use it if running protocol firmware + ether_addr_copy(&priv->hw_data.mac_addr[0], pcmd->permanent_addr); + } + + priv->desc_data[0].wcb_base = + le32_to_cpu(pcmd->wcb_base0) & 0x0000ffff; + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + priv->desc_data[i].wcb_base = + le32_to_cpu(pcmd->wcb_base[i - 1]) & 0x0000ffff; + priv->desc_data[0].rx_desc_read = + le32_to_cpu(pcmd->rxpd_rd_ptr) & 0x0000ffff; + priv->desc_data[0].rx_desc_write = + le32_to_cpu(pcmd->rxpd_wr_ptr) & 0x0000ffff; + priv->hw_data.region_code = le16_to_cpu(pcmd->region_code) & 0x00ff; + priv->hw_data.fw_release_num = le32_to_cpu(pcmd->fw_release_num); + priv->hw_data.max_num_tx_desc = le16_to_cpu(pcmd->num_wcb); + priv->hw_data.max_num_mc_addr = le16_to_cpu(pcmd->num_mcast_addr); + priv->hw_data.num_antennas = le16_to_cpu(pcmd->num_antenna); + priv->hw_data.hw_version = pcmd->version; + priv->hw_data.host_interface = pcmd->host_if; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_hw_spec *pcmd; + int i; + + pcmd = (struct hostcmd_cmd_set_hw_spec *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_HW_SPEC); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if ((priv->host_if == MWL_IF_PCIE) && + IS_PFU_ENABLED(priv->chip_type)) { + pcmd->wcb_base[0] = cpu_to_le32(priv->txbd_ring_pbase); +/* host_spec.txbd_addr_hi = +* (unsigned int)(((unsigned long long)pmadapter->txbd_ring_pbase)>>32); +*/ + pcmd->tx_wcb_num_per_queue = cpu_to_le32(MLAN_MAX_TXRX_BD); + pcmd->num_tx_queues = cpu_to_le32(SYSADPT_PFU_NUM_OF_DESC_DATA); + } else { + pcmd->wcb_base[0] = + cpu_to_le32(priv->desc_data[0].pphys_tx_ring); + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + pcmd->wcb_base[i] = + cpu_to_le32(priv->desc_data[i].pphys_tx_ring); + pcmd->tx_wcb_num_per_queue = + cpu_to_le32(SYSADPT_MAX_NUM_TX_DESC); + pcmd->num_tx_queues = cpu_to_le32(SYSADPT_NUM_OF_DESC_DATA); + + } + + pcmd->total_rx_wcb = cpu_to_le32(SYSADPT_MAX_NUM_RX_DESC); + pcmd->rxpd_wr_ptr = cpu_to_le32(priv->desc_data[0].pphys_rx_ring); + pcmd->features |= cpu_to_le32(HW_SET_PARMS_FEATURES_HOST_PROBE_RESP); + + if (priv->host_crypto) + pcmd->features |= cpu_to_le32(HW_SET_PARMS_FEATURES_HOST_ENCRDECRMGT); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_HW_SPEC)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +static u16 wlan_parse_cal_cfg(const u8 *src, size_t len, u8 *dst) +{ + const u8 *ptr; + u8 *dptr; + + ptr = src; + dptr = dst; + + while (ptr - src < len) { + if (*ptr && (isspace(*ptr) || iscntrl(*ptr))) { + ptr++; + continue; + } + + if (isxdigit(*ptr)) { + *dptr++ = simple_strtol(ptr, NULL, 16); + ptr += 2; + } else { + ptr++; + } + } + + return (dptr - dst); +} + +int mwl_fwcmd_set_cfg_data(struct ieee80211_hw *hw, u16 type) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_cfg *pcmd; + + if(!priv->cal_data) + return 0; + pcmd = (struct hostcmd_cmd_set_cfg *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + + pcmd->data_len = cpu_to_le16(wlan_parse_cal_cfg(priv->cal_data->data, + priv->cal_data->size, pcmd->data)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CFG); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + + le16_to_cpu(pcmd->data_len) - sizeof(pcmd->data)); + pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); + pcmd->type = cpu_to_le16(type); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_CFG)) { + mutex_unlock(&priv->fwcmd_mutex); + release_firmware(priv->cal_data); + priv->cal_data = NULL; + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + release_firmware(priv->cal_data); + priv->cal_data = NULL; + + return 0; + +} + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_802_11_get_stat *pcmd; + + pcmd = (struct hostcmd_cmd_802_11_get_stat *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_GET_STAT); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_GET_STAT)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + stats->dot11ACKFailureCount = + le32_to_cpu(pcmd->ack_failures); + stats->dot11RTSFailureCount = + le32_to_cpu(pcmd->rts_failures); + stats->dot11FCSErrorCount = + le32_to_cpu(pcmd->rx_fcs_errors); + stats->dot11RTSSuccessCount = + le32_to_cpu(pcmd->rts_successes); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_reg_mac(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_mac_reg_access *pcmd; + + pcmd = (struct hostcmd_cmd_mac_reg_access *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MAC_REG_ACCESS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->offset = cpu_to_le16(reg); + pcmd->action = cpu_to_le16(flag); + pcmd->value = cpu_to_le32(*val); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_MAC_REG_ACCESS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *val = le32_to_cpu(pcmd->value); + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_reg_mac); + +int mwl_fwcmd_reg_bb(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_bbp_reg_access *pcmd; + + pcmd = (struct hostcmd_cmd_bbp_reg_access *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BBP_REG_ACCESS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->offset = cpu_to_le16(reg); + pcmd->action = cpu_to_le16(flag); + pcmd->value = (u8)cpu_to_le32(*val); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BBP_REG_ACCESS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *val = (u32)pcmd->value; + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_reg_bb); + +int mwl_fwcmd_reg_rf(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_rf_reg_access *pcmd; + + pcmd = (struct hostcmd_cmd_rf_reg_access *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_RF_REG_ACCESS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->offset = cpu_to_le16(reg); + pcmd->action = cpu_to_le16(flag); + pcmd->value = (u8)cpu_to_le32(*val); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_RF_REG_ACCESS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *val = (u32)pcmd->value; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_reg_rf); + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw) +{ + return mwl_fwcmd_802_11_radio_control(hw->priv, true, false); +} + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw) +{ + return mwl_fwcmd_802_11_radio_control(hw->priv, false, false); +} + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble) +{ + struct mwl_priv *priv = hw->priv; + int rc; + + priv->radio_short_preamble = short_preamble; + rc = mwl_fwcmd_802_11_radio_control(priv, true, true); + + return rc; +} + +int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len, + u32 *val, u16 set) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_mem_addr_access *pcmd; + int i; + + pcmd = (struct hostcmd_cmd_mem_addr_access *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MEM_ADDR_ACCESS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->address = cpu_to_le32(addr); + pcmd->length = cpu_to_le16(len); + pcmd->value[0] = cpu_to_le32(*val); + pcmd->reserved = cpu_to_le16(set); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_MEM_ADDR_ACCESS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + for (i = 0; i < len; i++) + val[i] = le32_to_cpu(pcmd->value[i]); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_get_addr_value); + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u16 level) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv = hw->priv; + u16 band = 0, width = 0, sub_ch = 0; + u16 txpow[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; + int i = 0; + int rc = 0; + + if (channel->band == NL80211_BAND_2GHZ) + band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == NL80211_BAND_5GHZ) + band = FREQ_BAND_5GHZ; + + switch (conf->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + width = CH_20_MHZ_WIDTH; + sub_ch = NO_EXT_CHANNEL; + break; + case NL80211_CHAN_WIDTH_40: + width = CH_40_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + case NL80211_CHAN_WIDTH_80: + width = CH_80_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + sub_ch = EXT_CH_ABOVE_CTRL_CH; + else + sub_ch = EXT_CH_BELOW_CTRL_CH; + break; + default: + return -EINVAL; + } + + for (i = 0; i < SYSADPT_TX_GRP_PWR_LEVEL_TOTAL; i++) { + txpow[i] = level; + } + + rc = mwl_fwcmd_set_tx_powers(priv, txpow, HOSTCMD_ACT_SET_TARGET_TX_PWR, + channel->hw_value, band, width, sub_ch); + + return rc; +} + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int ant_tx_bmp, + int ant_rx_bmp) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_802_11_rf_antenna_v2 *pcmd; + int retval = 0; + + pcmd = (struct hostcmd_cmd_802_11_rf_antenna_v2 *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = + cpu_to_le16(HOSTCMD_CMD_802_11_RF_ANTENNA_V2); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le16(0); //Not need - just making things obvious + + pcmd->ant_tx_bmp = cpu_to_le16(ant_tx_bmp); + pcmd->ant_rx_bmp = cpu_to_le16(ant_rx_bmp); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RF_ANTENNA_V2)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + wiphy_err(hw->wiphy, "Command %s Rejected by FW\n", + mwl_fwcmd_get_cmd_string(HOSTCMD_CMD_802_11_RF_ANTENNA_V2)); + retval = -EINVAL; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return retval; +} + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct mwl_vif *mwl_vif, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_broadcast_ssid_enable *pcmd; + + pcmd = (struct hostcmd_cmd_broadcast_ssid_enable *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BROADCAST_SSID_ENABLE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if ((vif->cfg.ssid[0] != '\0') && + (vif->cfg.ssid_len != 0) && + (!vif->bss_conf.hidden_ssid)) + pcmd->enable = cpu_to_le32(true); + else + pcmd->enable = cpu_to_le32(false); + + if (vif->bss_conf.hidden_ssid) { + if((mwl_vif->beacon_info.ie_ssid_len) - 2) + pcmd->hidden_ssid_info = cpu_to_le32(NL80211_HIDDEN_SSID_ZERO_CONTENTS); + else + pcmd->hidden_ssid_info = cpu_to_le32(NL80211_HIDDEN_SSID_ZERO_LEN); + } + else + pcmd->hidden_ssid_info = cpu_to_le32(NL80211_HIDDEN_SSID_NOT_IN_USE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BROADCAST_SSID_ENABLE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_powersave_EnblDsbl(struct ieee80211_hw *hw, + struct ieee80211_conf *conf) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_802_11_ps_mode *pcmd; + + pcmd = (struct hostcmd_cmd_802_11_ps_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_PS_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->powermode = cpu_to_le16(conf->flags & IEEE80211_CONF_PS); + priv->ps_mode = le16_to_cpu(pcmd->powermode); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_PS_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +#ifdef CONFIG_PM +int mwl_fwcmd_hostsleep_control(struct ieee80211_hw *hw, bool hs_enable, bool ds_enable, int wakeupCond) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_hostsleep_ctrl *pcmd; + + pcmd = (struct hostcmd_cmd_hostsleep_ctrl *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_HOSTSLEEP_CTRL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->HS_enable = hs_enable; + pcmd->DS_enable = ds_enable; + pcmd->gap = cpu_to_le16(WOWLAN_WAKEUP_GAP_CFG); + pcmd->wakeupSignal = priv->wow.wakeSigType; + pcmd->wakeUpConditions = cpu_to_le32(wakeupCond); + //Enable below code only for debug purpose. + //It allows FW to unconditionally upload Rxed beacons. + //pcmd->options = cpu_to_le32(0x1); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_HOSTSLEEP_CTRL) || + (pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK))) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, " %s failed execution %x\n", + mwl_fwcmd_get_cmd_string(HOSTCMD_CMD_HOSTSLEEP_CTRL), + pcmd->cmd_hdr.result); + return -EIO; + } + + if (ds_enable) { + //We told firmware to enter deepsleep - set ds state accordingly + priv->if_ops.enter_deepsleep(priv); + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} +#endif + +#ifdef CONFIG_PM +int mwl_fwcmd_wowlan_apinrange_config(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_wowlan_ap_inrange_cfg *pcmd; + + pcmd = (struct hostcmd_cmd_wowlan_ap_inrange_cfg *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0, sizeof(*pcmd)); + + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (priv->wow.addrListCnt) { + /* Fill in BSSID */ + pcmd->addrIeList_Len = cpu_to_le16(priv->wow.addrListCnt * sizeof(struct wowlan_apinrange_addrIe)); + memcpy(&pcmd->addrIeList, priv->wow.addrList, sizeof(struct wowlan_apinrange_addrIe)); + } + + if (priv->wow.ssidListCnt) { + /* Fill in SSID */ + pcmd->ssidIeList_Len = cpu_to_le16(priv->wow.ssidListCnt * sizeof(struct wowlan_apinrange_ssidIe)); + memcpy(&pcmd->ssidIeList, priv->wow.ssidList, sizeof(struct wowlan_apinrange_ssidIe)); + } + + /* Fill in the list of channels to scan */ + pcmd->chanListCnt = cpu_to_le16(min(priv->wow.channelCnt, (u16)sizeof(pcmd->chanList))); + memcpy(pcmd->chanList, priv->wow.channels, le16_to_cpu(pcmd->chanListCnt)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, " %s failed execution\n", mwl_fwcmd_get_cmd_string(HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} +#endif + +int mwl_fwcmd_set_roc_channel(struct ieee80211_hw *hw, + struct ieee80211_channel *channel) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_rf_channel *pcmd; + u32 chnl_flags, freq_band = FREQ_BAND_2DOT4GHZ; + u32 chnl_width, act_primary; + + if (channel->band == NL80211_BAND_2GHZ) { + freq_band = FREQ_BAND_2DOT4GHZ; + } else if (channel->band == NL80211_BAND_5GHZ) { + freq_band = FREQ_BAND_5GHZ; + } + + chnl_width = CH_20_MHZ_WIDTH; + act_primary = ACT_PRIMARY_CHAN_0; + chnl_flags = (freq_band & FREQ_BAND_MASK) | + ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | + ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); + + pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RF_CHANNEL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->curr_chnl = channel->hw_value; + pcmd->remain_on_chan = 1; + pcmd->chnl_flags = cpu_to_le32(chnl_flags); + + if (mwl_fwcmd_exec_cmd(hw->priv, HOSTCMD_CMD_SET_RF_CHANNEL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int mwl_config_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_channel *channel, + bool remain_on_channel, int duration, + enum ieee80211_roc_type type) +{ + struct mwl_priv *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + int rc = 0; + + if (remain_on_channel) { + rc = mwl_fwcmd_radio_enable(hw); + } else { + channel = conf->chandef.chan; + if (conf->flags & IEEE80211_CONF_IDLE) + rc = mwl_fwcmd_radio_disable(hw); + } + + if (rc) + goto out; + + if (channel->band == NL80211_BAND_2GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED); + mwl_fwcmd_set_linkadapt_cs_mode(hw, + LINK_CS_STATE_CONSERV); + } else if (channel->band == NL80211_BAND_5GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_11AC); + mwl_fwcmd_set_linkadapt_cs_mode(hw, + LINK_CS_STATE_AUTO); + } + + if (remain_on_channel) + rc = mwl_fwcmd_set_roc_channel(hw, channel); + else + rc = mwl_fwcmd_set_rf_channel(hw, conf); + + if (rc) + goto out; + + priv->roc.in_progress = remain_on_channel; + priv->roc.chan = channel->hw_value; + priv->roc.duration = duration; + priv->roc.type = type; +out: + return rc; +} + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_rf_channel *pcmd; + u32 chnl_flags, freq_band, chnl_width, act_primary; + + pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RF_CHANNEL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->curr_chnl = channel->hw_value; + + if (channel->band == NL80211_BAND_2GHZ) { + freq_band = FREQ_BAND_2DOT4GHZ; + } else if (channel->band == NL80211_BAND_5GHZ) { + freq_band = FREQ_BAND_5GHZ; + } else { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + switch (conf->chandef.width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chnl_width = CH_20_MHZ_WIDTH; + act_primary = ACT_PRIMARY_CHAN_0; + break; + case NL80211_CHAN_WIDTH_40: + chnl_width = CH_40_MHZ_WIDTH; + if (conf->chandef.center_freq1 > channel->center_freq) + act_primary = ACT_PRIMARY_CHAN_0; + else + act_primary = ACT_PRIMARY_CHAN_1; + break; + case NL80211_CHAN_WIDTH_80: + chnl_width = CH_80_MHZ_WIDTH; + act_primary = + mwl_fwcmd_get_80m_pri_chnl(pcmd->curr_chnl); + break; + case NL80211_CHAN_WIDTH_160: + chnl_width = CH_160_MHZ_WIDTH; + act_primary = + mwl_fwcmd_get_160m_pri_chnl(pcmd->curr_chnl); + break; + default: + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + chnl_flags = (freq_band & FREQ_BAND_MASK) | + ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | + ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); + + pcmd->chnl_flags = cpu_to_le32(chnl_flags); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RF_CHANNEL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + mutex_unlock(&priv->fwcmd_mutex); + + if (priv->roc.in_progress) + return 0; + + if (priv->sw_scanning && priv->cur_survey_info.filled) { + int i; + for (i = 0; i < priv->survey_info_idx; i++) { + if (priv->cur_survey_info.channel.hw_value + == priv->survey_info[i].channel.hw_value) { + break; + } + } + memcpy(&priv->survey_info[i], &priv->cur_survey_info, + sizeof(struct mwl_survey_info)); + if (i == priv->survey_info_idx) + priv->survey_info_idx++; + } + + memset(&priv->cur_survey_info, 0x0, sizeof(struct mwl_survey_info)); + memcpy(&priv->cur_survey_info.channel, conf->chandef.chan, + sizeof(struct ieee80211_channel)); + priv->cur_survey_valid = true; + + if(mwl_fwcmd_get_survey(hw, 1)) { + return -EIO; + } + + return 0; +} + +int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *bssid, u16 aid) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_aid *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_aid *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_AID); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->aid = cpu_to_le16(aid); + ether_addr_copy(pcmd->mac_addr, bssid); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_AID)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_infra_mode *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_infra_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_INFRA_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_INFRA_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, int threshold) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_802_11_rts_thsd *pcmd; + + pcmd = (struct hostcmd_cmd_802_11_rts_thsd *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RTS_THSD); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->threshold = cpu_to_le16(threshold); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11_RTS_THSD)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, + u16 cw_min, u16 cw_max, u8 aifs, u16 txop) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_edca_params *pcmd; + + pcmd = (struct hostcmd_cmd_set_edca_params *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_EDCA_PARAMS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le16(0xffff); + pcmd->txop = cpu_to_le16(txop); + pcmd->cw_max = cpu_to_le32(cw_max); + pcmd->cw_min = cpu_to_le32(cw_min); + pcmd->aifsn = aifs; + pcmd->txq_num = index; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_EDCA_PARAMS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_802_11h_detect_radar *pcmd; + u16 radar_type = RADAR_TYPE_CODE_0; + u8 channel = hw->conf.chandef.chan->hw_value; + + pcmd = (struct hostcmd_cmd_802_11h_detect_radar *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + if (priv->reg.dfs_region == NL80211_DFS_JP) { + if (channel >= 52 && channel <= 64) + radar_type = RADAR_TYPE_CODE_53; + else if (channel >= 100 && channel <= 140) + radar_type = RADAR_TYPE_CODE_56; + else + radar_type = RADAR_TYPE_CODE_0; + } else if (priv->reg.dfs_region == NL80211_DFS_ETSI) { + radar_type = RADAR_TYPE_CODE_ETSI; + } + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11H_DETECT_RADAR); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(action); + pcmd->radar_type_code = cpu_to_le16(radar_type); + pcmd->min_chirp_cnt = cpu_to_le16(priv->dfs_chirp_count_min); + pcmd->chirp_time_intvl = cpu_to_le16(priv->dfs_chirp_time_interval); + pcmd->pw_filter = cpu_to_le16(priv->dfs_pw_filter); + pcmd->min_num_radar = cpu_to_le16(priv->dfs_min_num_radar); + pcmd->pri_min_num = cpu_to_le16(priv->dfs_min_pri_count); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_802_11H_DETECT_RADAR)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_wmm_mode *pcmd; + + pcmd = (struct hostcmd_cmd_set_wmm_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WMM_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WMM_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_ht_guard_interval *pcmd; + + pcmd = (struct hostcmd_cmd_ht_guard_interval *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_HT_GUARD_INTERVAL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le32(WL_SET); + pcmd->gi_type = cpu_to_le32(gi_type); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_HT_GUARD_INTERVAL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, int mcast, int mgmt) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_fixed_rate *pcmd; + + pcmd = (struct hostcmd_cmd_set_fixed_rate *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_FIXED_RATE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action = cpu_to_le32(HOSTCMD_ACT_NOT_USE_FIXED_RATE); + pcmd->multicast_rate = mcast; + pcmd->management_rate = mgmt; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_FIXED_RATE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw, u16 cs_mode) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_linkadapt_cs_mode *pcmd; + + pcmd = (struct hostcmd_cmd_set_linkadapt_cs_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_LINKADAPT_CS_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); + pcmd->cs_mode = cpu_to_le16(cs_mode); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_LINKADAPT_CS_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, u16 mode) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_rate_adapt_mode *pcmd; + + pcmd = (struct hostcmd_cmd_set_rate_adapt_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RATE_ADAPT_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->rate_adapt_mode = cpu_to_le16(mode); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_RATE_ADAPT_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_mac_addr *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_MAC_ADDR); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->mac_type = cpu_to_le16(WL_MAC_TYPE_SECONDARY_CLIENT); + ether_addr_copy(pcmd->mac_addr, mac_addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_MAC_ADDR) || + (pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK))) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_watchdog_bitmap *pcmd; + + pcmd = (struct hostcmd_cmd_get_watchdog_bitmap *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_WATCHDOG_BITMAP); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_WATCHDOG_BITMAP)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *bitmap = pcmd->watchdog_bitmap; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_mac_addr *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DEL_MAC_ADDR); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + ether_addr_copy(pcmd->mac_addr, mac_addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DEL_MAC_ADDR)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_ibss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bss_start *pcmd; + mwl_vif = mwl_dev_get_vif(vif); + if(enable) + wiphy_debug(priv->hw->wiphy, "CMD IBSS Start Enable,Mac Id %d\n", mwl_vif->macid); + else + wiphy_debug(priv->hw->wiphy, "CMD IBSS Start Disable\n"); + + + 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[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_IBSS_START); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (enable) { + pcmd->enable = cpu_to_le32(WL_ENABLE); + } else { + if (mwl_vif->macid == 0) + pcmd->enable = cpu_to_le32(WL_DISABLE); + else + pcmd->enable = cpu_to_le32(WL_DISABLE_VMAC); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_IBSS_START)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "failed execution\n"); + return -EIO; + } + + if (enable) + priv->running_bsses |= (1 << mwl_vif->macid); + else + priv->running_bsses &= ~(1 << mwl_vif->macid); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bss_start *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + if (enable && (priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + if (!enable && !(priv->running_bsses & (1 << mwl_vif->macid))) + return 0; + + pcmd = (struct hostcmd_cmd_bss_start *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BSS_START); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (enable) { + pcmd->enable = cpu_to_le32(WL_ENABLE); + } else { + if (mwl_vif->macid == 0) + pcmd->enable = cpu_to_le32(WL_DISABLE); + else + pcmd->enable = cpu_to_le32(WL_DISABLE_VMAC); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BSS_START)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (enable) + priv->running_bsses |= (1 << mwl_vif->macid); + else + priv->running_bsses &= ~(1 << mwl_vif->macid); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct beacon_info *b_inf; + int rc; + + mwl_vif = mwl_dev_get_vif(vif); + b_inf = &mwl_vif->beacon_info; + + mwl_fwcmd_parse_beacon(priv, mwl_vif, beacon, len); + + if (!b_inf->valid) + goto err; + + if (mwl_fwcmd_broadcast_ssid_enable(hw, mwl_vif, vif)) + goto err; + + if (mwl_fwcmd_set_ies(priv, mwl_vif)) + goto err; + + if (mwl_fwcmd_set_wsc_ie(hw, b_inf->ie_wsc_len, b_inf->ie_wsc_ptr)) + goto err; + + if (mwl_fwcmd_set_wfd_ie(hw, b_inf->ie_wfd_len, b_inf->ie_wfd_ptr)) + goto err; + + if (mwl_fwcmd_set_ap_beacon(priv, vif)) + goto err; + + if(vif->type == NL80211_IFTYPE_ADHOC) { + if (mwl_fwcmd_ibss_start(hw, vif, true)) + goto err; + } else { + if (mwl_fwcmd_bss_start(hw, vif, true)) + goto err; + } + + if (b_inf->cap_info & WLAN_CAPABILITY_SPECTRUM_MGMT) + rc = mwl_fwcmd_set_spectrum_mgmt(priv, true); + else + rc = mwl_fwcmd_set_spectrum_mgmt(priv, false); + if (rc) + goto err; + + if (b_inf->power_constraint) + rc = mwl_fwcmd_set_power_constraint(priv, + b_inf->power_constraint); + if (rc) + goto err; + + if (mwl_fwcmd_set_country_code(priv, mwl_vif, &vif->bss_conf)) + goto err; + + b_inf->valid = false; + + return 0; + +err: + + b_inf->valid = false; + + return -EIO; +} + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + u32 legacy_rates = 0; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->if_type = cpu_to_le16(vif->type); + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + pcmd->aid = cpu_to_le16(1); + pcmd->stn_id = cpu_to_le16(1); + } else { + pcmd->aid = cpu_to_le16(sta->aid); + pcmd->stn_id = cpu_to_le16(sta->aid); + } + ether_addr_copy(pcmd->mac_addr, sta->addr); + + if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ) { + /* + * 2.4 legacy rate bit definitions + * Driver: + * Modulation Bits Rate(Mbps) + * CCK 3 - 0 11, 5.5, 2, 1 + * OFDM 11 - 4 54, 48, 36, 24, 18, 12, 9, 6 + * Radio firmware: + * CCK 4 - 0 22, 11, 5.5, 2, 1 + * OFDM 12 - 5 54, 48, 36, 24, 18, 12, 9, 6 + */ + legacy_rates = sta->deflink.supp_rates[NL80211_BAND_2GHZ] & 0xF; + + /* shift OFDM rates by 1 */ + legacy_rates |= (sta->deflink.supp_rates[NL80211_BAND_2GHZ] & 0xFF0) << 1; + } else { + /* + * 5.0 legacy rate bit definitions + * Driver: + * Modulation Bits Rate(Mbps) + * OFDM 7 - 0 54, 48, 36, 24, 18, 12, 9, 6 + * Radio firmware: + * OFDM 12 - 5 54, 48, 36, 24, 18, 12, 9, 6 + * + * Shift by 5 + */ + legacy_rates = sta->deflink.supp_rates[NL80211_BAND_5GHZ] << 5; + } + pcmd->peer_info.legacy_rate_bitmap = cpu_to_le32(legacy_rates); + + if (sta->deflink.ht_cap.ht_supported) { + int i; + + for (i = 0; i < 4; i++) { + if (i < sta->deflink.rx_nss) { + pcmd->peer_info.ht_rates[i] = + sta->deflink.ht_cap.mcs.rx_mask[i]; + } else { + pcmd->peer_info.ht_rates[i] = 0; + } + } + pcmd->peer_info.ht_cap_info = cpu_to_le16(sta->deflink.ht_cap.cap); + pcmd->peer_info.mac_ht_param_info = + (sta->deflink.ht_cap.ampdu_factor & 3) | + ((sta->deflink.ht_cap.ampdu_density & 7) << 2); + } + + if (sta->deflink.vht_cap.vht_supported) { + u32 rx_mcs_map_mask = 0; + + rx_mcs_map_mask = ((0x0000FFFF) >> (sta->deflink.rx_nss * 2)) + << (sta->deflink.rx_nss * 2); + pcmd->peer_info.vht_max_rx_mcs = + cpu_to_le32((*((u32 *) + &sta->deflink.vht_cap.vht_mcs.rx_mcs_map)) | rx_mcs_map_mask); + pcmd->peer_info.vht_cap = cpu_to_le32(sta->deflink.vht_cap.cap); + pcmd->peer_info.vht_rx_channel_width = sta->deflink.bandwidth; + } + + pcmd->is_qos_sta = sta->wme; + pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + pcmd->aid = cpu_to_le16(2); + pcmd->stn_id = cpu_to_le16(2); + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->if_type = cpu_to_le16(vif->type); + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); + ether_addr_copy(pcmd->mac_addr, vif->addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN) || + (pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK))) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_new_stn *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_REMOVE); + ether_addr_copy(pcmd->mac_addr, addr); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_NEW_STN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_apmode *pcmd; + + pcmd = (struct hostcmd_cmd_set_apmode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_APMODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->apmode = apmode; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_APMODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_switch_channel(struct mwl_priv *priv, + struct ieee80211_channel_switch *ch_switch) +{ + struct hostcmd_cmd_set_switch_channel *pcmd; + struct cfg80211_chan_def *chandef = &ch_switch->chandef; + struct ieee80211_channel *channel = chandef->chan; + u32 chnl_flags, freq_band, chnl_width, act_primary, sec_chnl_offset; + + if (priv->csa_active) + return 0; + + if (channel->band == NL80211_BAND_2GHZ) + freq_band = FREQ_BAND_2DOT4GHZ; + else if (channel->band == NL80211_BAND_5GHZ) + freq_band = FREQ_BAND_5GHZ; + else + return -EINVAL; + + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + chnl_width = CH_20_MHZ_WIDTH; + act_primary = ACT_PRIMARY_CHAN_0; + sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + case NL80211_CHAN_WIDTH_40: + chnl_width = CH_40_MHZ_WIDTH; + if (chandef->center_freq1 > channel->center_freq) { + act_primary = ACT_PRIMARY_CHAN_0; + sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + } else { + act_primary = ACT_PRIMARY_CHAN_1; + sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + } + break; + case NL80211_CHAN_WIDTH_80: + chnl_width = CH_80_MHZ_WIDTH; + act_primary = + mwl_fwcmd_get_80m_pri_chnl(channel->hw_value); + if ((act_primary == ACT_PRIMARY_CHAN_0) || + (act_primary == ACT_PRIMARY_CHAN_2)) + sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + else + sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + break; + default: + return -EINVAL; + } + + chnl_flags = (freq_band & FREQ_BAND_MASK) | + ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | + ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); + + pcmd = (struct hostcmd_cmd_set_switch_channel *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SWITCH_CHANNEL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->next_11h_chnl = cpu_to_le32(channel->hw_value); + pcmd->mode = cpu_to_le32(ch_switch->block_tx); + pcmd->init_count = cpu_to_le32(ch_switch->count + 1); + pcmd->chnl_flags = cpu_to_le32(chnl_flags); + pcmd->next_ht_extchnl_offset = cpu_to_le32(sec_chnl_offset); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_SWITCH_CHANNEL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + priv->csa_active = true; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, u8 encr_type) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_update_encryption *pcmd; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_update_encryption *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + pcmd->action_type = cpu_to_le32(ENCR_ACTION_ENABLE_HW_ENCR); + ether_addr_copy(pcmd->mac_addr, addr); + pcmd->action_data[0] = encr_type; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + if (ether_addr_equal(mwl_vif->bssid, addr)) + ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); + else + ether_addr_copy(pcmd->mac_addr, mwl_vif->bssid); + + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + int rc; + int keymlen; + u32 action; + u8 idx; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, vif, addr, key); + if (rc) { + mutex_unlock(&priv->fwcmd_mutex); + if (rc < 0) + wiphy_err(hw->wiphy, "encryption not supported\n"); + return rc; + } + + idx = key->keyidx; + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + action = ENCR_ACTION_TYPE_SET_KEY; + else + action = ENCR_ACTION_TYPE_SET_GROUP_KEY; + +/* TODO: This code is missing in git ToT now - security mayn't work */ +#if 0 +{ + action = ENCR_ACTION_TYPE_SET_GROUP_KEY; + + /* if (vif->type == NL80211_IFTYPE_MESH_POINT && + !ether_addr_equal(mwl_vif->bssid, addr)) */ + pcmd->key_param.key_info |= + cpu_to_le32(ENCR_KEY_FLAG_RXGROUPKEY); +} +#endif + + 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; + } + + if (vif->type == NL80211_IFTYPE_STATION) { + ether_addr_copy(mwl_vif->bssid, vif->bss_conf.bssid); + } else if(vif->type == NL80211_IFTYPE_ADHOC) { + if(!ether_addr_equal(vif->addr, addr)) { + /* To handle a race condition when mac80211 doesnt populate + * keys for the newly added STA. Requesting FW to populate + * Keys for all the IBSS STA added till now */ + pcmd->key_param.cfg_flags = cpu_to_le32(0x1); + } + } + keymlen = key->keylen; + action = ENCR_ACTION_TYPE_SET_KEY; + break; + case WLAN_CIPHER_SUITE_TKIP: + keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH; + break; + case WLAN_CIPHER_SUITE_CCMP: + keymlen = key->keylen; + break; + default: + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "encryption not support\n"); + return -ENOTSUPP; + } + + memcpy((void *)&pcmd->key_param.key, key->key, keymlen); + pcmd->action_type = cpu_to_le32(action); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + if (ether_addr_equal(mwl_vif->bssid, addr)) + ether_addr_copy(pcmd->key_param.mac_addr, + mwl_vif->sta_mac); + else + ether_addr_copy(pcmd->key_param.mac_addr, + mwl_vif->bssid); + + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + int rc; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, vif, addr, key); + if (rc) { + if (rc < 0) + wiphy_err(hw->wiphy, "encryption not supported\n"); + goto out; + } + + pcmd->action_type = cpu_to_le32(ENCR_ACTION_TYPE_REMOVE_KEY); + + rc = mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION); + +out: + mutex_unlock(&priv->fwcmd_mutex); + return rc; +} + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + u32 ba_flags, ba_type, ba_direction; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = cpu_to_le16(0xffff); + + pcmd->action_type = cpu_to_le32(BA_CHECK_STREAM); + ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr); + pcmd->ba_info.create_params.tid = stream->tid; + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.create_params.queue_id = stream->idx; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_bastream *pcmd; + u32 ba_flags, ba_type, ba_direction; + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + pcmd->cmd_hdr.result = cpu_to_le16(0xffff); + + pcmd->action_type = cpu_to_le32(BA_CREATE_STREAM); + pcmd->ba_info.create_params.bar_thrs = cpu_to_le32(buf_size); + pcmd->ba_info.create_params.window_size = cpu_to_le32(buf_size); + ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0], + stream->sta->addr); + pcmd->ba_info.create_params.tid = stream->tid; + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.create_params.queue_id = stream->idx; + pcmd->ba_info.create_params.param_info = + (stream->sta->deflink.ht_cap.ampdu_factor & + IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((stream->sta->deflink.ht_cap.ampdu_density << 2) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + +#if 0 + pcmd->ba_info.create_params.reset_seq_no = 1; + pcmd->ba_info.create_params.current_seq = cpu_to_le16(0); +#endif + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "create ba result error %d\n", + le16_to_cpu(pcmd->cmd_hdr.result)); + return -EINVAL; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_bastream *pcmd; + u32 ba_flags, ba_type, ba_direction; + + pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + pcmd->action_type = cpu_to_le32(BA_DESTROY_STREAM); + ba_type = BASTREAM_FLAG_IMMEDIATE_TYPE; + ba_direction = BASTREAM_FLAG_DIRECTION_UPSTREAM; + ba_flags = (ba_type & BA_TYPE_MASK) | + ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); + pcmd->ba_info.destroy_params.flags = cpu_to_le32(ba_flags); + pcmd->ba_info.destroy_params.fw_ba_context.context = cpu_to_le32(idx); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_BASTREAM)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +/* caller must hold priv->stream_lock when calling the stream functions */ +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 tid) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + int i; + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) { + stream->sta = sta; + stream->state = AMPDU_STREAM_NEW; + stream->tid = tid; + stream->idx = i; + return stream; + } + } + + return NULL; +} + +void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + int i; + + spin_lock_bh(&priv->stream_lock); + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->sta == sta) { + mwl_fwcmd_remove_stream(hw, stream); + spin_unlock_bh(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, stream->idx); + spin_lock_bh(&priv->stream_lock); + } + } + spin_unlock_bh(&priv->stream_lock); +} + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + /* if the stream has already been started, don't start it again */ + if (stream->state != AMPDU_STREAM_NEW) + return 0; + + return ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); +} + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream) +{ + memset(stream, 0, sizeof(*stream)); +} + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + int i; + + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_NO_STREAM) + continue; + + if (ether_addr_equal(stream->sta->addr, addr) && + stream->tid == tid) + return stream; + } + + return NULL; +} + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + + if (WARN_ON(tid >= SYSADPT_MAX_TID)) + return false; + + sta_info = mwl_dev_get_sta(sta); + + tx_stats = &sta_info->tx_stats[tid]; + + return (sta_info->is_ampdu_allowed && + tx_stats->pkts > SYSADPT_AMPDU_PACKET_THRESHOLD); +} + +int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_optimization_level *pcmd; + + pcmd = (struct hostcmd_cmd_set_optimization_level *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->opt_level = opt_level; + + wiphy_info(hw->wiphy, "WMM Turbo=%d\n", opt_level); + + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_mimops_ht(struct ieee80211_hw *hw, u8 *addr, u8 smps_ctrl) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_mimops_ht *pcmd; + + pcmd = (struct hostcmd_cmd_set_mimops_ht *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_MIMOPSHT); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + ether_addr_copy(pcmd->addr, addr); + + pcmd->enbl = (smps_ctrl & 0x1); + pcmd->mode = (smps_ctrl >> 1); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_MIMOPSHT)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_wfd_ie(struct ieee80211_hw *hw, u8 len, u8 *data) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_wfd_ie *pcmd; + + if (len > sizeof(pcmd->data)) { + wiphy_err(priv->hw->wiphy, + "WFD IE is longer than radio supports"); + return -EIO; + } + + pcmd = (struct hostcmd_cmd_set_wfd_ie *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WFD_IE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->len = cpu_to_le16(len); + memcpy(pcmd->data, data, len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WFD_IE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + pcmd->ie_type = cpu_to_le16(WFD_IE_SET_PROBE_RESPONSE); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WFD_IE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->len = cpu_to_le16(len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WFD_IE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_wsc_ie *pcmd; + + if (len > sizeof(pcmd->data)) { + wiphy_err(priv->hw->wiphy, + "WSC IE is longer than radio supports"); + return -EIO; + } + + pcmd = (struct hostcmd_cmd_set_wsc_ie *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WSC_IE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->len = cpu_to_le16(len); + memcpy(pcmd->data, data, len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WSC_IE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + pcmd->ie_type = cpu_to_le16(WSC_IE_SET_PROBE_RESPONSE); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WSC_IE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->len = cpu_to_le16(len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_WSC_IE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_dwds_enable *pcmd; + + pcmd = (struct hostcmd_cmd_dwds_enable *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DWDS_ENABLE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enable = cpu_to_le32(enable); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DWDS_ENABLE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_fw_flush_timer *pcmd; + + pcmd = (struct hostcmd_cmd_fw_flush_timer *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_FW_FLUSH_TIMER); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->value = cpu_to_le32(value); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_FW_FLUSH_TIMER)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_set_cdd *pcmd; + + pcmd = (struct hostcmd_cmd_set_cdd *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CDD); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enable = cpu_to_le32(priv->cdd); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_CDD)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_reg_cau(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_bbp_reg_access *pcmd; + + pcmd = (struct hostcmd_cmd_bbp_reg_access *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_CAU_REG_ACCESS); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->offset = cpu_to_le16(reg); + pcmd->action = cpu_to_le16(flag); + pcmd->value = (u8)cpu_to_le32(*val); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_CAU_REG_ACCESS)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *val = (u32)pcmd->value; + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(mwl_fwcmd_reg_cau); + +int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, s32 *temp) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_temp *pcmd; + + pcmd = (struct hostcmd_cmd_get_temp *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_TEMP); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_GET_TEMP)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + *temp = le32_to_cpu(pcmd->celcius); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw, + u32 *fw_region_code) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_fw_region_code *pcmd; + u16 cmd; + int status; + + pcmd = (struct hostcmd_cmd_get_fw_region_code *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + cmd = HOSTCMD_CMD_GET_FW_REGION_CODE; + pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, cmd)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + status = le32_to_cpu(pcmd->status); + + if (!status) + *fw_region_code = le32_to_cpu(pcmd->fw_region_code); + + mutex_unlock(&priv->fwcmd_mutex); + + return status; +} + +int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw, + struct mwl_device_pwr_tbl *device_ch_pwrtbl, + u8 *region_code, + u8 *number_of_channels, + u32 channel_index) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_device_pwr_tbl *pcmd; + int status; + u16 cmd; + + pcmd = (struct hostcmd_cmd_get_device_pwr_tbl *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + cmd = HOSTCMD_CMD_GET_DEVICE_PWR_TBL; + pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->status = cpu_to_le16(cmd); + pcmd->current_channel_index = cpu_to_le32(channel_index); + + if (mwl_fwcmd_exec_cmd(priv, cmd)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + device_ch_pwrtbl->channel = pcmd->channel_pwr_tbl.channel; + memcpy(device_ch_pwrtbl->tx_pwr, pcmd->channel_pwr_tbl.tx_pwr, + SYSADPT_TX_GRP_PWR_LEVEL_TOTAL); + device_ch_pwrtbl->dfs_capable = pcmd->channel_pwr_tbl.dfs_capable; + device_ch_pwrtbl->ax_ant = pcmd->channel_pwr_tbl.ax_ant; + device_ch_pwrtbl->cdd = pcmd->channel_pwr_tbl.cdd; + *region_code = pcmd->region_code; + *number_of_channels = pcmd->number_of_channels; + status = le16_to_cpu(pcmd->status); + + mutex_unlock(&priv->fwcmd_mutex); + + return status; +} + +int mwl_fwcmd_get_fw_region_code_sc4(struct ieee80211_hw *hw, + u32 *fw_region_code) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_fw_region_code_sc4 *pcmd; + u16 cmd; + + pcmd = (struct hostcmd_cmd_get_fw_region_code_sc4 *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + cmd = HOSTCMD_CMD_GET_FW_REGION_CODE_SC4; + pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, cmd)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + if (pcmd->cmd_hdr.result != 0) { + mutex_unlock(&priv->fwcmd_mutex); + return -EINVAL; + } + + *fw_region_code = le32_to_cpu(pcmd->fw_region_code); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_get_pwr_tbl_sc4(struct ieee80211_hw *hw, + struct mwl_device_pwr_tbl *device_ch_pwrtbl, + u8 *region_code, + u8 *number_of_channels, + u32 channel_index) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_get_device_pwr_tbl_sc4 *pcmd; + int status; + u16 cmd; + + pcmd = (struct hostcmd_cmd_get_device_pwr_tbl_sc4 *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + cmd = HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4; + pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->status = cpu_to_le16(cmd); + pcmd->current_channel_index = cpu_to_le32(channel_index); + + if (mwl_fwcmd_exec_cmd(priv, cmd)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + device_ch_pwrtbl->channel = pcmd->channel_pwr_tbl.channel; + memcpy(device_ch_pwrtbl->tx_pwr, pcmd->channel_pwr_tbl.tx_pwr, + SYSADPT_TX_PWR_LEVEL_TOTAL_SC4); + device_ch_pwrtbl->dfs_capable = pcmd->channel_pwr_tbl.dfs_capable; + device_ch_pwrtbl->ax_ant = pcmd->channel_pwr_tbl.ax_ant; + device_ch_pwrtbl->cdd = pcmd->channel_pwr_tbl.cdd; + *region_code = pcmd->region_code; + *number_of_channels = pcmd->number_of_channels; + status = le16_to_cpu(pcmd->status); + + mutex_unlock(&priv->fwcmd_mutex); + + return status; +} + +int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period, + u32 duration, u32 next_offset) +{ + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_quiet_mode *pcmd; + + pcmd = (struct hostcmd_cmd_quiet_mode *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_QUIET_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->action = cpu_to_le16(WL_SET); + pcmd->enable = cpu_to_le32(enable); + if (enable) { + pcmd->period = cpu_to_le32(period); + pcmd->duration = cpu_to_le32(duration); + pcmd->next_offset = cpu_to_le32(next_offset); + } + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_QUIET_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_get_survey(struct ieee80211_hw *hw, int rstReg) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_survey_info *survey_info; + int last_read_val = 0, cca_cnt_val = 0, txpe_cnt_val = 0; + + if(!priv->cur_survey_valid) + return 0; + + survey_info = &priv->cur_survey_info; + + if(mwl_fwcmd_reg_mac(hw, WL_GET, MCU_LAST_READ, &last_read_val)) + return -EIO; + if(mwl_fwcmd_reg_mac(hw, WL_GET, MCU_CCA_CNT, &cca_cnt_val)) + return -EIO; + if(mwl_fwcmd_reg_mac(hw, WL_GET, MCU_TXPE_CNT, &txpe_cnt_val)) + return -EIO; + + if(!rstReg) { + survey_info->filled = SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_TX | + SURVEY_INFO_NOISE_DBM; + + survey_info->time_period += last_read_val; + survey_info->time_busy += cca_cnt_val; + survey_info->time_tx += txpe_cnt_val; + survey_info->noise = priv->noise; + } + return 0; +} + +int mwl_fwcmd_dump_otp_data(struct ieee80211_hw *hw) +{ + int otp_data_len; + struct mwl_priv *priv = hw->priv; + struct hostcmd_cmd_dump_otp_data *pcmd; + + pcmd = (struct hostcmd_cmd_dump_otp_data *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DUMP_OTP_DATA); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_DUMP_OTP_DATA)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + otp_data_len = pcmd->cmd_hdr.len - cpu_to_le16(sizeof(*pcmd)); + + if (otp_data_len <= MWL_OTP_BUF_SIZE) { + wiphy_info(hw->wiphy, "OTP data len = %d\n", otp_data_len); + priv->otp_data.len = otp_data_len; + memcpy(priv->otp_data.buf, pcmd->pload, otp_data_len); + mwl_hex_dump(priv->otp_data.buf, priv->otp_data.len); + } else { + wiphy_err(hw->wiphy, "Driver OTP buf size is less\n"); + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_set_pre_scan(struct ieee80211_hw *hw) +{ + struct hostcmd_cmd_pre_scan *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_pre_scan*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_PRE_SCAN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_PRE_SCAN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int mwl_fwcmd_set_post_scan(struct ieee80211_hw *hw) +{ + struct hostcmd_cmd_post_scan *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_post_scan*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_POST_SCAN); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_SET_POST_SCAN)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int mwl_fwcmd_get_region_mapping(struct ieee80211_hw *hw, + struct mwl_region_mapping *map) +{ + struct hostcmd_cmd_region_mapping *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_region_mapping*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_LRD_REGION_MAPPING); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_REGION_MAPPING) || + pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "mwl_fwcmd_get_region_mapping failed execution\n"); + return -EIO; + } + + memcpy(map->cc, pcmd->cc, sizeof(map->cc)); + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int mwl_fwcmd_encryption_set_tx_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct hostcmd_cmd_set_key *pcmd; + struct hostcmd_cmd_update_encryption *pcmd2; + int rc; + + if (key->cipher != WLAN_CIPHER_SUITE_WEP40 && + key->cipher != WLAN_CIPHER_SUITE_WEP104) { + wiphy_err(hw->wiphy, "encryption not support\n"); + return -ENOTSUPP; + } + + mwl_vif = mwl_dev_get_vif(vif); + + pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->cmd_hdr.macid = mwl_vif->macid; + + rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, vif, mwl_vif->bssid, key); + if (rc) { + mutex_unlock(&priv->fwcmd_mutex); + if (rc < 0) + wiphy_err(hw->wiphy, "encryption not supported\n"); + return rc; + } + + memcpy((void *)&pcmd->key_param.key, key->key, key->keylen); + pcmd->action_type = cpu_to_le32(ENCR_ACTION_TYPE_SET_KEY); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + pcmd2 = (struct hostcmd_cmd_update_encryption *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + memset(pcmd2, 0x00, sizeof(*pcmd2)); + pcmd2->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); + pcmd2->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd2->cmd_hdr.macid = mwl_vif->macid; + + pcmd2->action_type = cpu_to_le32(ENCR_ACTION_ENABLE_HW_ENCR); + pcmd2->action_data[0] = ENCR_TYPE_WEP; + + ether_addr_copy(pcmd2->mac_addr, mwl_vif->bssid); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_mfg_start(struct ieee80211_hw *hw, u32 *data) +{ + struct hostcmd_cmd_mfg *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_mfg*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(u32)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_LRD_MFG); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + sizeof(u32)); + pcmd->action = cpu_to_le32(MFG_TYPE_START); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_MFG) || + pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_mfg_start failed execution %x\n", le16_to_cpu(pcmd->cmd_hdr.result)); + return -EIO; + } + + if (data ) { + *data = le32_to_cpu(*(u32*)pcmd->data); + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_mfg_end(struct ieee80211_hw *hw) +{ + struct hostcmd_cmd_mfg *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_cmd_mfg*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_LRD_MFG); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + sizeof(u32)); + pcmd->action = cpu_to_le32(MFG_TYPE_END); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_MFG) || + pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_mfg_end failed execution %x\n", le16_to_cpu(pcmd->cmd_hdr.result)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int lrd_fwcmd_mfg_write(struct ieee80211_hw *hw, void *data, int data_len) +{ + struct hostcmd_cmd_mfg *pcmd; + struct mwl_priv *priv = hw->priv; + + //Validate data is multiple of 32 + if ((data_len % sizeof(u32))) { + return -EINVAL; + } + + pcmd = (struct hostcmd_cmd_mfg*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + data_len); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_LRD_MFG); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + data_len); + pcmd->action = cpu_to_le32(MFG_TYPE_WRITE); + + memcpy(pcmd->data, data, data_len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_MFG) || + pcmd->cmd_hdr.result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_mfg_write failed execution %x\n", le16_to_cpu(pcmd->cmd_hdr.result)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lru_write(struct ieee80211_hw *hw, void *data, int len, void **rsp) +{ + struct hostcmd_mfgfw_header *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_mfgfw_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd = cpu_to_le16(HOSTCMD_CMD_MFG); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + len); + + memcpy(((u8*)pcmd) + sizeof(struct hostcmd_mfgfw_header), data, len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_MFG)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lru_write failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + if (le16_to_cpu(pcmd->len)) { + /* To keep structures somewhat encapsulated we are going to squash + * part of the hostcmd_mfgfw_header so that we are left only with + * lrd_vndr_header and data response + */ + u16 len = le16_to_cpu(pcmd->len) - sizeof(struct hostcmd_mfgfw_header) + sizeof(struct lrd_vndr_header); + + *rsp = kzalloc(len, GFP_KERNEL); + if (!*rsp) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lru_write failed allocation response %d\n", le16_to_cpu(pcmd->len)); + return -EIO; + } + + ((struct lrd_vndr_header*)*rsp)->command = le16_to_cpu(pcmd->cmd); + ((struct lrd_vndr_header*)*rsp)->result = le16_to_cpu(pcmd->result); + ((struct lrd_vndr_header*)*rsp)->len = len; + memcpy(((u8*)*rsp) + (u8)sizeof(struct lrd_vndr_header), ((u8*)pcmd) + sizeof(struct hostcmd_mfgfw_header) , len - sizeof(struct lrd_vndr_header)); + } + + mutex_unlock(&priv->fwcmd_mutex); + + return 0; +} + +int lrd_fwcmd_lrd_write(struct ieee80211_hw *hw, void *data, int len, void **rsp) +{ + struct hostcmd_header *pcmd; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + len); + + memcpy(((u8*)pcmd) + sizeof(struct hostcmd_header), data, len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_write failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + if (pcmd->len) { + /* To keep structures somewhat encapsulated we are going to squash + * part of the hostcmd_cmd_lrd so that we are left only with + * lrd_vndr_header and data response + */ + u16 len = le16_to_cpu(pcmd->len) - sizeof(struct hostcmd_header) + sizeof(struct lrd_vndr_header); + + *rsp = kzalloc(len, GFP_KERNEL); + if (!*rsp) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_write failed allocation response %d\n", le16_to_cpu(pcmd->len)); + return -EIO; + } + + ((struct lrd_vndr_header*)*rsp)->command = le16_to_cpu(pcmd->cmd); + ((struct lrd_vndr_header*)*rsp)->result = le16_to_cpu(pcmd->result); + ((struct lrd_vndr_header*)*rsp)->len = len; + + memcpy(((u8*)*rsp) + sizeof(struct lrd_vndr_header), ((u8*)pcmd) + sizeof(struct hostcmd_header) , len - sizeof(struct lrd_vndr_header)); + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lrd_get_caps(struct ieee80211_hw *hw, struct lrd_radio_caps *r_caps) +{ + struct hostcmd_header *pcmd; + struct lrdcmd_cmd_cap *caps; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(*caps)); + + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + sizeof(*caps)); + + //Fill in capability struct + caps = (struct lrdcmd_cmd_cap*) (((u8*)pcmd) + sizeof(struct hostcmd_header)); + caps->hdr.lrd_cmd = cpu_to_le16(LRD_CMD_CAPS); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD) || + pcmd->result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_get_caps failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + r_caps->capability = le16_to_cpu(caps->capability); + r_caps->num_mac = le16_to_cpu(caps->num_mac_addr); + r_caps->version = le16_to_cpu(caps->version); + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lrd_set_ant_gain_adjust(struct ieee80211_hw *hw, u32 adjust) +{ + struct hostcmd_header *pcmd; + struct lrdcmd_cmd_ant_gain_adjust *adjust_cmd; + struct mwl_priv *priv = hw->priv; + + if (adjust & ~ANT_GAIN_VALID_MASK) { + wiphy_err(hw->wiphy, "Invalid antenna gain adjust value (0x%x)!!\n", adjust); + return -EINVAL; + } + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(*adjust_cmd)); + + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + sizeof(*adjust_cmd)); + + //Fill in pwr struct + adjust_cmd = (struct lrdcmd_cmd_ant_gain_adjust*) (((u8*)pcmd) + sizeof(struct hostcmd_header)); + adjust_cmd->hdr.lrd_cmd = cpu_to_le16(LRD_CMD_ANT_GAIN_ADJUST); + adjust_cmd->ant_gain_adjust = cpu_to_le32(adjust); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD) || + pcmd->result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_set_ant_gain_adjust failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lrd_reset_power_table(struct ieee80211_hw *hw) +{ + struct hostcmd_header *pcmd; + struct mwl_priv *priv = hw->priv; + struct lrdcmd_header *pwr_cmd; + + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(*pwr_cmd)); + + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + sizeof(*pwr_cmd)); + + //Fill in pwr struct + pwr_cmd = (struct lrdcmd_header*) (((u8*)pcmd) + sizeof(struct hostcmd_header)); + pwr_cmd->lrd_cmd = cpu_to_le16(LRD_CMD_PWR_TABLE_RESET); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD) || + pcmd->result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_reset_pwr_table failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lrd_set_power_table(struct ieee80211_hw *hw, u16 index, void *data, u32 data_len) +{ + struct hostcmd_header *pcmd; + struct lrdcmd_cmd_pwr_table *pwr_cmd; + struct mwl_priv *priv = hw->priv; + u8 a1[] = {0xC0,0xEE,0x40,0x40,0x02,0x30}; + u8 a2[] = {0xC0,0xEE,0x40,0x42,0x7F,0x6C}; + + if (NULL == data || data_len < MIN_AWM_SIZE || + data_len >= (CMD_BUF_SIZE - INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)) ) { + wiphy_err(hw->wiphy, "Invalid power table (0x%x)!!\n", data_len); + return -EINVAL; + } + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(*pwr_cmd) + data_len); + + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + sizeof(*pwr_cmd) + data_len); + + //Fill in pwr struct + pwr_cmd = (struct lrdcmd_cmd_pwr_table*) (((u8*)pcmd) + sizeof(struct hostcmd_header)); + pwr_cmd->hdr.lrd_cmd = cpu_to_le16(LRD_CMD_PWR_TABLE); + pwr_cmd->len = cpu_to_le16(data_len + sizeof(struct lrdcmd_cmd_pwr_table) - offsetof(struct lrdcmd_cmd_pwr_table, len)); + + //Add header + pwr_cmd->frm.frame_control = cpu_to_le16(0x4208); + pwr_cmd->frm.duration_id = cpu_to_le16(44); + pwr_cmd->frm.seq_ctrl = cpu_to_le16(index << 4); + memcpy(pwr_cmd->frm.addr1, a1, sizeof(pwr_cmd->frm.addr1)); + memcpy(pwr_cmd->frm.addr2, a2, sizeof(pwr_cmd->frm.addr2)); + memcpy(pwr_cmd->frm.addr3, a2, sizeof(pwr_cmd->frm.addr3)); + + // It is expected caller has dealt with any endian issues in data. + memcpy(pwr_cmd->data, data, data_len); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD) || + pcmd->result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_set_pwr_table failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int lrd_fwcmd_lrd_get_power_table_result(struct ieee80211_hw *hw, u32 *result, u32 *pn) +{ + struct hostcmd_header *pcmd; + struct lrdcmd_cmd_pwr_table_result *pwr_result; + struct mwl_priv *priv = hw->priv; + + pcmd = (struct hostcmd_header*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd) + sizeof(*pwr_result)); + + pcmd->cmd = cpu_to_le16(HOSTCMD_LRD_CMD); + pcmd->len = cpu_to_le16(sizeof(*pcmd) + sizeof(*pwr_result)); + + //Fill in pwr struct + pwr_result = (struct lrdcmd_cmd_pwr_table_result*) (((u8*)pcmd) + sizeof(struct hostcmd_header)); + pwr_result->hdr.lrd_cmd = cpu_to_le16(LRD_CMD_PWR_TABLE_RESULT); + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_LRD_CMD) || + pcmd->result != cpu_to_le16(HOSTCMD_RESULT_OK)) { + mutex_unlock(&priv->fwcmd_mutex); + wiphy_err(hw->wiphy, "lrd_fwcmd_lrd_set_pwr_table failed execution %x\n", le16_to_cpu(pcmd->result)); + return -EIO; + } + + *result = le32_to_cpu(pwr_result->result); + *pn = le32_to_cpu(pwr_result->pn); + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} + +int mwl_fwcmd_set_monitor_mode(struct ieee80211_hw *hw, bool enable) +{ + struct hostcmd_cmd_monitor_mode *pcmd; + struct mwl_priv *priv = hw->priv; + pcmd = (struct hostcmd_cmd_monitor_mode*)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + mutex_lock(&priv->fwcmd_mutex); + + memset(pcmd, 0x00, sizeof(*pcmd)); + pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MONITOR_MODE); + pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); + pcmd->enableFlag = enable; + + if (mwl_fwcmd_exec_cmd(priv, HOSTCMD_CMD_MONITOR_MODE)) { + mutex_unlock(&priv->fwcmd_mutex); + return -EIO; + } + + mutex_unlock(&priv->fwcmd_mutex); + return 0; +} diff --git a/drivers/net/wireless/laird/lrdmwl/fwcmd.h b/drivers/net/wireless/laird/lrdmwl/fwcmd.h new file mode 100644 index 0000000000000..f622f4d0e9d85 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/fwcmd.h @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines firmware host command related + * functions. + */ + +#ifndef _FWCMD_H_ +#define _FWCMD_H_ + +/* Define OpMode for SoftAP/Station mode + * + * The following mode signature has to be written to PCI scratch register#0 + * right after successfully downloading the last block of firmware and + * before waiting for firmware ready signature + */ + +#define HOSTCMD_STA_MODE 0x5A +#define HOSTCMD_SOFTAP_MODE 0xA5 + +#define HOSTCMD_STA_FWRDY_SIGNATURE 0xF0F1F2F4 +#define HOSTCMD_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 +#define MFG_FW_READY_SIGNATURE 0xFEDCBA00 + +#define HOSTCMD_RESP_BIT 0x8000 + +#define GUARD_INTERVAL_STANDARD 1 +#define GUARD_INTERVAL_SHORT 2 +#define GUARD_INTERVAL_AUTO 3 + +#define LINK_CS_STATE_CONSERV 0 +#define LINK_CS_STATE_AGGR 1 +#define LINK_CS_STATE_AUTO 2 +#define LINK_CS_STATE_AUTO_DISABLED 3 + +#define STOP_DETECT_RADAR 0 +#define CAC_START 1 +#define MONITOR_START 3 + +enum { + WL_ANTENNATYPE_RX = 1, + WL_ANTENNATYPE_TX = 2, +}; + +enum encr_type { + ENCR_TYPE_WEP = 0, + ENCR_TYPE_DISABLE = 1, + ENCR_TYPE_TKIP = 4, + ENCR_TYPE_AES = 6, + ENCR_TYPE_MIX = 7, +}; + +struct cmd_header { + __le16 command; + __le16 len; +} __packed; + +enum lrd_fw_commands{ + LRD_CMD_CAPS = 1, + LRD_CMD_ANT_GAIN_ADJUST = 2, + LRD_CMD_AWM_INIT, + LRD_CMD_PWR_TABLE, + LRD_CMD_PWR_TABLE_RESULT, + LRD_CMD_PWR_TABLE_RESET, + LRD_CMD_CC_OTP_INFO, + LRD_CMD_CC_INFO, +}; + +struct lrd_vndr_header +{ + __le16 command; + __le16 result; + __le16 len; +}__packed; + +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); + +char *mwl_fwcmd_get_cmd_string(unsigned short cmd); + +int mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_cfg_data(struct ieee80211_hw *hw, u16 type); + +int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats); + +int mwl_fwcmd_reg_mac(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); + +int mwl_fwcmd_reg_bb(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); + +int mwl_fwcmd_reg_rf(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); + +int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw); + +int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw); + +int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, + bool short_preamble); + +int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len, + u32 *val, u16 set); + +int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, u16 level); + +int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna); + +int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, + struct mwl_vif *mwl_vif, + struct ieee80211_vif *vif); + +int mwl_fwcmd_powersave_EnblDsbl(struct ieee80211_hw *hw, + struct ieee80211_conf *conf); + +int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf); + +int mwl_config_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_channel *channel, + bool remain_on_channel, int duration, + enum ieee80211_roc_type type); + +int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *bssid, u16 aid); + +int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); + +int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, + int threshold); + +int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, + u16 cw_min, u16 cw_max, u8 aifs, u16 txop); + +int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action); + +int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable); + +int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type); + +int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, + int mcast, int mgmt); + +int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw, + u16 cs_mode); + +int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, + u16 mode); + +int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr); + +int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, + u8 *bitmap); + +int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac_addr); + +int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_ibss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, bool enable); + +int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len); + +int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); + +int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr); + +int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode); + +int mwl_fwcmd_set_switch_channel(struct mwl_priv *priv, + struct ieee80211_channel_switch *ch_switch); + +int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, u8 encr_type); + +int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_encryption_set_tx_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr, + struct ieee80211_key_conf *key); + +int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + struct ieee80211_vif *vif); + +int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif); + +int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, + u8 idx); + +struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 tid); + +void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw, + struct ieee80211_sta *sta); + +int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, + struct mwl_ampdu_stream *stream); + +struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, + u8 *addr, u8 tid); + +bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid); + +int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level); + +int mwl_fwcmd_set_mimops_ht(struct ieee80211_hw *hw, u8 *addr, u8 smps_ctrl); + +int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data); + +int mwl_fwcmd_set_wfd_ie(struct ieee80211_hw *hw, u8 len, u8 *data); + +int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable); + +int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value); + +int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw); + +int mwl_fwcmd_reg_cau(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); + +int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, s32 *temp); + +int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw, + u32 *fw_region_code); + +int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw, + struct mwl_device_pwr_tbl *device_ch_pwrtbl, + u8 *region_code, + u8 *number_of_channels, + u32 channel_index); + +int mwl_fwcmd_get_fw_region_code_sc4(struct ieee80211_hw *hw, + u32 *fw_region_code); + +int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, + u8 action, u16 ch, u16 band, u16 width, u16 sub_ch); + +int mwl_fwcmd_getEDMAC(struct mwl_priv *priv, int * pEdmac); + +int mwl_fwcmd_get_pwr_tbl_sc4(struct ieee80211_hw *hw, + struct mwl_device_pwr_tbl *device_ch_pwrtbl, + u8 *region_code, + u8 *number_of_channels, + u32 channel_index); + +int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period, + u32 duration, u32 next_offset); + +int mwl_fwcmd_get_survey(struct ieee80211_hw *hw, int rstReg); +int mwl_fwcmd_set_slot_time(struct ieee80211_hw *hw, bool short_slot); +int mwl_fwcmd_dump_otp_data(struct ieee80211_hw *hw); + +int mwl_fwcmd_config_EDMACCtrl(struct ieee80211_hw *hw, int EDMAC_Ctrl); + +int mwl_fwcmd_set_pre_scan(struct ieee80211_hw *hw); +int mwl_fwcmd_set_post_scan(struct ieee80211_hw *hw); + +int mwl_fwcmd_get_region_mapping(struct ieee80211_hw *hw, + struct mwl_region_mapping *map); + +int lrd_fwcmd_mfg_start(struct ieee80211_hw *hw, u32 *data); +int lrd_fwcmd_mfg_write(struct ieee80211_hw *hw, void *data, int len); +int lrd_fwcmd_mfg_end(struct ieee80211_hw *hw); + +int lrd_fwcmd_lru_write(struct ieee80211_hw *hw, void* data, int len, void** rsp); +int lrd_fwcmd_lrd_write(struct ieee80211_hw *hw, void* data, int len, void** rsp); + +int lrd_fwcmd_lrd_get_caps(struct ieee80211_hw *hw, struct lrd_radio_caps* caps); +int lrd_fwcmd_lrd_set_ant_gain_adjust(struct ieee80211_hw *hw, u32 adjust); + +int lrd_fwcmd_lrd_set_power_table(struct ieee80211_hw *hw, u16 index, void *data, u32 len); +int lrd_fwcmd_lrd_get_power_table_result(struct ieee80211_hw *hw, u32 *result, u32 *pn); + +int lrd_fwcmd_lrd_reset_power_table(struct ieee80211_hw *hw); + + +#ifdef CONFIG_PM +int mwl_fwcmd_hostsleep_control(struct ieee80211_hw *hw, bool hs_enable, bool ds_enable, int wakeupCond); +int mwl_fwcmd_wowlan_apinrange_config(struct ieee80211_hw *hw); +#endif + +void mwl_hex_dump(const void *buf, size_t len); + +int mwl_fwcmd_set_monitor_mode(struct ieee80211_hw *hw, bool enable); + +int mwl_fwcmd_enter_deepsleep (struct ieee80211_hw *hw); +int mwl_fwcmd_exit_deepsleep (struct ieee80211_hw *hw); +int mwl_fwcmd_confirm_ps (struct ieee80211_hw *hw); +#endif /* _FWCMD_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/hostcmd.h b/drivers/net/wireless/laird/lrdmwl/hostcmd.h new file mode 100644 index 0000000000000..2d8e408b235b2 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/hostcmd.h @@ -0,0 +1,1272 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines firmware host command related + * structure. + */ + +#ifndef _HOSTCMD_H_ +#define _HOSTCMD_H_ + +/* 16 bit host command code */ +#define HOSTCMD_CMD_GET_HW_SPEC 0x0003 +#define HOSTCMD_CMD_SET_HW_SPEC 0x0004 +#define HOSTCMD_CMD_802_11_GET_STAT 0x0014 +#define HOSTCMD_CMD_MAC_REG_ACCESS 0x0019 +#define HOSTCMD_CMD_BBP_REG_ACCESS 0x001a +#define HOSTCMD_CMD_RF_REG_ACCESS 0x001b +#define HOSTCMD_CMD_802_11_RADIO_CONTROL 0x001c +#define HOSTCMD_CMD_MEM_ADDR_ACCESS 0x001d +#define HOSTCMD_CMD_802_11_TX_POWER 0x001f +#define HOSTCMD_CMD_802_11_RF_ANTENNA 0x0020 +#define HOSTCMD_CMD_802_11_PS_MODE 0x0021 +#define HOSTCMD_CMD_802_11_RF_ANTENNA_V2 0x0022 +#define HOSTCMD_CMD_BROADCAST_SSID_ENABLE 0x0050 /* per-vif */ +#define HOSTCMD_CMD_MFG 0x0089 +#define HOSTCMD_CMD_SET_CFG 0x008f + +#define HOSTCMD_LRD_CMD 0x00fd +#define HOSTCMD_LRD_MFG 0x00fe +#define HOSTCMD_LRD_REGION_MAPPING 0x00ff + +#define HOSTCMD_CMD_SET_PRE_SCAN 0x0107 +#define HOSTCMD_CMD_SET_POST_SCAN 0x0108 + +#define HOSTCMD_CMD_SET_RF_CHANNEL 0x010a +#define HOSTCMD_CMD_SET_AID 0x010d /* per-vif */ +#define HOSTCMD_CMD_SET_INFRA_MODE 0x010e /* per-vif */ +#define HOSTCMD_CMD_802_11_RTS_THSD 0x0113 +#define HOSTCMD_CMD_SET_EDCA_PARAMS 0x0115 +#define HOSTCMD_CMD_802_11H_DETECT_RADAR 0x0120 +#define HOSTCMD_CMD_SET_WMM_MODE 0x0123 +#define HOSTCMD_CMD_HT_GUARD_INTERVAL 0x0124 +#define HOSTCMD_CMD_SET_FIXED_RATE 0x0126 +#define HOSTCMD_CMD_SET_IES 0x0127 +#define HOSTCMD_CMD_SET_LINKADAPT_CS_MODE 0x0129 +#define HOSTCMD_CMD_DUMP_OTP_DATA 0x0142 +#define HOSTCMD_CMD_SET_MAC_ADDR 0x0202 /* per-vif */ +#define HOSTCMD_CMD_SET_RATE_ADAPT_MODE 0x0203 +#define HOSTCMD_CMD_GET_WATCHDOG_BITMAP 0x0205 +#define HOSTCMD_CMD_DEL_MAC_ADDR 0x0206 /* per-vif */ +#define HOSTCMD_CMD_BSS_START 0x1100 /* per-vif */ +#define HOSTCMD_CMD_AP_BEACON 0x1101 /* per-vif */ +#define HOSTCMD_CMD_SET_NEW_STN 0x1111 /* per-vif */ +#define HOSTCMD_CMD_SET_APMODE 0x1114 +#define HOSTCMD_CMD_SET_SWITCH_CHANNEL 0x1121 +#define HOSTCMD_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */ +#define HOSTCMD_CMD_BASTREAM 0x1125 +#define HOSTCMD_CMD_SET_SPECTRUM_MGMT 0x1128 +#define HOSTCMD_CMD_SET_POWER_CONSTRAINT 0x1129 +#define HOSTCMD_CMD_SET_COUNTRY_CODE 0x1130 +#define HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL 0x1133 +#define HOSTCMD_CMD_SET_MIMOPSHT 0x1135 +#define HOSTCMD_CMD_SET_WSC_IE 0x1136 /* per-vif */ +#define HOSTCMD_CMD_DWDS_ENABLE 0x1144 +#define HOSTCMD_CMD_FW_FLUSH_TIMER 0x1148 +#define HOSTCMD_CMD_SET_CDD 0x1150 +#define HOSTCMD_CMD_CAU_REG_ACCESS 0x1157 +#define HOSTCMD_CMD_GET_TEMP 0x1159 +#define HOSTCMD_CMD_GET_FW_REGION_CODE 0x116A +#define HOSTCMD_CMD_GET_DEVICE_PWR_TBL 0x116B +#define HOSTCMD_CMD_GET_FW_REGION_CODE_SC4 0x118A +#define HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4 0x118B +#define HOSTCMD_CMD_QUIET_MODE 0x1201 +#define HOSTCMD_CMD_SET_WFD_IE 0x1202 +#define HOSTCMD_CMD_802_11_SLOT_TIME 0x1203 +#define HOSTCMD_CMD_EDMAC_CTRL 0x1204 +#define HOSTCMD_CMD_HOSTSLEEP_CTRL 0x1205 +#define HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG 0x1206 +#define HOSTCMD_CMD_MONITOR_MODE 0x1207 +#define HOSTCMD_CMD_DEEPSLEEP 0x1209 +#define HOSTCMD_CMD_CONFIRM_PS 0x1210 +#define HOSTCMD_CMD_IBSS_START 0x1212 + + +/* Define general result code for each command */ +#define HOSTCMD_RESULT_OK 0x0000 +/* General error */ +#define HOSTCMD_RESULT_ERROR 0x0001 +/* Command is not valid */ +#define HOSTCMD_RESULT_NOT_SUPPORT 0x0002 +/* Command is pending (will be processed) */ +#define HOSTCMD_RESULT_PENDING 0x0003 +/* System is busy (command ignored) */ +#define HOSTCMD_RESULT_BUSY 0x0004 +/* Data buffer is not big enough */ +#define HOSTCMD_RESULT_PARTIAL_DATA 0x0005 + +/* Define channel related constants */ +#define FREQ_BAND_2DOT4GHZ 0x1 +#define FREQ_BAND_4DOT9GHZ 0x2 +#define FREQ_BAND_5GHZ 0x4 +#define FREQ_BAND_5DOT2GHZ 0x8 +#define CH_AUTO_WIDTH 0 +#define CH_10_MHZ_WIDTH 0x1 +#define CH_20_MHZ_WIDTH 0x2 +#define CH_40_MHZ_WIDTH 0x4 +#define CH_80_MHZ_WIDTH 0x5 +#define CH_160_MHZ_WIDTH 0x6 +#define EXT_CH_ABOVE_CTRL_CH 0x1 +#define EXT_CH_AUTO 0x2 +#define EXT_CH_BELOW_CTRL_CH 0x3 +#define NO_EXT_CHANNEL 0x0 + +#define ACT_PRIMARY_CHAN_0 0 +#define ACT_PRIMARY_CHAN_1 1 +#define ACT_PRIMARY_CHAN_2 2 +#define ACT_PRIMARY_CHAN_3 3 +#define ACT_PRIMARY_CHAN_4 4 +#define ACT_PRIMARY_CHAN_5 5 +#define ACT_PRIMARY_CHAN_6 6 +#define ACT_PRIMARY_CHAN_7 7 + +/* Define rate related constants */ +#define HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 + +/* Define station related constants */ +#define HOSTCMD_ACT_STA_ACTION_ADD 0 +#define HOSTCMD_ACT_STA_ACTION_REMOVE 2 + +/* Define key related constants */ +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +#define KEY_TYPE_ID_WEP 0x00 +#define KEY_TYPE_ID_TKIP 0x01 +#define KEY_TYPE_ID_AES 0x02 + +/* Group key for RX only */ +#define ENCR_KEY_FLAG_RXGROUPKEY 0x00000002 +#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 +#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 +#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 +#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 +#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 + +/* Define block ack related constants */ +#define BASTREAM_FLAG_IMMEDIATE_TYPE 1 +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0 + +/* Define general purpose action */ +#define HOSTCMD_ACT_GEN_SET 0x0001 +#define HOSTCMD_ACT_GEN_SET_LIST 0x0002 +#define HOSTCMD_ACT_GEN_GET_LIST 0x0003 + +/* Define TXPower control action*/ +#define HOSTCMD_ACT_GET_TARGET_TX_PWR 0x0000 +#define HOSTCMD_ACT_GET_MAX_TX_PWR 0x0001 +#define HOSTCMD_ACT_SET_TARGET_TX_PWR 0x0002 +#define HOSTCMD_ACT_SET_MAX_TX_PWR 0x0003 + +/* Misc */ +#define WSC_IE_MAX_LENGTH 251 +#define WSC_IE_SET_BEACON 0 +#define WSC_IE_SET_PROBE_RESPONSE 1 + +/* Allocating space only for single NOA descriptor */ +#define WFD_IE_MAX_LENGTH 38 +#define WFD_IE_SET_BEACON 0 +#define WFD_IE_SET_PROBE_RESPONSE 1 + +#define HW_SET_PARMS_FEATURES_HOST_PROBE_RESP 0x00000020 +#define HW_SET_PARMS_FEATURES_HOST_ENCRDECRMGT 0x00000080 + +#define EDMAC_2G_ENABLE_MASK 0x00000001 +#define EDMAC_2G_ENABLE_SHIFT 0x0 +#define EDMAC_5G_ENABLE_MASK 0x00000002 +#define EDMAC_5G_ENABLE_SHIFT 0x1 +#define EDMAC_2G_THRESHOLD_OFFSET_MASK 0x00000FF0 +#define EDMAC_2G_THRESHOLD_OFFSET_SHIFT 0x4 +#define EDMAC_5G_THRESHOLD_OFFSET_MASK 0x000FF000 +#define EDMAC_5G_THRESHOLD_OFFSET_SHIFT 0xC +#define EDMAC_QLOCK_BITMAP_MASK 0x3FF00000 +#define EDMAC_QLOCK_BITMAP_SHIFT 0x14 + +#define WOWLAN_WAKE_BITMAP_LINK_LOST 0x0001 +#define WOWLAN_WAKE_BITMAP_AP_INRANGE 0x0002 +#define WOWLAN_WAKE_BITMAP_RX_UCAST_DATA 0x0100 +#define WOWLAN_WAKE_BITMAP_RX_UCAST_DATA_ANY 0x0200 +#define WOWLAN_WAKE_BITMAP_PATTERN_MATCH 0x0400 +#define WOWLAN_WAKE_BITMAP_MAGIC_PACKET 0x0800 + +#define WOWLAN_WAKEUP_GAP_CFG 1000 /*msec*/ +#define WOWLAN_WAKEUP_SIGNAL_TYPE_HIGH 0x1 +#define WOWLAN_WAKEUP_SIGNAL_TYPE_LOW 0x0 + +#define ANT_GAIN_VALID_MASK 0x000FFFFF + +enum { + WL_DISABLE = 0, + WL_ENABLE = 1, + WL_DISABLE_VMAC = 0x80, +}; + +enum { + WL_GET = 0, + WL_SET = 1, + WL_RESET = 2, +}; + +enum { + WL_LONG_PREAMBLE = 1, + WL_SHORT_PREAMBLE = 3, + WL_AUTO_PREAMBLE = 5, +}; + +enum encr_action_type { + /* request to enable/disable HW encryption */ + ENCR_ACTION_ENABLE_HW_ENCR, + /* request to set encryption key */ + ENCR_ACTION_TYPE_SET_KEY, + /* request to remove one or more keys */ + ENCR_ACTION_TYPE_REMOVE_KEY, + ENCR_ACTION_TYPE_SET_GROUP_KEY, +}; + +enum ba_action_type { + BA_CREATE_STREAM, + BA_UPDATE_STREAM, + BA_DESTROY_STREAM, + BA_FLUSH_STREAM, + BA_CHECK_STREAM, +}; + +enum mac_type { + WL_MAC_TYPE_PRIMARY_CLIENT, + WL_MAC_TYPE_SECONDARY_CLIENT, + WL_MAC_TYPE_PRIMARY_AP, + WL_MAC_TYPE_SECONDARY_AP, +}; + +enum mfg_action_type { + MFG_TYPE_START, + MFG_TYPE_WRITE, + MFG_TYPE_END, +}; + +/* General host command header */ +struct hostcmd_header { + __le16 cmd; + __le16 len; + u8 seq_num; + u8 macid; + __le16 result; +} __packed; + +#define WCB_BASE_COUNT 20 +/* HOSTCMD_CMD_GET_HW_SPEC */ +struct hostcmd_cmd_get_hw_spec { + struct hostcmd_header cmd_hdr; + u8 version; /* version of the HW */ + u8 host_if; /* host interface */ + __le16 num_wcb; /* Max. number of WCB FW can handle */ + __le16 num_mcast_addr; /* MaxNbr of MC addresses FW can handle */ + u8 permanent_addr[ETH_ALEN]; /* MAC address programmed in HW */ + __le16 region_code; + __le16 num_antenna; /* Number of antenna used */ + __le32 fw_release_num; /* 4 byte of FW release number */ + __le32 wcb_base0; + __le32 rxpd_wr_ptr; + __le32 rxpd_rd_ptr; + __le32 fw_awake_cookie; + __le32 wcb_base[WCB_BASE_COUNT - 1]; +} __packed; + +/* HOSTCMD_CMD_SET_HW_SPEC */ +struct hostcmd_cmd_set_hw_spec { + struct hostcmd_header cmd_hdr; + /* HW revision */ + u8 version; + /* Host interface */ + u8 host_if; + /* Max. number of Multicast address FW can handle */ + __le16 num_mcast_addr; + /* MAC address */ + u8 permanent_addr[ETH_ALEN]; + /* Region Code */ + __le16 region_code; + /* 4 byte of FW release number, example 0x1234=1.2.3.4 */ + __le32 fw_release_num; + /* Firmware awake cookie - used to ensure that the device + * is not in sleep mode + */ + __le32 fw_awake_cookie; + /* Device capabilities (see above) */ + __le32 device_caps; + /* Rx shared memory queue */ + __le32 rxpd_wr_ptr; + /* Actual number of TX queues in WcbBase array */ + __le32 num_tx_queues; + /* TX WCB Rings */ + __le32 wcb_base[WCB_BASE_COUNT]; + /* Max AMSDU size (00 - AMSDU Disabled, + * 01 - 4K, 10 - 8K, 11 - not defined) + */ + __le32 features; + __le32 tx_wcb_num_per_queue; + __le32 total_rx_wcb; +} __packed; + +/* HOSTCMD_CMD_SET_CFG */ +struct hostcmd_cmd_set_cfg { + struct hostcmd_header cmd_hdr; + /* Action */ + __le16 action; + /* Type */ + __le16 type; + /* Data length */ + __le16 data_len; + /* Data */ + u8 data[1]; +} __packed; + +/* HOSTCMD_CMD_802_11_GET_STAT */ +struct hostcmd_cmd_802_11_get_stat { + struct hostcmd_header cmd_hdr; + __le32 tx_retry_successes; + __le32 tx_multiple_retry_successes; + __le32 tx_failures; + __le32 rts_successes; + __le32 rts_failures; + __le32 ack_failures; + __le32 rx_duplicate_frames; + __le32 rx_fcs_errors; + __le32 tx_watchdog_timeouts; + __le32 rx_overflows; + __le32 rx_frag_errors; + __le32 rx_mem_errors; + __le32 pointer_errors; + __le32 tx_underflows; + __le32 tx_done; + __le32 tx_done_buf_try_put; + __le32 tx_done_buf_put; + /* Put size of requested buffer in here */ + __le32 wait_for_tx_buf; + __le32 tx_attempts; + __le32 tx_successes; + __le32 tx_fragments; + __le32 tx_multicasts; + __le32 rx_non_ctl_pkts; + __le32 rx_multicasts; + __le32 rx_undecryptable_frames; + __le32 rx_icv_errors; + __le32 rx_excluded_frames; + __le32 rx_weak_iv_count; + __le32 rx_unicasts; + __le32 rx_bytes; + __le32 rx_errors; + __le32 rx_rts_count; + __le32 tx_cts_count; +} __packed; + +/* HOSTCMD_CMD_MAC_REG_ACCESS */ +struct hostcmd_cmd_mac_reg_access { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 offset; + u32 value; +} __packed; + +/* HOSTCMD_CMD_BBP_REG_ACCESS */ +struct hostcmd_cmd_bbp_reg_access { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 offset; + u8 value; + u8 reserverd[3]; +} __packed; + +/* HOSTCMD_CMD_RF_REG_ACCESS */ +struct hostcmd_cmd_rf_reg_access { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 offset; + u8 value; + u8 reserverd[3]; +} __packed; + +/* HOSTCMD_CMD_802_11_RADIO_CONTROL */ +struct hostcmd_cmd_802_11_radio_control { + struct hostcmd_header cmd_hdr; + __le16 action; + /* @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix */ + __le16 control; + __le16 radio_on; +} __packed; + +/* HOSTCMD_CMD_802_11_SHORT_SLOT */ +struct hostcmd_cmd_802_11_slot_time { + struct hostcmd_header cmd_hdr; + __le16 action; + /* 0:long slot; 1:short slot */ + __le16 short_slot; +} __packed; + +/* HOSTCMD_CMD_EDMAC_CTRL */ +struct hostcmd_cmd_edmac_ctrl { + struct hostcmd_header cmd_hdr; + __le16 action; + u8 ed_ctrl_2g; + s8 ed_offset_2g; + u8 ed_ctrl_5g; + s8 ed_offset_5g; + __le16 ed_bitmap_txq_lock; +} __packed; + +/* HOSTCMD_CMD_MEM_ADDR_ACCESS */ +struct hostcmd_cmd_mem_addr_access { + struct hostcmd_header cmd_hdr; + __le32 address; + __le16 length; + __le16 reserved; + __le32 value[64]; +} __packed; + +/* HOSTCMD_CMD_802_11_TX_POWER */ +struct hostcmd_cmd_802_11_tx_power { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 band; + __le16 ch; + __le16 bw; + __le16 sub_ch; + __le16 power_level_list[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; +} __packed; + +/* HOSTCMD_CMD_802_11_RF_ANTENNA */ +struct hostcmd_cmd_802_11_rf_antenna_v2 { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 ant_tx_bmp; /* Number of antennas or 0xffff(diversity) */ + __le16 ant_rx_bmp; /* Number of antennas or 0xffff(diversity) */ +} __packed; + +//Note this structure is packed mwl_wowlan equivalent is not +struct wowlan_apinrange_addrIe { + u8 address[ETH_ALEN]; +} __packed; + +//Note This structure is packed mwl_wowlan_ equivalent is not +struct wowlan_apinrange_ssidIe { + u8 ssidLen; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}__packed; + +/* HOSTCMD_CMD_HOSTSLEEP_CTRL */ +struct hostcmd_cmd_hostsleep_ctrl { + struct hostcmd_header cmd_hdr; + u8 HS_enable:1; /**/ + u8 DS_enable:1; /**/ + u8 reserved:6; /**/ + u8 wakeupSignal; /*1: active high 0: active low */ + __le16 gap; /* Time in ms Fw needs to wait before sending Evnts or frames */ + __le32 wakeUpConditions; + __le32 options; /* For debug purpose Only*/ +} __packed; + +/* HOSTCMD_CMD_WOWLAN_AP_INRANGE_CFG */ +struct hostcmd_cmd_wowlan_ap_inrange_cfg { + struct hostcmd_header cmd_hdr; + __le16 chanListCnt; + __le16 addrIeList_Len; + __le16 ssidIeList_Len; + u8 chanList[SYSADPT_MAX_NUM_CHANNELS]; + struct wowlan_apinrange_addrIe addrIeList; + struct wowlan_apinrange_ssidIe ssidIeList; +} __packed; + +/* HOSTCMD_CMD_802_11_PS_MODE */ +struct hostcmd_cmd_802_11_ps_mode { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 powermode; /* PowerSave Enable/Disable */ +} __packed; + +/* HOSTCMD_CMD_BROADCAST_SSID_ENABLE */ +struct hostcmd_cmd_broadcast_ssid_enable { + struct hostcmd_header cmd_hdr; + __le32 enable; + /* Hidden SSID Options- + ** 0: Do not Hide + ** 1: Hide SSID by using zero len SSID element + ** 2: Hide SSID by using correct len in SSID element but zero out complete SSID + */ + __le32 hidden_ssid_info; +} __packed; + +/* HOSTCMD_CMD_SET_RF_CHANNEL */ +#define FREQ_BAND_MASK 0x0000003f +#define CHNL_WIDTH_MASK 0x000007c0 +#define CHNL_WIDTH_SHIFT 6 +#define ACT_PRIMARY_MASK 0x00003800 +#define ACT_PRIMARY_SHIFT 11 + +struct hostcmd_cmd_set_rf_channel { + struct hostcmd_header cmd_hdr; + __le16 action; + u8 curr_chnl; + __le32 chnl_flags; + u8 remain_on_chan; +} __packed; + +/* HOSTCMD_CMD_SET_AID */ +struct hostcmd_cmd_set_aid { + struct hostcmd_header cmd_hdr; + __le16 aid; + u8 mac_addr[ETH_ALEN]; /* AP's Mac Address(BSSID) */ + __le32 gprotect; + u8 ap_rates[SYSADPT_MAX_DATA_RATES_G]; +} __packed; + +/* HOSTCMD_CMD_SET_INFRA_MODE */ +struct hostcmd_cmd_set_infra_mode { + struct hostcmd_header cmd_hdr; +} __packed; + +/* HOSTCMD_CMD_802_11_RTS_THSD */ +struct hostcmd_cmd_802_11_rts_thsd { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 threshold; +} __packed; + +/* HOSTCMD_CMD_SET_EDCA_PARAMS */ +struct hostcmd_cmd_set_edca_params { + struct hostcmd_header cmd_hdr; + /* 0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN */ + __le16 action; + __le16 txop; /* in unit of 32 us */ + __le32 cw_max; /* 0~15 */ + __le32 cw_min; /* 0~15 */ + u8 aifsn; + u8 txq_num; /* Tx Queue number. */ +} __packed; + +/* HOSTCMD_CMD_802_11H_DETECT_RADAR */ +#define RADAR_TYPE_CODE_0 0 +#define RADAR_TYPE_CODE_53 53 +#define RADAR_TYPE_CODE_56 56 +#define RADAR_TYPE_CODE_ETSI 151 + +struct hostcmd_cmd_802_11h_detect_radar { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 radar_type_code; + __le16 min_chirp_cnt; + __le16 chirp_time_intvl; + __le16 pw_filter; + __le16 min_num_radar; + __le16 pri_min_num; +} __packed; + +/* HOSTCMD_CMD_SET_WMM_MODE */ +struct hostcmd_cmd_set_wmm_mode { + struct hostcmd_header cmd_hdr; + __le16 action; /* 0->unset, 1->set */ +} __packed; + +/* HOSTCMD_CMD_HT_GUARD_INTERVAL */ +struct hostcmd_cmd_ht_guard_interval { + struct hostcmd_header cmd_hdr; + __le32 action; + __le32 gi_type; +} __packed; + +/* HOSTCMD_CMD_SET_FIXED_RATE */ +struct fix_rate_flag { /* lower rate after the retry count */ + /* 0: legacy, 1: HT */ + __le32 fix_rate_type; + /* 0: retry count is not valid, 1: use retry count specified */ + __le32 retry_count_valid; +} __packed; + +struct fix_rate_entry { + struct fix_rate_flag fix_rate_type_flags; + /* depending on the flags above, this can be either a legacy + * rate(not index) or an MCS code. + */ + __le32 fixed_rate; + __le32 retry_count; +} __packed; + +struct hostcmd_cmd_set_fixed_rate { + struct hostcmd_header cmd_hdr; + /* HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 */ + __le32 action; + /* use fixed rate specified but firmware can drop to */ + __le32 allow_rate_drop; + __le32 entry_count; + struct fix_rate_entry fixed_rate_table[4]; + u8 multicast_rate; + u8 multi_rate_tx_type; + u8 management_rate; +} __packed; + +/* HOSTCMD_CMD_SET_IES */ +struct hostcmd_cmd_set_ies { + struct hostcmd_header cmd_hdr; + __le16 action; /* 0->unset, 1->set */ + __le16 ie_list_len_ht; + __le16 ie_list_len_vht; + __le16 ie_list_len_proprietary; + __le16 ie_list_len_11k; + /*Buffer size same as Generic_Beacon*/ + u8 ie_list_ht[148]; + u8 ie_list_vht[24]; + u8 ie_list_proprietary[112]; + u8 ie_list_11k[106]; +} __packed; + +/* HOSTCMD_CMD_SET_LINKADAPT_CS_MODE */ +struct hostcmd_cmd_set_linkadapt_cs_mode { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 cs_mode; +} __packed; + +/* HOSTCMD_CMD_SET_MAC_ADDR, HOSTCMD_CMD_DEL_MAC_ADDR */ +struct hostcmd_cmd_set_mac_addr { + struct hostcmd_header cmd_hdr; + __le16 mac_type; + u8 mac_addr[ETH_ALEN]; +} __packed; + +/* HOSTCMD_CMD_SET_RATE_ADAPT_MODE */ +struct hostcmd_cmd_set_rate_adapt_mode { + struct hostcmd_header cmd_hdr; + __le16 action; + __le16 rate_adapt_mode; /* 0:Indoor, 1:Outdoor */ +} __packed; + +/* HOSTCMD_CMD_GET_WATCHDOG_BITMAP */ +struct hostcmd_cmd_get_watchdog_bitmap { + struct hostcmd_header cmd_hdr; + u8 watchdog_bitmap; /* for SW/BA */ +} __packed; + +/* HOSTCMD_CMD_BSS_START */ +struct hostcmd_cmd_bss_start { + struct hostcmd_header cmd_hdr; + __le32 enable; /* FALSE: Disable or TRUE: Enable */ +} __packed; + +/* HOSTCMD_CMD_AP_BEACON */ +struct cf_params { + u8 elem_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + __le16 cfp_max_duration; + __le16 cfp_duration_remaining; +} __packed; + +struct ibss_params { + u8 elem_id; + u8 len; + __le16 atim_window; +} __packed; + +union ss_params { + struct cf_params cf_param_set; + struct ibss_params ibss_param_set; +} __packed; + +struct fh_params { + u8 elem_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ds_params { + u8 elem_id; + u8 len; + u8 current_chnl; +} __packed; + +union phy_params { + struct fh_params fh_param_set; + struct ds_params ds_param_set; +} __packed; + +struct rsn_ie { + u8 elem_id; + u8 len; + u8 oui_type[4]; /* 00:50:f2:01 */ + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; +} __packed; + +struct rsn48_ie { + u8 elem_id; + u8 len; + u8 ver[2]; + u8 grp_key_cipher[4]; + u8 pws_key_cnt[2]; + u8 pws_key_cipher_list[4]; + u8 auth_key_cnt[2]; + u8 auth_key_list[4]; + u8 rsn_cap[2]; + u8 pmk_id_cnt[2]; + u8 pmk_id_list[16]; /* Should modify to 16 * S */ + u8 reserved[8]; +} __packed; + +struct ac_param_rcd { + u8 aci_aifsn; + u8 ecw_min_max; + __le16 txop_lim; +} __packed; + +struct wmm_param_elem { + u8 elem_id; + u8 len; + u8 oui[3]; + u8 type; + u8 sub_type; + u8 version; + // Adding QosInfo to make sure FW and Driver sees same structure + u8 qos_info; + u8 rsvd; + struct ac_param_rcd ac_be; + struct ac_param_rcd ac_bk; + struct ac_param_rcd ac_vi; + struct ac_param_rcd ac_vo; +} __packed; + +struct channel_info { + u8 first_channel_num; + u8 num_channels; + u8 max_tx_pwr_level; +} __packed; + +struct country { + u8 elem_id; + u8 len; + u8 country_str[3]; + struct channel_info channel_info[40]; +} __packed; + +struct power_constraint_ie { + u8 elem_id; + u8 len; + u8 local_constraint; +} __packed; + +struct start_cmd { + u8 sta_mac_addr[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bss_type; + __le16 bcn_period; + u8 dtim_period; + union ss_params ss_param_set; + union phy_params phy_param_set; + __le16 probe_delay; + __le16 cap_info; + u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; + u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; + __le16 offset_StaRsnIE; + __le16 offset_StaRsnIE48; + + struct wmm_param_elem wmm_param; + struct country country; + __le32 ap_rf_type; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ + struct power_constraint_ie power_constraint; + u8 Bssid[ETH_ALEN]; + + u8 rsn_data[102]; +} __packed; + +struct hostcmd_cmd_ap_beacon { + struct hostcmd_header cmd_hdr; + struct start_cmd start_cmd; +} __packed; + +/* HOSTCMD_CMD_SET_NEW_STN */ +struct add_ht_info { + u8 control_chnl; + u8 add_chnl; + __le16 op_mode; + __le16 stbc; +} __packed; + +struct peer_info { + __le32 legacy_rate_bitmap; + u8 ht_rates[4]; + __le16 cap_info; + __le16 ht_cap_info; + u8 mac_ht_param_info; + u8 mrvl_sta; + struct add_ht_info add_ht_info; + __le32 tx_bf_capabilities; /* EXBF_SUPPORT */ + __le32 vht_max_rx_mcs; + __le32 vht_cap; + /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */ + u8 vht_rx_channel_width; +} __packed; + +struct hostcmd_cmd_set_new_stn { + struct hostcmd_header cmd_hdr; + __le16 aid; + u8 mac_addr[ETH_ALEN]; + __le16 stn_id; + __le16 action; + __le16 if_type; + struct peer_info peer_info; + /* UAPSD_SUPPORT */ + u8 qos_info; + u8 is_qos_sta; + __le32 fw_sta_ptr; +} __packed; + +/* HOSTCMD_CMD_SET_APMODE */ +struct hostcmd_cmd_set_apmode { + struct hostcmd_header cmd_hdr; + u8 apmode; +} __packed; + +/* HOSTCMD_CMD_SET_SWITCH_CHANNEL */ +struct hostcmd_cmd_set_switch_channel { + struct hostcmd_header cmd_hdr; + __le32 next_11h_chnl; + __le32 mode; + __le32 init_count; + __le32 chnl_flags; + __le32 next_ht_extchnl_offset; + __le32 dfs_test_mode; +} __packed; + +/* HOSTCMD_CMD_UPDATE_ENCRYPTION */ +struct hostcmd_cmd_update_encryption { + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + __le32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + __le32 data_length; + u8 mac_addr[ETH_ALEN]; + u8 action_data[1]; +} __packed; + +struct wep_type_key { + /* WEP key material (max 128bit) */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; +} __packed; + +struct encr_tkip_seqcnt { + __le16 low; + __le32 high; +} __packed; + +struct tkip_type_key { + /* TKIP Key material. Key type (group or pairwise key) is + * determined by flags + */ + /* in KEY_PARAM_SET structure. */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; + /* MIC keys */ + u8 tkip_tx_mic_key[MIC_KEY_LENGTH]; + u8 tkip_rx_mic_key[MIC_KEY_LENGTH]; + struct encr_tkip_seqcnt tkip_rsc; + struct encr_tkip_seqcnt tkip_tsc; +} __packed; + +struct aes_type_key { + /* AES Key material */ + u8 key_material[MAX_ENCR_KEY_LENGTH]; +} __packed; + +union mwl_key_type { + struct wep_type_key wep_key; + struct tkip_type_key tkip_key; + struct aes_type_key aes_key; +} __packed; + +struct key_param_set { + /* Total length of this structure (Key is variable size array) */ + __le16 length; + /* Key type - WEP, TKIP or AES-CCMP. */ + /* See definitions above */ + __le16 key_type_id; + /* key flags (ENCR_KEY_FLAG_XXX_ */ + __le32 key_info; + /* For WEP only - actual key index */ + __le32 key_index; + /* Size of the key */ + __le16 key_len; + /* Key material (variable size array) */ + union mwl_key_type key; + u8 mac_addr[ETH_ALEN]; + __le32 cfg_flags; +} __packed; + +struct hostcmd_cmd_set_key { + struct hostcmd_header cmd_hdr; + /* Action type - see encr_action_type */ + __le32 action_type; /* encr_action_type */ + /* size of the data buffer attached. */ + __le32 data_length; + /* data buffer - maps to one KEY_PARAM_SET structure */ + struct key_param_set key_param; +} __packed; + +/* HOSTCMD_CMD_BASTREAM */ +#define BA_TYPE_MASK 0x00000001 +#define BA_DIRECTION_MASK 0x00000006 +#define BA_DIRECTION_SHIFT 1 + +struct ba_context { + __le32 context; +} __packed; + +/* parameters for block ack creation */ +struct create_ba_params { + /* BA Creation flags - see above */ + __le32 flags; + /* idle threshold */ + __le32 idle_thrs; + /* block ack transmit threshold (after how many pkts should we + * send BAR?) + */ + __le32 bar_thrs; + /* receiver window size */ + __le32 window_size; + /* MAC Address of the BA partner */ + u8 peer_mac_addr[ETH_ALEN]; + /* Dialog Token */ + u8 dialog_token; + /* TID for the traffic stream in this BA */ + u8 tid; + /* shared memory queue ID (not sure if this is required) */ + u8 queue_id; + u8 param_info; + /* returned by firmware - firmware context pointer. */ + /* this context pointer will be passed to firmware for all + * future commands. + */ + struct ba_context fw_ba_context; + u8 reset_seq_no; /** 0 or 1**/ + __le16 current_seq; + /* This is for virtual station in Sta proxy mode for V6FW */ + u8 sta_src_mac_addr[ETH_ALEN]; +} __packed; + +/* new transmit sequence number information */ +struct ba_update_seq_num { + /* BA flags - see above */ + __le32 flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; + /* new sequence number for this block ack stream */ + __le16 ba_seq_num; +} __packed; + +struct ba_stream_context { + /* BA Stream flags */ + __le32 flags; + /* returned by firmware in the create ba stream response */ + struct ba_context fw_ba_context; +} __packed; + +union ba_info { + /* information required to create BA Stream... */ + struct create_ba_params create_params; + /* update starting/new sequence number etc. */ + struct ba_update_seq_num updt_seq_num; + /* destroy an existing stream... */ + struct ba_stream_context destroy_params; + /* destroy an existing stream... */ + struct ba_stream_context flush_params; +} __packed; + +struct hostcmd_cmd_bastream { + struct hostcmd_header cmd_hdr; + __le32 action_type; + union ba_info ba_info; +} __packed; + +/* HOSTCMD_CMD_SET_SPECTRUM_MGMT */ +struct hostcmd_cmd_set_spectrum_mgmt { + struct hostcmd_header cmd_hdr; + __le32 spectrum_mgmt; +} __packed; + +/* HOSTCMD_CMD_SET_POWER_CONSTRAINT */ +struct hostcmd_cmd_set_power_constraint { + struct hostcmd_header cmd_hdr; + __le32 power_constraint; +} __packed; + +/* HOSTCMD_CMD_SET_COUNTRY_CODE */ +struct domain_chnl_entry { + u8 first_chnl_num; + u8 chnl_num; + u8 max_transmit_pw; +} __packed; + +struct domain_country_info { + u8 country_string[3]; + u8 g_chnl_len; + struct domain_chnl_entry domain_entry_g[1]; + u8 a_chnl_len; + struct domain_chnl_entry domain_entry_a[20]; +} __packed; + +struct hostcmd_cmd_set_country_code { + struct hostcmd_header cmd_hdr; + __le32 action ; /* 0 -> unset, 1 ->set */ + struct domain_country_info domain_info; +} __packed; + +/* HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL */ +struct hostcmd_cmd_set_optimization_level { + struct hostcmd_header cmd_hdr; + u8 opt_level; +} __packed; + +/* HOSTCMD_CMD_SET_MIMOPSHT */ +struct hostcmd_cmd_set_mimops_ht { + struct hostcmd_header cmd_hdr; + u8 addr[ETH_ALEN]; + u8 enbl; + u8 mode; +} __packed; + +/* HOSTCMD_CMD_SET_WSC_IE */ +struct hostcmd_cmd_set_wsc_ie { + struct hostcmd_header cmd_hdr; + __le16 ie_type; /* 0 -- beacon. or 1 -- probe response. */ + __le16 len; + u8 data[WSC_IE_MAX_LENGTH]; +} __packed; + +/* HOSTCMD_CMD_SET_WFD_IE */ +struct hostcmd_cmd_set_wfd_ie { + struct hostcmd_header cmd_hdr; + __le16 ie_type; /* 0 -- beacon. or 1 -- probe response. */ + __le16 len; + u8 data[WFD_IE_MAX_LENGTH]; +} __packed; + +/* HOSTCMD_CMD_DWDS_ENABLE */ +struct hostcmd_cmd_dwds_enable { + struct hostcmd_header cmd_hdr; + __le32 enable; /* 0 -- Disable. or 1 -- Enable. */ +} __packed; + +/* HOSTCMD_CMD_FW_FLUSH_TIMER */ +struct hostcmd_cmd_fw_flush_timer { + struct hostcmd_header cmd_hdr; + /* 0 -- Disable. > 0 -- holds time value in usecs. */ + __le32 value; +} __packed; + +/* HOSTCMD_CMD_SET_CDD */ +struct hostcmd_cmd_set_cdd { + struct hostcmd_header cmd_hdr; + __le32 enable; +} __packed; + +/* HOSTCMD_CMD_GET_TEMP */ +struct hostcmd_cmd_get_temp { + struct hostcmd_header cmd_hdr; + __le32 celcius; + __le32 raw_data; +} __packed; + +/* HOSTCMD_CMD_GET_FW_REGION_CODE */ +struct hostcmd_cmd_get_fw_region_code { + struct hostcmd_header cmd_hdr; + __le32 status; /* 0 = Found, 1 = Error */ + __le32 fw_region_code; +} __packed; + +/* HOSTCMD_CMD_GET_DEVICE_PWR_TBL */ +#define HAL_TRPC_ID_MAX 28 +#define HAL_TRPC_ID_2G_MAX 16 +#define HAL_TRPC_ID_5G_MAX 28 + +struct channel_power_tbl { + u8 channel; + u8 tx_pwr[HAL_TRPC_ID_MAX]; + u8 dfs_capable; + u8 ax_ant; + u8 cdd; +} __packed; + +struct hostcmd_cmd_get_device_pwr_tbl { + struct hostcmd_header cmd_hdr; + __le16 status; /* 0 = Found, 1 = Error */ + u8 region_code; + u8 number_of_channels; + __le32 current_channel_index; + /* Only for 1 channel, so, 1 channel at a time */ + struct channel_power_tbl channel_pwr_tbl; +} __packed; + +/* HOSTCMD_CMD_GET_FW_REGION_CODE_SC4 */ +struct hostcmd_cmd_get_fw_region_code_sc4 { + struct hostcmd_header cmd_hdr; + __le32 fw_region_code; +} __packed; + +/* HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4 */ +#define HAL_TRPC_ID_MAX_SC4 32 +#define MAX_GROUP_PER_CHANNEL_5G 39 +#define MAX_GROUP_PER_CHANNEL_2G 21 +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MAX_GROUP_PER_CHANNEL_RATE \ + MAX(MAX_GROUP_PER_CHANNEL_5G, MAX_GROUP_PER_CHANNEL_2G) + +struct channel_power_tbl_sc4 { + u8 channel; + u8 grp_pwr[MAX_GROUP_PER_CHANNEL_RATE]; + u8 tx_pwr[HAL_TRPC_ID_MAX_SC4]; + u8 dfs_capable; + u8 ax_ant; + u8 cdd; + u8 rsvd; +} __packed; + +struct hostcmd_cmd_get_device_pwr_tbl_sc4 { + struct hostcmd_header cmd_hdr; + __le16 status; /* 0 = Found, 1 = Error */ + u8 region_code; + u8 number_of_channels; + __le32 current_channel_index; + /* Only for 1 channel, so, 1 channel at a time */ + struct channel_power_tbl_sc4 channel_pwr_tbl; +} __packed; + +/* HOSTCMD_CMD_QUIET_MODE */ +struct hostcmd_cmd_quiet_mode { + struct hostcmd_header cmd_hdr; + __le16 action; + __le32 enable; + __le32 period; + __le32 duration; + __le32 next_offset; +} __packed; + +struct hostcmd_cmd_dump_otp_data { + struct hostcmd_header cmd_hdr; + u8 pload[0]; +} __packed; + +struct hostcmd_cmd_pre_scan { + struct hostcmd_header cmd_hdr; + u8 pload[0]; +} __packed; + +struct hostcmd_cmd_post_scan { + struct hostcmd_header cmd_hdr; + u8 pload[0]; +} __packed; + +struct hostcmd_cmd_monitor_mode { + struct hostcmd_header cmd_hdr; + u8 enableFlag; +} __packed; + +struct hostcmd_cmd_region_mapping +{ + struct hostcmd_header cmd_hdr; + u8 cc[2]; +}__packed; + +struct hostcmd_cmd_mfg +{ + struct hostcmd_header cmd_hdr; + __le32 action; + u8 data[0]; +}__packed; + +struct hostcmd_mfgfw_header { + __le16 cmd; + __le16 len; + __le16 seq; + __le16 result; +} __packed; + +struct hostcmd_cmd_lru +{ + struct hostcmd_mfgfw_header cmd_hdr; + __le32 cmd; + __le16 action; + __le16 id; + __le32 error; + u8 data[0]; +}__packed; + +struct lrdcmd_header +{ + __le16 lrd_cmd; + __le16 result; + __le32 reserved; /* should be 0 */ +}__packed; + +struct lrdcmd_cmd_cap +{ + struct lrdcmd_header hdr; + __le16 capability; + __le16 num_mac_addr; + __le16 version; +}__packed; + +struct lrdcmd_cmd_ant_gain_adjust +{ + struct lrdcmd_header hdr; + __le32 ant_gain_adjust; +}__packed; + +struct lrdcmd_cmd_pwr_table +{ + struct lrdcmd_header hdr; + __le16 len; + struct ieee80211_hdr frm; + u8 data[0]; +}__packed; + +struct lrdcmd_cmd_pwr_table_result +{ + struct lrdcmd_header hdr; + __le32 result; + __le32 pn; +}__packed; + +struct lrdcmd_cmd_cc_info +{ + struct lrdcmd_header hdr; + u32 region; + u8 alpha2[2]; +} __packed; + +struct hostcmd_cmd_deepsleep { + struct hostcmd_header cmd_hdr; + u8 enableFlag; +} __packed; + +struct hostcmd_cmd_confirm_ps { + struct hostcmd_header cmd_hdr; + u8 status; +} __packed; + +#endif /* _HOSTCMD_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/isr.c b/drivers/net/wireless/laird/lrdmwl/isr.c new file mode 100644 index 0000000000000..fd7a8267c230d --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/isr.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements interrupt related functions. */ + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "isr.h" + +#define INVALID_WATCHDOG 0xAA + + +void mwl_chnl_switch_event(struct work_struct *work) +{ + struct mwl_priv *priv = + container_of(work, struct mwl_priv, chnl_switch_handle); + struct mwl_vif *mwl_vif; + struct ieee80211_vif *vif; + + if (!priv->csa_active) { + wiphy_err(priv->hw->wiphy, + "csa is not active (got channel switch event)\n"); + return; + } + + spin_lock_bh(&priv->vif_lock); + list_for_each_entry(mwl_vif, &priv->vif_list, list) { + vif = container_of((void *)mwl_vif, struct ieee80211_vif, + drv_priv); + + if (vif->bss_conf.csa_active) + ieee80211_csa_finish(vif); + } + spin_unlock_bh(&priv->vif_lock); + + wiphy_info(priv->hw->wiphy, "channel switch is done\n"); + + priv->csa_active = false; +} + +void mwl_watchdog_ba_events(struct work_struct *work) +{ + int rc; + u8 bitmap = 0, stream_index; + struct mwl_ampdu_stream *streams; + struct mwl_priv *priv = + container_of(work, struct mwl_priv, watchdog_ba_handle); + + rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap); + + if (rc) + return; + + spin_lock_bh(&priv->stream_lock); + + /* the bitmap is the hw queue number. Map it to the ampdu queue. */ + if (bitmap != INVALID_WATCHDOG) { + if (bitmap == SYSADPT_TX_AMPDU_QUEUES) + stream_index = 0; + else if (bitmap > SYSADPT_TX_AMPDU_QUEUES) + stream_index = bitmap - SYSADPT_TX_AMPDU_QUEUES; + else + stream_index = bitmap + 3; /** queue 0 is stream 3*/ + + if (bitmap != 0xFF) { + /* Check if the stream is in use before disabling it */ + streams = &priv->ampdu[stream_index]; + + if (streams->state == AMPDU_STREAM_ACTIVE) + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + } else { + for (stream_index = 0; + stream_index < SYSADPT_TX_AMPDU_QUEUES; + stream_index++) { + streams = &priv->ampdu[stream_index]; + + if (streams->state != AMPDU_STREAM_ACTIVE) + continue; + + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + } + } + } + + spin_unlock_bh(&priv->stream_lock); +} diff --git a/drivers/net/wireless/laird/lrdmwl/isr.h b/drivers/net/wireless/laird/lrdmwl/isr.h new file mode 100644 index 0000000000000..1e705abe37d20 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/isr.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines interrupt related functions. */ + +#ifndef _ISR_H_ +#define _ISR_H_ + +#include + +irqreturn_t mwl_isr(int irq, void *dev_id); +void mwl_chnl_switch_event(struct work_struct *work); +void mwl_watchdog_ba_events(struct work_struct *work); + +#endif /* _ISR_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/mac80211.c b/drivers/net/wireless/laird/lrdmwl/mac80211.c new file mode 100644 index 0000000000000..c0ab06e442a88 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/mac80211.c @@ -0,0 +1,1423 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements mac80211 related functions. */ + +#include + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "tx.h" +#include "main.h" +#include "vendor_cmd.h" + +#define MWL_DRV_NAME KBUILD_MODNAME + +#define MAX_AMPDU_ATTEMPTS 5 + +extern struct ieee80211_rate mwl_rates_24[]; +extern struct ieee80211_rate mwl_rates_50[]; + +static void mwl_mac80211_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl_priv *priv = hw->priv; + + if (!priv->radio_on) { + + if (!priv->recovery_in_progress) + wiphy_warn(hw->wiphy, "dropped TX frame since radio is disabled\n"); + + dev_kfree_skb_any(skb); + + return; + } + + mwl_tx_xmit(hw, control, skb); +} + +static int mwl_mac80211_start(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int rc; + + /* Enable TX and RX tasklets. */ + if (priv->if_ops.ptx_task != NULL) + tasklet_enable(priv->if_ops.ptx_task); + + tasklet_enable(&priv->rx_task); + + if (priv->if_ops.ptx_done_task != NULL) + tasklet_enable(priv->if_ops.ptx_done_task); + + if (priv->if_ops.pqe_task != NULL) + tasklet_enable(priv->if_ops.pqe_task); + + if (priv->stop_shutdown) { + if (priv->if_ops.up_pwr != NULL) { + rc = priv->if_ops.up_pwr(priv); + if (rc) + goto fwcmd_fail; + } + + priv->mac_init_complete = true; + + rc = mwl_reinit_sw(priv, true); + if (rc) + goto fwcmd_fail; + } + + priv->mac_init_complete = true; + + /* Enable interrupts */ + mwl_fwcmd_int_enable(hw); + + rc = mwl_fwcmd_radio_enable(hw); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_wmm_mode(hw, true); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_ht_guard_interval(hw, GUARD_INTERVAL_AUTO); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_dwds_stamode(hw, true); + if (rc) + goto fwcmd_fail; + if (priv->tx_amsdu_enable) + rc = mwl_fwcmd_set_fw_flush_timer(hw, SYSADPT_AMSDU_FLUSH_TIME); + else + rc = mwl_fwcmd_set_fw_flush_timer(hw, 0); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_set_optimization_level(hw, wmm_turbo); + if (rc) + goto fwcmd_fail; + rc = mwl_fwcmd_config_EDMACCtrl(hw, EDMAC_Ctrl); + if (rc) + goto fwcmd_fail; + + ieee80211_wake_queues(hw); + + priv->mac_started = true; + + return 0; + +fwcmd_fail: + mwl_fwcmd_int_disable(hw); + if (priv->if_ops.ptx_task != NULL) + tasklet_disable(priv->if_ops.ptx_task); + + if (priv->if_ops.ptx_done_task != NULL) + tasklet_disable(priv->if_ops.ptx_done_task); + + if (priv->if_ops.pqe_task != NULL) + tasklet_disable(priv->if_ops.pqe_task); + tasklet_disable(&priv->rx_task); + + return rc; +} + +void mwl_mac80211_stop(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + /* + * mwl_mac80211_stop may be called directly by the driver in error + * recovery scenarios, in addition to the standard calls from mac80211. + * It could be called by both in some error scenarios, and calls + * could come in either order. It must not be called if already + * stopped, so handle that here. + */ + if (!priv->mac_started) { + wiphy_warn(hw->wiphy, "%s: already stopped, ignoring...", __func__); + return; + } + + priv->mac_started = false; + + mwl_fwcmd_radio_disable(hw); + + ieee80211_stop_queues(hw); + + /* Disable interrupts */ + mwl_fwcmd_int_disable(hw); + + /* Disable TX reclaim and RX tasklets. */ + if (priv->if_ops.ptx_task != NULL) + tasklet_disable(priv->if_ops.ptx_task); + + if (priv->if_ops.ptx_work != NULL) + cancel_work_sync(priv->if_ops.ptx_work); + + if (priv->if_ops.ptx_done_task != NULL) + tasklet_disable(priv->if_ops.ptx_done_task); + + if (priv->if_ops.pqe_task != NULL) + tasklet_disable(priv->if_ops.pqe_task); + + // Disable rx tasklet only if we are not shutting down + // Interface cleanup code will call tasklet_kill at shutdown, and tasklet must + // not be allowed to be both disabled and queued simultaneously + if (!priv->shutdown) + tasklet_disable(&priv->rx_task); + + /* Return all skbs to mac80211 */ + if (priv->if_ops.tx_done != NULL) + priv->if_ops.tx_done((unsigned long)hw); + + if (priv->stop_shutdown && !priv->recovery_in_progress) { + mwl_shutdown_sw(priv, true); + + if (priv->if_ops.down_pwr != NULL) + priv->if_ops.down_pwr(priv); + } +} + +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; + int rc = 0; + + wiphy_dbg(hw->wiphy,"mwl_mac80211_add_interface %x \n", vif->type); + + switch (vif->type) { + case NL80211_IFTYPE_MONITOR: + return 0; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + macids_supported = priv->ap_macids_supported; + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + macids_supported = priv->sta_macids_supported; + break; + case NL80211_IFTYPE_ADHOC: + macids_supported = priv->adhoc_macids_supported; + break; + default: + return -EINVAL; + } + + macid = ffs(macids_supported & ~priv->macids_used); + + if (!macid) { + wiphy_warn(hw->wiphy, "no macid can be allocated\n"); + return -EBUSY; + } + macid--; + + /* Setup driver private area. */ + mwl_vif = mwl_dev_get_vif(vif); + memset(mwl_vif, 0, sizeof(*mwl_vif)); + mwl_vif->vif = vif; + mwl_vif->macid = macid; + mwl_vif->seqno = 0; + mwl_vif->is_hw_crypto_enabled = false; + mwl_vif->beacon_info.valid = false; + mwl_vif->iv16 = 1; + mwl_vif->iv32 = 0; + mwl_vif->keyidx = 0; + mwl_vif->tx_key_idx = -1; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + ether_addr_copy(mwl_vif->bssid, vif->addr); + rc = mwl_fwcmd_set_new_stn_add_self(hw, vif); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + ether_addr_copy(mwl_vif->sta_mac, vif->addr); + mwl_fwcmd_bss_start(hw, vif, true); + mwl_fwcmd_set_infra_mode(hw, vif); + rc = mwl_fwcmd_set_mac_addr_client(hw, vif, vif->addr); + break; + case NL80211_IFTYPE_ADHOC: + ether_addr_copy(mwl_vif->sta_mac, vif->addr); + mwl_fwcmd_set_infra_mode(hw, vif); + rc = mwl_fwcmd_set_new_stn_add_self(hw, vif); + break; + default: + return -EINVAL; + } + + if (!rc) { + priv->macids_used |= 1 << mwl_vif->macid; + spin_lock_bh(&priv->vif_lock); + list_add_tail(&mwl_vif->list, &priv->vif_list); + spin_unlock_bh(&priv->vif_lock); + } + else { + wiphy_err(hw->wiphy, "Failed to add interface %x %d\n",vif->type, rc); + } + + return rc; +} + +void mwl_mac80211_remove_vif(struct mwl_priv *priv, + struct ieee80211_vif *vif) +{ + struct mwl_vif *mwl_vif = mwl_dev_get_vif(vif); + + if (!priv->macids_used) + return; + + mwl_tx_del_pkts_via_vif(priv->hw, vif); + + priv->macids_used &= ~(1 << mwl_vif->macid); + spin_lock_bh(&priv->vif_lock); + list_del(&mwl_vif->list); + spin_unlock_bh(&priv->vif_lock); +} + +static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + + wiphy_dbg(hw->wiphy,"mwl_mac80211_remove_interface %x \n", vif->type); + + switch (vif->type) { + case NL80211_IFTYPE_MONITOR: + if (priv->monitor_mode) { + mwl_fwcmd_set_monitor_mode (hw, 0); + } + return; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr); + break; + case NL80211_IFTYPE_ADHOC: + mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr); + mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr); + break; + default: + break; + } + + mwl_mac80211_remove_vif(priv, vif); +} + +static int mwl_mac80211_config(struct ieee80211_hw *hw, + u32 changed) +{ + struct ieee80211_conf *conf = &hw->conf; + struct mwl_priv *priv = hw->priv; + + int rc = 0; + + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + if (conf->flags & IEEE80211_CONF_IDLE) { + mwl_restart_ds_timer(priv, false); + } else { + mwl_delete_ds_timer(priv); + } + } + + if (conf->flags & IEEE80211_CONF_IDLE) { + rc = mwl_fwcmd_radio_disable(hw); + } + else { + if (priv->reg.regulatory_set) { + rc = mwl_fwcmd_radio_enable(hw); + } + else { + goto out; + } + } + + if (rc) + goto out; + + if ((changed & IEEE80211_CONF_CHANGE_MONITOR) && + (priv->radio_caps.capability & LRD_CAP_SU60)) { + if(conf->flags & IEEE80211_CONF_MONITOR) + rc = mwl_fwcmd_set_monitor_mode (hw, 1); + else + rc = mwl_fwcmd_set_monitor_mode (hw, 0); + + if (rc) + goto out; + + priv->monitor_mode = (conf->flags & IEEE80211_CONF_MONITOR) != 0; + } + + if (changed & IEEE80211_CONF_CHANGE_PS) { + int empty_list; + + // Firmware will hang if powersave mode is attempted with empty station list + // This is possible due to race conditions during restart attempts, block it here + spin_lock_bh(&priv->sta_lock); + empty_list = list_empty(&priv->sta_list); + spin_unlock_bh(&priv->sta_lock); + if (empty_list && (conf->flags & IEEE80211_CONF_PS)) { + wiphy_err(hw->wiphy, "%s() Warning! Attempt to change power mode with empty station list!\n", __FUNCTION__); + } + else { + rc = mwl_fwcmd_powersave_EnblDsbl(hw, conf); + if (rc) + goto out; + } + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + int rate = 0; + +// wiphy_debug(hw->wiphy, "config: c=%x fl=%x pw=%d rd=%d ch=%d\n", +// changed, conf->flags, conf->power_level, +// conf->radar_enabled, conf->chandef.chan->hw_value); + + if (conf->chandef.chan->band == NL80211_BAND_2GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED); + mwl_fwcmd_set_linkadapt_cs_mode(hw, LINK_CS_STATE_CONSERV); + rate = mwl_rates_24[0].hw_value; + } else if (conf->chandef.chan->band == NL80211_BAND_5GHZ) { + mwl_fwcmd_set_apmode(hw, AP_MODE_11AC); + mwl_fwcmd_set_linkadapt_cs_mode(hw, LINK_CS_STATE_AUTO); + rate = mwl_rates_50[0].hw_value; + + if (conf->radar_enabled) + mwl_fwcmd_set_radar_detect(hw, MONITOR_START); + else + mwl_fwcmd_set_radar_detect(hw, STOP_DETECT_RADAR); + } + + rc = mwl_fwcmd_get_survey(hw, 0); + if (rc) + goto out; + rc = mwl_fwcmd_set_rf_channel(hw, conf); + if (rc) + goto out; + rc = mwl_fwcmd_use_fixed_rate(hw, rate, rate); + if (rc) + goto out; + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + priv->target_power = conf->power_level; + } + + if (changed & (IEEE80211_CONF_CHANGE_POWER | IEEE80211_CONF_CHANGE_CHANNEL)) { + rc = mwl_fwcmd_tx_power(hw, conf, priv->target_power); + if (rc) + goto out; + } + +out: + + return rc; +} + +static void mwl_rc_update_work(struct work_struct *work) +{ + struct mwl_sta *sta_info = + container_of(work, struct mwl_sta, rc_update_work); + struct ieee80211_sta *sta = + container_of((void *)sta_info, struct ieee80211_sta, drv_priv); + struct mwl_priv *priv = sta_info->mwl_private; + struct ieee80211_hw *hw = priv->hw; + u8 smps_mode; + + wiphy_dbg(hw->wiphy, "%s() new smps_mode=%d\n", + __FUNCTION__, sta->deflink.smps_mode); + wiphy_dbg(hw->wiphy, "mac: %x:%x:%x:%x:%x:%x\n", + sta->addr[0], sta->addr[1], + sta->addr[2], sta->addr[3], + sta->addr[4], sta->addr[5]); + + /* Convert mac80211 enum to 80211 format again */ + switch (sta->deflink.smps_mode) { + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_OFF: + smps_mode = 0; + break; + case IEEE80211_SMPS_DYNAMIC: + smps_mode = 0x11; + break; + default: + smps_mode = 0x1; // Enable + break; + } + + mwl_fwcmd_set_mimops_ht(hw, + sta->addr, smps_mode); +} + +void mwl_mac80211_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + u32 changed) +{ + struct mwl_priv *priv = hw->priv; + + if(changed & IEEE80211_RC_SMPS_CHANGED) { + struct mwl_sta *sta_info; + sta_info = mwl_dev_get_sta(sta); + + queue_work(priv->rx_defer_workq, &sta_info->rc_update_work); + } + /* TODO: VHT OpMode notification related handling here */ +} + +static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u64 changed) +{ + if((vif->type != NL80211_IFTYPE_AP) && (vif->type != NL80211_IFTYPE_P2P_GO) && + (vif->type != NL80211_IFTYPE_STATION) && (vif->type != NL80211_IFTYPE_P2P_CLIENT) && + (vif->type != NL80211_IFTYPE_ADHOC) ) { + wiphy_err(hw->wiphy,"Unsupported Interface Type\n"); + return; + } + + //TODO Add common functions + if (changed & BSS_CHANGED_ERP_SLOT) + mwl_fwcmd_set_slot_time(hw, vif->bss_conf.use_short_slot); + + if (changed & BSS_CHANGED_ERP_PREAMBLE) + mwl_fwcmd_set_radio_preamble(hw, vif->bss_conf.use_short_preamble); + + if (changed & BSS_CHANGED_BASIC_RATES) { + int idx; + int rate; + + /* Use lowest supported basic rate for multicasts + * and management frames (such as probe responses -- + * beacons will always go out at 1 Mb/s). + */ + idx = ffs(vif->bss_conf.basic_rates); + if (idx) + idx--; + + if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ) + rate = mwl_rates_24[idx].hw_value; + else + rate = mwl_rates_50[idx].hw_value; + + mwl_fwcmd_use_fixed_rate(hw, rate, rate); + } + + if ((vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_ADHOC) || + (vif->type == NL80211_IFTYPE_P2P_GO)) + { + if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) { + struct sk_buff *skb; + + if(changed & BSS_CHANGED_BEACON_INT) + wiphy_dbg(hw->wiphy, "Beacon interval changed\n"); + if(changed & BSS_CHANGED_BEACON) + wiphy_dbg(hw->wiphy, "Beacon data changed\n"); + + skb = ieee80211_beacon_get(hw, vif, 0); + if (skb) { + mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len); + dev_kfree_skb_any(skb); + } + } + } + + if ((changed & BSS_CHANGED_ASSOC) && vif->cfg.assoc) { + if (vif->type == NL80211_IFTYPE_STATION || vif->type == NL80211_IFTYPE_P2P_CLIENT) + mwl_fwcmd_set_aid(hw, vif, (u8 *)vif->bss_conf.bssid, vif->cfg.aid); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + if(vif->type == NL80211_IFTYPE_ADHOC) + mwl_fwcmd_ibss_start(hw, vif, info->enable_beacon); + else + mwl_fwcmd_bss_start(hw, vif, info->enable_beacon); + } + + if (changed & BSS_CHANGED_ASSOC) { + if (vif->type == NL80211_IFTYPE_STATION || vif->type == NL80211_IFTYPE_P2P_CLIENT) + { + // clear force_host_crypto when assoc/disassoc + struct mwl_vif *mwl_vif; + mwl_vif = mwl_dev_get_vif(vif); + mwl_vif->force_host_crypto = false; + } + } +} + +static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + *total_flags &= FIF_CONTROL | FIF_OTHER_BSS | + FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC | + FIF_PROBE_REQ; +} + +static int mwl_mac80211_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd_param, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + int rc = 0; + u8 encr_type; + u8 *addr; + + if (priv->host_crypto) + return -EOPNOTSUPP; + + mwl_vif = mwl_dev_get_vif(vif); + + if (!sta) { + addr = vif->addr; + } else { + addr = sta->addr; + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) + ether_addr_copy(mwl_vif->bssid, addr); + } + + if (cmd_param == SET_KEY) { + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) || + (key->cipher == WLAN_CIPHER_SUITE_WEP104)) { + encr_type = ENCR_TYPE_WEP; + } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) { + encr_type = ENCR_TYPE_AES; + if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0) { + if ((vif->type != NL80211_IFTYPE_STATION) && + (vif->type != NL80211_IFTYPE_P2P_CLIENT)) + mwl_vif->keyidx = key->keyidx; + } + } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + encr_type = ENCR_TYPE_TKIP; + } else if ((key->cipher == WLAN_CIPHER_SUITE_AES_CMAC) || + (key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_128) || + (key->cipher == WLAN_CIPHER_SUITE_BIP_GMAC_256) || + (key->cipher == WLAN_CIPHER_SUITE_BIP_CMAC_256)) { + // always using host encryption for management frames + return -EOPNOTSUPP; + } else { + // use host/kernel cryptography + encr_type = ENCR_TYPE_DISABLE; + } + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + // force_host_crypto initialized when parsing the beacon rsnie + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + // if pairwise uses host crypto, then group must also + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + if (encr_type == ENCR_TYPE_DISABLE) { + mwl_vif->force_host_crypto = true; + } else { + mwl_vif->force_host_crypto = false; + } + } + break; + case NL80211_IFTYPE_ADHOC: + default: + mwl_vif->force_host_crypto = false; + break; + } + + if (mwl_vif->force_host_crypto) { + // using host crypto for all keys (pairwise/group) + encr_type = ENCR_TYPE_DISABLE; + } + + if (encr_type != ENCR_TYPE_DISABLE) { + rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key); + if (rc) + goto out; + } + + rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr, + encr_type); + if (rc) + goto out; + + if (encr_type != ENCR_TYPE_DISABLE) { + mwl_vif->is_hw_crypto_enabled = true; + } else { + // using host encryption for data packets + mwl_vif->is_hw_crypto_enabled = false; + return -EOPNOTSUPP; + } + + } else { + rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key); + if (rc) { + if (rc == -ENETDOWN) + rc = 0; + goto out; + } + + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) { + mwl_vif->wep_key_conf[key->keyidx].enabled = 0; + } + } + +out: + + return rc; +} + +static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw, + u32 value) +{ + return mwl_fwcmd_set_rts_threshold(hw, value); +} + +static int mwl_mac80211_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + struct mwl_sta *sta_info; + struct ieee80211_key_conf *key; + int rc; + int i; + + mwl_vif = mwl_dev_get_vif(vif); + sta_info = mwl_dev_get_sta(sta); + + wiphy_dbg(hw->wiphy,"mwl_mac80211_sta_add \n"); + memset(sta_info, 0, sizeof(*sta_info)); + + if (sta->deflink.ht_cap.ht_supported) { + sta_info->is_ampdu_allowed = true; + sta_info->is_amsdu_allowed = false; + if (sta->deflink.ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU) + sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_8K; + else + sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_4K; + if ((sta->tdls) && (!sta->wme)) + sta->wme = true; + } + sta_info->iv16 = 1; + sta_info->iv32 = 0; + sta_info->sta = sta; + sta_info->vif = vif; + spin_lock_init(&sta_info->amsdu_lock); + INIT_WORK(&sta_info->rc_update_work, mwl_rc_update_work); + sta_info->mwl_private = priv; + + spin_lock_bh(&priv->sta_lock); + list_add_tail(&sta_info->list, &priv->sta_list); + spin_unlock_bh(&priv->sta_lock); + + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) + mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); + + rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta); + + for (i = 0; i < NUM_WEP_KEYS; i++) { + key = (struct ieee80211_key_conf *)mwl_vif->wep_key_conf[i].key; + + if (mwl_vif->wep_key_conf[i].enabled) + mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key); + } + + return rc; +} + +int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + int rc; + int i; + struct mwl_vif *mwl_vif; + struct ieee80211_key_conf *key; + struct mwl_sta *sta_info; + bool bfound = false; + + wiphy_dbg(hw->wiphy,"mwl_mac80211_sta_remove \n"); + + // Ensure the station is on our list of managed stations + // Could have already been removed locally in reset/recovery scenario + spin_lock_bh(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + if (sta_info->sta == sta) + { + bfound = true; + break; + } + } + spin_unlock_bh(&priv->sta_lock); + + if (!bfound) + { + return 0; + } + + mwl_vif = mwl_dev_get_vif(vif); + cancel_work_sync(&sta_info->rc_update_work); + + mwl_tx_del_sta_amsdu_pkts(sta); + mwl_fwcmd_del_sta_streams(hw, sta); + mwl_tx_del_pkts_via_sta(hw, sta); + + for (i = 0; i < NUM_WEP_KEYS; i++) { + key = (struct ieee80211_key_conf *)mwl_vif->wep_key_conf[i].key; + + if (mwl_vif->wep_key_conf[i].enabled) + rc = mwl_fwcmd_encryption_remove_key(hw, vif, sta->addr, key); + } + + rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); + + spin_lock_bh(&priv->sta_lock); + list_del(&sta_info->list); + spin_unlock_bh(&priv->sta_lock); + + return rc; +} + +static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + unsigned int link_id, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + int q; + + if (WARN_ON(queue > SYSADPT_TX_WMM_QUEUES - 1)) + return -EINVAL; + + q = SYSADPT_TX_WMM_QUEUES - 1 - queue; + memcpy(&priv->wmm_params[q], params, sizeof(*params)); + + if (!priv->wmm_enabled) { + rc = mwl_fwcmd_set_wmm_mode(hw, true); + priv->wmm_enabled = true; + } + + if (!rc) { + + +// wiphy_info(hw->wiphy, "WMM Params[Q %d]: cwmin=%d cwmax=%d aifs=%d txop=%d\n", q, params->cw_min, params->cw_max, params->aifs, params->txop); + + rc = mwl_fwcmd_set_edca_params(hw, q, + params->cw_min, params->cw_max, + params->aifs, params->txop); + } + + if (queue == IEEE80211_AC_BK){ + /* All WMM config received, create tc=>txq mapping */ + wmm_init_tc_to_txq_mapping(priv); + } + + return rc; +} + +static int mwl_mac80211_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + return mwl_fwcmd_get_stat(hw, stats); +} + +static int mwl_mac80211_get_survey(struct ieee80211_hw *hw, + int idx, + struct survey_info *survey) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_survey_info *survey_info; + + if ((priv->survey_info_idx) && (idx < priv->survey_info_idx)) { + survey_info = &priv->survey_info[idx]; + } else { + if (idx > priv->survey_info_idx) { + priv->survey_info_idx = 0; + return -ENOENT; + } else if (idx == priv->survey_info_idx) { + int i; + for (i = 0; i < priv->survey_info_idx; i++) { + if (priv->cur_survey_info.channel.hw_value + == priv->survey_info[i].channel.hw_value) { + priv->survey_info_idx = 0; + return -ENOENT; + } + } + } + + if(mwl_fwcmd_get_survey(hw, 0)) { + return -EIO; + } + survey_info = &priv->cur_survey_info; + if (!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) + survey->filled |= SURVEY_INFO_IN_USE; + } + + survey->channel = &survey_info->channel; + survey->filled |= survey_info->filled; + survey->time = survey_info->time_period / 1000; + survey->time_busy = survey_info->time_busy / 1000; + survey->time_tx = survey_info->time_tx / 1000; + survey->noise = survey_info->noise; + + return 0; +} + +static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_ampdu_params *params) +{ + int rc = 0; + struct mwl_priv *priv = hw->priv; + struct mwl_ampdu_stream *stream; + enum ieee80211_ampdu_mlme_action action = params->action; + struct ieee80211_sta *sta = params->sta; + u16 tid = params->tid; + u8 buf_size = params->buf_size; + u8 *addr = sta->addr, idx; + struct mwl_sta *sta_info; + + sta_info = mwl_dev_get_sta(sta); + + spin_lock_bh(&priv->stream_lock); + + stream = mwl_fwcmd_lookup_stream(hw, addr, tid); + + wiphy_dbg(hw->wiphy, "%s(e) Action=%d stream=%p state=%d\n", + __FUNCTION__, action, stream, stream ? (int)stream->state : -1); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + if (!sta_info->is_ampdu_allowed) { + wiphy_warn(hw->wiphy, "ampdu not allowed\n"); + rc = -EPERM; + break; + } + + if (!stream) { + stream = mwl_fwcmd_add_stream(hw, sta, tid); + if (!stream) { + wiphy_warn(hw->wiphy, "no stream found\n"); + rc = -EPERM; + break; + } + } + + spin_unlock_bh(&priv->stream_lock); + rc = mwl_fwcmd_check_ba(hw, stream, vif); + spin_lock_bh(&priv->stream_lock); + if (rc) { + mwl_fwcmd_remove_stream(hw, stream); + sta_info->check_ba_failed[tid]++; + rc = -EPERM; + break; + } + stream->state = AMPDU_STREAM_IN_PROGRESS; + params->ssn = 0; + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + if (stream) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + stream->state = AMPDU_STREAM_IN_PROGRESS; + if (action != IEEE80211_AMPDU_TX_STOP_CONT) + mwl_tx_del_ampdu_pkts(hw, sta, tid); + idx = stream->idx; + spin_unlock_bh(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + spin_lock_bh(&priv->stream_lock); + sta_info->is_amsdu_allowed = false; + } + + mwl_fwcmd_remove_stream(hw, stream); + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + } else { + /* In recovery scenarios mac80211 instructs us to IEEE80211_AMPDU_TX_STOP_FLUSH + * and occasionally IEEE80211_AMPDU_TX_STOP_CONT. We've already destroyed the + * stream, so there isn't anything we can do. Don't return error here, as it + * does nothing but cause an ASSERT. Simply call the callback indicating we are + * done, so mac is happy. + */ + if (action != IEEE80211_AMPDU_TX_STOP_FLUSH_CONT) + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + } + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + if (stream) { + if (WARN_ON(stream->state != + AMPDU_STREAM_IN_PROGRESS)) { + rc = -EPERM; + break; + } + spin_unlock_bh(&priv->stream_lock); + rc = mwl_fwcmd_create_ba(hw, stream, buf_size, vif); + spin_lock_bh(&priv->stream_lock); + + if (!rc) { + stream->state = AMPDU_STREAM_ACTIVE; + sta_info->check_ba_failed[tid] = 0; + sta_info->is_amsdu_allowed = params->amsdu; + } else { + idx = stream->idx; + spin_unlock_bh(&priv->stream_lock); + mwl_fwcmd_destroy_ba(hw, idx); + spin_lock_bh(&priv->stream_lock); + mwl_fwcmd_remove_stream(hw, stream); + wiphy_err(hw->wiphy, + "ampdu operation error code: %d\n", + rc); + } + } else { + rc = -EPERM; + } + break; + default: + rc = -ENOTSUPP; + break; + } + + spin_unlock_bh(&priv->stream_lock); + + return rc; +} + + +static int mwl_mac80211_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *chan, + int duration, enum ieee80211_roc_type type) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + + rc = mwl_config_remain_on_channel(hw, chan, true, duration, type); + if (!rc) { + mod_timer(&priv->roc.roc_timer, jiffies + msecs_to_jiffies(duration)); + priv->roc.tmr_running = true; + ieee80211_ready_on_channel(hw); + } + + return rc; +} + +static int mwl_mac80211_cancel_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int rc = 0; + + rc = mwl_config_remain_on_channel(hw, 0, false, 0, 0); + + return rc; +} + +static int mwl_mac80211_chnl_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *ch_switch) +{ + struct mwl_priv *priv = hw->priv; + int rc = 0; + + rc = mwl_fwcmd_set_switch_channel(priv, ch_switch); + + return rc; +} + +static void mwl_mac80211_sw_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) +{ + struct mwl_priv *priv = hw->priv; + + /* Stop BA timer again */ + del_timer_sync(&priv->period_timer); + + priv->sw_scanning = true; + priv->survey_info_idx = 0; + priv->cur_survey_valid = false; + + mwl_fwcmd_set_pre_scan(hw); +} + +static void mwl_mac80211_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + int scan_cnt; + + if (!priv->sw_scanning) + return; + + priv->sw_scanning = false; + mwl_fwcmd_set_post_scan(hw); + + if (!priv->shutdown) { + /* Start BA timer again */ + mod_timer(&priv->period_timer, jiffies + + msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); + } + + if (!priv->recovery_in_progress_full) { + scan_cnt = atomic_read(&priv->null_scan_count); + if (scan_cnt == 1) { + wiphy_err(hw->wiphy, "%s: No packets received, resetting!\n", __func__); + lrd_radio_recovery(priv); + } + else if (scan_cnt > 1) { + wiphy_err(hw->wiphy, "%s: No packets received, checking %d more scans before resetting!\n", __func__, scan_cnt - 1); + atomic_cmpxchg(&priv->null_scan_count, scan_cnt, scan_cnt - 1); + } + } +} + +int mwl_mac80211_set_ant(struct ieee80211_hw *hw, + u32 tx_ant, u32 rx_ant) +{ + struct mwl_priv *priv = hw->priv; + int rc; + + wiphy_err(hw->wiphy, "set ant: tx=0x%x rx=0x%x\n", + tx_ant, rx_ant); + + if (tx_ant & (~((u32)MWL_8997_DEF_TX_ANT_BMP))) { + return -EINVAL; + } + + if (rx_ant & (~((u32)MWL_8997_DEF_RX_ANT_BMP))) { + return -EINVAL; + } + +#if 1 + if ((tx_ant == 0x2) && (rx_ant == 0x3)) { + /* tx=ANT_B & rx=ANT_AB seems to be a invalid config + ** for KF2 (FW asserts). Blocking this setting here. + */ + return -EINVAL; + } +#endif + + rc = mwl_fwcmd_rf_antenna(hw, tx_ant, rx_ant); + if (rc){ + return -EINVAL; + } + + priv->ant_tx_bmp = tx_ant; + priv->ant_tx_num = MWL_TXANT_BMP_TO_NUM(tx_ant); + + priv->ant_rx_bmp = rx_ant; + priv->ant_rx_num = MWL_RXANT_BMP_TO_NUM(rx_ant); + + /* Update up band/rate information for 2.4G */ + if (!priv->disable_2g) { + mwl_set_ht_caps(priv, &priv->band_24); + mwl_set_vht_caps(priv, &priv->band_24); + } + + /* Update band/rate information for 5G */ + if (!priv->disable_5g) { + mwl_set_ht_caps(priv, &priv->band_50); + mwl_set_vht_caps(priv, &priv->band_50); + } + + return 0; +} + +int mwl_mac80211_get_ant(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct mwl_priv *priv = hw->priv; + + *tx_ant = priv->ant_tx_bmp; + *rx_ant = priv->ant_rx_bmp; + +// wiphy_err(hw->wiphy, "get ant: tx=0x%x rx=0x%x\n", +// *tx_ant, *rx_ant); + return 0; +} + +void +mwl_mac80211_set_default_uni_key (struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + int idx) +{ + struct mwl_vif *mwl_vif; + struct ieee80211_key_conf *key; + + mwl_vif = mwl_dev_get_vif(vif); + + if (idx >= 0 && idx < NUM_WEP_KEYS) { + + if (mwl_vif->wep_key_conf[idx].enabled && mwl_vif->tx_key_idx != idx) { + key = (struct ieee80211_key_conf*)mwl_vif->wep_key_conf[idx].key; + + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + mwl_vif->tx_key_idx = idx; + + mwl_fwcmd_encryption_set_tx_key(hw, vif, key); + } + } + } + else { + mwl_vif->tx_key_idx = -1; + } + + return; +} + +#ifdef CONFIG_PM +int mwl_mac80211_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) +{ + struct mwl_priv *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + int i = 0; + int rc = 0; + + if (wowlan) { + priv->wow.wowlanCond = 0; + + if (wowlan->any) { + priv->wow.wowlanCond |= MWL_WOW_CND_RX_DATA; + priv->wow.wowlanCond |= MWL_WOW_CND_DISCONNECT; + } + else { + if (wowlan->disconnect) { + priv->wow.wowlanCond |= MWL_WOW_CND_DISCONNECT; + } + + if (wowlan->nd_config) { + priv->wow.wowlanCond |= MWL_WOW_CND_AP_INRANGE; + + /* channel set */ + priv->wow.channelCnt = min((u32)sizeof(priv->wow.channels), + wowlan->nd_config->n_channels); + + for (i=0; i < priv->wow.channelCnt; i++) { + priv->wow.channels[i] = wowlan->nd_config->channels[i]->hw_value; + } + + /* ssid */ + priv->wow.ssidListCnt = min((int)ARRAY_SIZE(priv->wow.ssidList), + wowlan->nd_config->n_ssids); + + for (i = 0; i < priv->wow.ssidListCnt; i++) { + priv->wow.ssidList[i].ssidLen = min(wowlan->nd_config->ssids[i].ssid_len, + (u8)sizeof(priv->wow.ssidList[i].ssid)); + memcpy(priv->wow.ssidList[i].ssid, wowlan->nd_config->ssids[i].ssid, + priv->wow.ssidList[i].ssidLen); + } + } + } + + if (priv->wow.state & WOWLAN_STATE_ENABLED) { + + wiphy_info(hw->wiphy, "Enabling WOW for conditions 0x%x, 0x%x ch=0x%d\n", + priv->wow.wowlanCond, conf->flags & IEEE80211_CONF_IDLE, hw->conf.chandef.chan->hw_value); + + /* Configure AP detect settings */ + if (priv->wow.wowlanCond & MWL_WOW_CND_AP_INRANGE) { + mwl_fwcmd_wowlan_apinrange_config(priv->hw); + } + + /* Clear results */ + memset(&priv->wow.results, 0, sizeof(struct mwl_wowlan_result)); + + /* Enable the Host Sleep */ + priv->wow.state &= ~(WOWLAN_STATE_HS_SENT); + + //Disable DS timer + mwl_delete_ds_timer(priv); + + rc = mwl_fwcmd_hostsleep_control(priv->hw, true, conf->flags & IEEE80211_CONF_IDLE, priv->wow.wowlanCond); + if (!rc) { + priv->wow.state |= WOWLAN_STATE_HS_SENT; + } + else { + mwl_restart_ds_timer(priv, false); + } + } + } + + return rc; +} + +int mwl_mac80211_resume(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + if (priv->wow.state & WOWLAN_STATE_ENABLED) { + /* Disable the Host Sleep */ + mwl_fwcmd_hostsleep_control(priv->hw, 0, 0, priv->wow.wowlanCond); + priv->wow.state &= ~WOWLAN_STATE_HS_SENT; + + if (priv->wow.results.mwl_vif) { + /* wow event report before resume call */ + lrd_report_wowlan_wakeup(priv); + } + else { + priv->wow.jiffies = jiffies; + } + } + + return 0; +} + +void mwl_mac80211_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct mwl_priv *priv = hw->priv; + + if (enabled) { + priv->wow.state |= WOWLAN_STATE_ENABLED; + } + else { + priv->wow.state &= ~WOWLAN_STATE_ENABLED; + } +} + +#endif + +void mwl_mac80211_reconfig_complete(struct ieee80211_hw *hw, + enum ieee80211_reconfig_type reconfig_type) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_vif *mwl_vif; + + if (!priv->recovery_in_progress) + priv->recovery_in_progress_full = false; + + if (reconfig_type == IEEE80211_RECONFIG_TYPE_RESTART) + { + // If hardware has been restarted, this is due to radio recovery + // Walk the interface list and indicate connection loss so + // connections are restarted cleanly + list_for_each_entry(mwl_vif, &priv->vif_list, list) + { + if (mwl_vif->vif->type != NL80211_IFTYPE_AP) { + wiphy_err(priv->hw->wiphy, "%s: Indicating connection loss...\n", + MWL_DRV_NAME); + ieee80211_connection_loss(mwl_vif->vif); + } + + lrd_send_restart_event(hw->wiphy, mwl_vif->vif, LRD_REASON_RESET); + } + } + +} + +static int mwl_join_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + return 0; +} + +static void mwl_leave_ibss(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + +} + +static u64 mwl_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + return 0; +} + +static void mwl_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u64 tsf) +{ + +} + +#define TX_FLUSH_TIMEOUT_MS 500 + +void mwl_mac80211_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop) +{ + struct mwl_priv *priv = hw->priv; + int ret; + + ret = wait_event_interruptible_timeout(priv->tx_flush_wq, + !mwl_tx_pending(hw), + msecs_to_jiffies(TX_FLUSH_TIMEOUT_MS)); + + // Warn if this returned due to timeout or event + if (ret <= 1) + wiphy_warn(hw->wiphy, "flush exited with ret=%d\n", ret); +} +EXPORT_SYMBOL_GPL(mwl_mac80211_flush); + + +const struct ieee80211_ops mwl_mac80211_ops = { + .tx = mwl_mac80211_tx, + .start = mwl_mac80211_start, + .stop = mwl_mac80211_stop, +#ifdef CONFIG_PM + .suspend = mwl_mac80211_suspend, + .resume = mwl_mac80211_resume, + .set_wakeup = mwl_mac80211_set_wakeup, +#endif + .add_interface = mwl_mac80211_add_interface, + .remove_interface = mwl_mac80211_remove_interface, + .config = mwl_mac80211_config, + .sta_rc_update = mwl_mac80211_sta_rc_update, + .bss_info_changed = mwl_mac80211_bss_info_changed, + .configure_filter = mwl_mac80211_configure_filter, + .set_key = mwl_mac80211_set_key, + .set_rts_threshold = mwl_mac80211_set_rts_threshold, + .sta_add = mwl_mac80211_sta_add, + .sta_remove = mwl_mac80211_sta_remove, + .conf_tx = mwl_mac80211_conf_tx, + .get_stats = mwl_mac80211_get_stats, + .get_survey = mwl_mac80211_get_survey, + .ampdu_action = mwl_mac80211_ampdu_action, + .pre_channel_switch = mwl_mac80211_chnl_switch, + .remain_on_channel = mwl_mac80211_remain_on_channel, + .cancel_remain_on_channel = mwl_mac80211_cancel_remain_on_channel, + .sw_scan_start = mwl_mac80211_sw_scan_start, + .sw_scan_complete = mwl_mac80211_sw_scan_complete, + .set_default_unicast_key = mwl_mac80211_set_default_uni_key, + + .join_ibss = mwl_join_ibss, + .leave_ibss = mwl_leave_ibss, + .get_tsf = mwl_get_tsf, + .set_tsf = mwl_set_tsf, + + .set_antenna = mwl_mac80211_set_ant, + .get_antenna = mwl_mac80211_get_ant, + + .reconfig_complete = mwl_mac80211_reconfig_complete, + .flush = mwl_mac80211_flush, +}; diff --git a/drivers/net/wireless/laird/lrdmwl/main.c b/drivers/net/wireless/laird/lrdmwl/main.c new file mode 100644 index 0000000000000..ee49466415990 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/main.c @@ -0,0 +1,2369 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements main functions of this module. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "sysadpt.h" +#include "dev.h" +#include "pcie.h" +#include "fwcmd.h" +#include "tx.h" +#include "rx.h" +#include "isr.h" +#include "thermal.h" +#include "vendor_cmd.h" +#include "hostcmd.h" + +#ifdef CONFIG_SYSFS +#include "sysfs.h" +#endif + +#ifdef CONFIG_DEBUG_FS +#include "debugfs.h" +#endif + +#include "main.h" +#include "vendor_cmd.h" +#define FILE_PATH_LEN 64 + +#define NOT_LRD_HW 0x214C5244 +#define NUM_UNII3_CHANNELS 5 +#define NUM_WW_CHANNELS 3 +#define REG_PWR_DB_NAME MWL_FW_ROOT"/regpwr.db" + +static const struct ieee80211_channel mwl_channels_24[] = { + { .band = NL80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, + { .band = NL80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, +}; + +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 = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_channel mwl_channels_50[] = { + { .band = NL80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, }, + { .band = NL80211_BAND_5GHZ, .center_freq = 5825, .hw_value = 165, }, +}; + +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 ibss_if_limits[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC) } +}; + +static const struct ieee80211_iface_limit ap_if_su_limits[] = { + { .max = SYSADPT_NUM_OF_SU_AP, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = SYSADPT_NUM_OF_STA, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT)}, +}; + +static const struct ieee80211_iface_combination if_su_comb[] = { + { + .limits = ap_if_su_limits, + .n_limits = ARRAY_SIZE(ap_if_su_limits), + .max_interfaces = SYSADPT_NUM_OF_SU_AP, + .num_different_channels = 1, + .radar_detect_widths = 0, + }, + { + .limits = ibss_if_limits, + .n_limits = ARRAY_SIZE(ibss_if_limits), + .max_interfaces = 1, + .num_different_channels = 1, + .radar_detect_widths = 0, + } +}; + + +static const struct ieee80211_iface_limit if_st_limits[] = { + { .max = SYSADPT_NUM_OF_ST_AP, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = SYSADPT_NUM_OF_STA, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT)}, +}; + +static const struct ieee80211_iface_combination if_st_comb[] = { + { + .limits = if_st_limits, + .n_limits = ARRAY_SIZE(if_st_limits), + .max_interfaces = SYSADPT_NUM_OF_ST_AP + SYSADPT_NUM_OF_STA, + .num_different_channels = 1, + .radar_detect_widths = 0, + }, + { + .limits = ibss_if_limits, + .n_limits = ARRAY_SIZE(ibss_if_limits), + .max_interfaces = 1, + .num_different_channels = 1, + .radar_detect_widths = 0 + } + +}; + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support lrd_wowlan_support = { + .flags = WIPHY_WOWLAN_ANY | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_NET_DETECT, + .n_patterns = 0, + .pattern_min_len = 0, + .pattern_max_len = 0, +}; +#endif + +/* CAL data config file */ +static char *cal_data_cfg; + +/* WMM Turbo mode */ +int wmm_turbo = 1; + +/* EDMAC Control */ +int EDMAC_Ctrl = 0x0; + +/* Tx AMSDU control*/ +int tx_amsdu_enable = 0; + +int ds_enable = DS_ENABLE_ON; +int enable_bt_dup = 0; + +/* MFG mode control*/ +int mfg_mode = 0; + +/* Laird additions */ +int SISO_mode = 0; +int lrd_debug = 0; +int null_scan_count = 0; +unsigned int stop_shutdown = 0; +unsigned int stop_shutdown_timeout = 12000; +unsigned int enable_24_40mhz = 0; +unsigned int ant_gain_adjust = 0; +unsigned int host_crypto_mode = 0; + +static int lrd_send_fw_event(struct device *dev, bool on) +{ + static char *env_on[] = { "FIRMWARE=on", NULL }; + static char *env_off[] = { "FIRMWARE=off", NULL }; + + return kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, on ? env_on : env_off); +} + +static bool mwl_is_world_region(struct cc_info *cc) +{ + if (cc->region == REG_CODE_WW) { + return true; + } + + return false; +} + +static bool mwl_is_en440_region(struct cc_info *cc) +{ + if (cc->region == REG_CODE_ETSI0) { + return true; + } + else if ( cc->region == REG_CODE_ETSI ) { + if ((cc->alpha2[0] == 'A' && cc->alpha2[1] == 'T') || + (cc->alpha2[0] == 'B' && cc->alpha2[1] == 'E') || + (cc->alpha2[0] == 'B' && cc->alpha2[1] == 'G') || + (cc->alpha2[0] == 'C' && cc->alpha2[1] == 'Y') || + (cc->alpha2[0] == 'C' && cc->alpha2[1] == 'Z') || + (cc->alpha2[0] == 'D' && cc->alpha2[1] == 'E') || + (cc->alpha2[0] == 'D' && cc->alpha2[1] == 'K') || + (cc->alpha2[0] == 'E' && cc->alpha2[1] == 'E') || + (cc->alpha2[0] == 'E' && cc->alpha2[1] == 'S') || + (cc->alpha2[0] == 'F' && cc->alpha2[1] == 'I') || + (cc->alpha2[0] == 'F' && cc->alpha2[1] == 'R') || + (cc->alpha2[0] == 'G' && cc->alpha2[1] == 'B') || + (cc->alpha2[0] == 'G' && cc->alpha2[1] == 'R') || + (cc->alpha2[0] == 'H' && cc->alpha2[1] == 'R') || + (cc->alpha2[0] == 'H' && cc->alpha2[1] == 'U') || + (cc->alpha2[0] == 'I' && cc->alpha2[1] == 'E') || + (cc->alpha2[0] == 'I' && cc->alpha2[1] == 'T') || + (cc->alpha2[0] == 'L' && cc->alpha2[1] == 'T') || + (cc->alpha2[0] == 'L' && cc->alpha2[1] == 'U') || + (cc->alpha2[0] == 'L' && cc->alpha2[1] == 'V') || + (cc->alpha2[0] == 'M' && cc->alpha2[1] == 'T') || + (cc->alpha2[0] == 'N' && cc->alpha2[1] == 'L') || + (cc->alpha2[0] == 'P' && cc->alpha2[1] == 'L') || + (cc->alpha2[0] == 'P' && cc->alpha2[1] == 'T') || + (cc->alpha2[0] == 'R' && cc->alpha2[1] == 'O') || + (cc->alpha2[0] == 'S' && cc->alpha2[1] == 'E') || + (cc->alpha2[0] == 'S' && cc->alpha2[1] == 'K') || + (cc->alpha2[0] == 'S' && cc->alpha2[1] == 'I')) { + return true; + } + } + + return false; +} + +static bool mwl_is_unknown_country(struct cc_info *cc) +{ + if (cc->alpha2[0] == '9' && cc->alpha2[1] == '9') { + return true; + } + + return false; +} + +static int lrd_fixup_channels(struct mwl_priv *priv, struct cc_info *info) +{ + if (mwl_is_world_region(info)) { + /* when configured for WW, firmware does not allow + * channels 12-14 to be configured, remove them here + * to keep mac80211 in sync with FW. + */ + if (priv->band_24.n_channels == ARRAY_SIZE(mwl_channels_24)) { + priv->band_24.n_channels -= NUM_WW_CHANNELS; + } + + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + } + else { + priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24);; + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + + if (mwl_is_en440_region(info) ) { + if (0 == (priv->radio_caps.capability & LRD_CAP_440) ) { + //If 440 isn't set in caps fw does not allow + //channels 149-165 to be configured. Remove + //them here to keep mac80211 in sync with FW. + priv->band_50.n_channels -= NUM_UNII3_CHANNELS; + } + } + } + + return 0; +} + + +static int mwl_init_firmware(struct mwl_priv *priv) +{ + int rc = 0; + const char *fw_name; + + /* Attempt to load mfg firmware first. + * + * If mfg_mode module parameter is set, mfg firmware is expected to be + * present so request_firmware() can be safely used. + * Otherwise, attempt to load mfg firmware from standard location + */ + fw_name = priv->if_ops.mwl_chip_tbl.mfg_image; + + if (mfg_mode) { + wiphy_info(priv->hw->wiphy, "%s: Manufacturing mode forced on!\n", MWL_DRV_NAME); + + priv->mfg_mode = true; + + /* request_firmware() may hang if firmware is not present and + * FW user mode helper support is enabled in kernel + */ + rc = request_firmware((const struct firmware **)&priv->fw_ucode, + fw_name, priv->dev); + } + else { + /* request_firmware_direct() will only find firmware if it is present in + * standard locations, but will not hang + */ + rc = request_firmware_direct((const struct firmware **)&priv->fw_ucode, + fw_name, priv->dev); + if (!rc) + { + /* mfg firmware found and loaded directly */ + priv->mfg_mode = true; + } + } + + if (rc) { + + if (!mfg_mode) { + + fw_name = priv->if_ops.mwl_chip_tbl.fw_image; + + rc = request_firmware((const struct firmware **)&priv->fw_ucode, + fw_name, priv->dev); + } + + if (rc) { + wiphy_err(priv->hw->wiphy, + "%s: cannot find firmware image <%s>\n", + MWL_DRV_NAME, fw_name); + + goto err_load_fw; + } + } + + wiphy_info(priv->hw->wiphy, "%s: found firmware image <%s>\n", + MWL_DRV_NAME, fw_name); + + rc = priv->if_ops.prog_fw(priv); + if (rc) { + if (rc != -EINPROGRESS) + wiphy_err(priv->hw->wiphy, + "%s: firmware download/init failed! <%s> %d\n", + MWL_DRV_NAME, fw_name, rc); + goto err_download_fw; + } + + if (cal_data_cfg) { + + wiphy_info(priv->hw->wiphy, + "Looking for cal file <%s>\n", cal_data_cfg); + + if ((request_firmware((const struct firmware **)&priv->cal_data, + cal_data_cfg, priv->dev)) < 0) + wiphy_info(priv->hw->wiphy, + "Cal data request_firmware() failed\n"); + } + +err_download_fw: + release_firmware(priv->fw_ucode); + +err_load_fw: + return rc; +} + +static void mwl_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw; + struct mwl_priv *priv; + + hw = (struct ieee80211_hw *)wiphy_priv(wiphy); + priv = hw->priv; + + wiphy_debug(hw->wiphy, "mwl_reg_notifier set=%d %s %c%c\n", priv->reg.regulatory_set, reg_initiator_name(request->initiator), request->alpha2[0], request->alpha2[1]); + + if (request->initiator == NL80211_REGDOM_SET_BY_DRIVER) { + + if ( 0 == memcmp(priv->reg.hint.alpha2, request->alpha2, sizeof(priv->reg.hint.alpha2)) ) { + memcpy(&priv->reg.cc, &priv->reg.hint, sizeof(priv->reg.cc)); + memset(priv->reg.hint.alpha2, 'x', sizeof(priv->reg.hint.alpha2)); + priv->reg.hint.region =(u32)-1; + priv->reg.dfs_region = request->dfs_region; + } + } +} + +#pragma pack(push,1) +typedef struct module_signature { + u8 sig[8]; + __le32 len; +} module_signature_t; + +typedef struct ww_pwr_entry +{ + u8 cc[2]; + __le16 rc; + __le32 len; + u8 data[0]; +} ww_pwr_entry_t; + +#pragma pack(pop) + +#define MODULE_SIG_STRING "~Module signature appended~\n" + +static int lrd_validate_db(const struct firmware *fw, struct mwl_priv *priv, int * payload_size) +{ + u8 *buf = (uint8_t*)fw->data; + int bSize = fw->size; + int markerlen = sizeof(MODULE_SIG_STRING)-1; + int x = 0; + struct ww_pwr_entry *entry; + struct module_signature *info; + struct ieee80211_hw *hw = priv->hw; + + wiphy_debug(hw->wiphy,"Validating %s\n", REG_PWR_DB_NAME); + + /* Verify signature marker is present */ + if (bSize < markerlen) { + wiphy_err(hw->wiphy, "%s too small to contain signature\n", REG_PWR_DB_NAME); + return -1; + } + + bSize -= markerlen; + + if (memcmp(buf+ bSize, MODULE_SIG_STRING, markerlen) != 0 ) { + wiphy_err(hw->wiphy, "Module signature marker not found %d\n", bSize); + return -1; + } + + /* Verify module signature info is present */ + if (bSize < sizeof(module_signature_t)) { + wiphy_err(hw->wiphy, "Module signature not found %d\n", bSize); + return -1; + } + + bSize -= sizeof(module_signature_t); + info = (module_signature_t*)(buf + bSize); + + /*Verify actual signature is present */ + if (bSize < le32_to_cpu(info->len)) { + wiphy_err(hw->wiphy, "Complete signature not present %d(%d)\n", bSize, le32_to_cpu(info->len)); + return -1; + } + + bSize -= le32_to_cpu(info->len); + + for (x=0; x < bSize;) { + entry = (ww_pwr_entry_t*)(buf + x); + + //Check header present + if ( (bSize - x) < sizeof(ww_pwr_entry_t)) { + wiphy_err(hw->wiphy, "Incomplete Tx Pw Entry header\n"); + return -1; + } + + //Move past header + x += sizeof(ww_pwr_entry_t); + + //Check data size + if ( (bSize - x) < le32_to_cpu(entry->len)) { + wiphy_err(hw->wiphy, "Incomplete Tx Pw Entry \n"); + return -1; + } + + //Move to next entry + x += le32_to_cpu(entry->len); + } + + if (x == bSize) { + wiphy_debug(hw->wiphy, "Successfully validated %s %d\n", REG_PWR_DB_NAME, bSize); + *payload_size = bSize; + return 0; + } + + wiphy_err(hw->wiphy, "Validation of %s failed.\n", REG_PWR_DB_NAME); + + return -1; +} + + +static int find_pwr_entry(struct mwl_priv *priv, u8 *cc, u16 rc, ww_pwr_entry_t**entry) +{ + struct ww_pwr_entry *tmp = NULL; + bool bMatch = false; + int x = 0; + int y = 0; + int idx = -1; + + //See if RC/CC is found in DB + for (x=0, y = 0; x < priv->reg.db_size; y++) { + tmp = (ww_pwr_entry_t*)(priv->reg.db + x); + + if (rc < 256) { + if (rc == le16_to_cpu(tmp->rc)) { + bMatch = true; + } + } + else { + u16 region = cpu_to_le16(rc); + if (0 == memcmp(tmp->cc, ®ion, sizeof(tmp->cc))) { + bMatch = true; + } + } + + if (bMatch) { + if (cc == NULL || 0 == memcmp(tmp->cc, cc, sizeof(tmp->cc))) { + if (entry) { + *entry = tmp; + } + idx = y; + break; + } + } + + //Move to next entry + x += sizeof(ww_pwr_entry_t) + le32_to_cpu(tmp->len); + } + + return idx; +} + +static void lrd_send_hint_no_lock(struct mwl_priv *priv, struct cc_info *cc) +{ + //Check region and CCin case either has changed. + if (0 == memcmp(&priv->reg.cc, cc, sizeof(priv->reg.cc))) { + //Aleady using, don't send again + return; + } + + wiphy_debug(priv->hw->wiphy,"Sending regulatory hint for %c%c\n", cc->alpha2[0], cc->alpha2[1]); + + //Fixup the channel set based on country/region + lrd_fixup_channels(priv, cc); + + memcpy(&priv->reg.hint, cc, sizeof(priv->reg.hint)); + + //Send driver hint + priv->reg.regulatory_set = true; + regulatory_hint(priv->hw->wiphy, cc->alpha2); +} + +static void lrd_send_hint(struct mwl_priv *priv, struct cc_info *cc) +{ + mutex_lock(&priv->reg.mutex); + lrd_send_hint_no_lock(priv, cc); + mutex_unlock(&priv->reg.mutex); +} + +int check_regdb(const char *alpha2); + +static void lrd_cc_cb(const struct firmware *fw, void *context) +{ + struct mwl_priv *priv = (struct mwl_priv*)context; + struct ww_pwr_entry *entry = NULL; + int idx = -1; + int payload_size; + int rc = 0; + + if (!fw) { + wiphy_info(priv->hw->wiphy,"/lib/firmware/%s not found.\n", REG_PWR_DB_NAME); + goto exit; + } + + if (priv->reg.db) { + kfree(priv->reg.db); + priv->reg.db = NULL; + priv->reg.db_size = 0; + } + + if (!lrd_validate_db(fw, priv, &payload_size)) { + priv->reg.db = kmemdup(fw->data, payload_size, GFP_KERNEL); + priv->reg.db_size = priv->reg.db ? payload_size : 0; + } + + release_firmware(fw); + + /* Find entry */ + wiphy_debug(priv->hw->wiphy, + "checking %s for region:%x country:%c%c\n", REG_PWR_DB_NAME, + priv->reg.otp.region, priv->reg.otp.alpha2[0], + priv->reg.otp.alpha2[1]); + + idx = find_pwr_entry(priv, mwl_is_unknown_country(&priv->reg.otp) ? + NULL : priv->reg.otp.alpha2, priv->reg.otp.region, &entry); + + if (!entry || le32_to_cpu(entry->len) <= 0) { + if (idx < 0) { + wiphy_warn(priv->hw->wiphy, + "Region %x country %c%c not found in %s.\n", + priv->reg.otp.region, priv->reg.otp.alpha2[0], + priv->reg.otp.alpha2[1], REG_PWR_DB_NAME); + } + goto exit; + } + + mutex_lock(&priv->reg.mutex); + if (!priv->reg.pn) { + rtnl_lock(); + rc = check_regdb(entry->cc); + rtnl_unlock(); + if (rc >= 0) { + /* Send to firmware */ + rc = lrd_fwcmd_lrd_set_power_table(priv->hw, idx, entry->data, + le32_to_cpu(entry->len)); + if (!rc) { + /* Queue event work */ + queue_work(priv->lrd_workq, &priv->reg.event); + } else { + idx = -100; + } + } + else { + wiphy_err(priv->hw->wiphy, + "Country Code %c%c not present in CRDA database.\n", + entry->cc[0], entry->cc[1]); + idx = -101; + } + } + mutex_unlock(&priv->reg.mutex); + +exit: + if (!mwl_is_unknown_country(&priv->reg.otp)) { + /* Send driver hint */ + lrd_send_hint(priv, &priv->reg.otp); + } else if (idx < 0) { + wiphy_err(priv->hw->wiphy, + "Failed to resolve region mapping %d, will not enable transmitter.\n", idx); + } +} + +void lrd_dump_max_pwr_table(struct mwl_priv *priv) +{ + u16 powlist[HAL_TRPC_ID_MAX]; + int ch_index, rate_index; + int edmac; + int rc; + + memset(powlist, 0, sizeof(powlist)); + pr_info("Power Table - Max per RateId:\n"); + pr_info(" CH, 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27\n"); + for (ch_index = 0; ch_index < ARRAY_SIZE(mwl_channels_24); ch_index++) { + int ch = mwl_channels_24[ch_index].hw_value; + rc = mwl_fwcmd_get_tx_powers(priv, &powlist[0], HOSTCMD_ACT_GET_MAX_TX_PWR, + ch, FREQ_BAND_2DOT4GHZ, CH_20_MHZ_WIDTH, NO_EXT_CHANNEL); + if (rc) { + pr_err("Failed to get max power for channel %d!\n", ch); + return; + } + pr_info("%3d, {", ch); + for (rate_index = 0; rate_index < HAL_TRPC_ID_2G_MAX; rate_index++) { + pr_cont("%2d%s", powlist[rate_index], rate_index == HAL_TRPC_ID_2G_MAX-1 ? "}\n" : ", "); + } + } + + pr_info("\n"); + memset(powlist, 0, sizeof(powlist)); + for (ch_index = 0; ch_index < ARRAY_SIZE(mwl_channels_50); ch_index++) { + int ch = mwl_channels_50[ch_index].hw_value; + rc = mwl_fwcmd_get_tx_powers(priv, &powlist[0], HOSTCMD_ACT_GET_MAX_TX_PWR, + ch, FREQ_BAND_5GHZ, CH_20_MHZ_WIDTH, NO_EXT_CHANNEL); + if (rc) { + pr_err("Failed to get max power for channel %d!\n", ch); + return; + } + pr_info("%3d, {", ch); + for (rate_index = 0; rate_index < HAL_TRPC_ID_5G_MAX; rate_index++) { + pr_cont("%2d%s", powlist[rate_index], rate_index == HAL_TRPC_ID_5G_MAX-1 ? "}\n" : ", "); + } + } + + if (!mwl_fwcmd_getEDMAC(priv, &edmac)) { + pr_info("EDMAC: %s\n", edmac ? "Enabled" : "Disabled"); + } + return; +} + +void mwl_set_ht_caps(struct mwl_priv *priv, + struct ieee80211_supported_band *band) +{ + struct ieee80211_hw *hw; + + hw = priv->hw; + + band->ht_cap.ht_supported = 1; + + band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; + band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS; + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + + if (band->band == NL80211_BAND_5GHZ || enable_24_40mhz) { + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + if (band->band == NL80211_BAND_2GHZ) { + band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; + } + } + + if ((priv->chip_type == MWL8997) && + (priv->ant_tx_num > 1)){ + band->ht_cap.cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + } + + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU); + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + band->ht_cap.mcs.rx_mask[0] = 0xff; + + if (priv->ant_rx_num > 1) + band->ht_cap.mcs.rx_mask[1] = 0xff; + +#if 0 + if (priv->antenna_rx == ANTENNA_RX_4_AUTO) + band->ht_cap.mcs.rx_mask[2] = 0xff; +#endif + band->ht_cap.mcs.rx_mask[4] = 0x01; + + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +void mwl_set_vht_caps(struct mwl_priv *priv, + struct ieee80211_supported_band *band) +{ + band->vht_cap.vht_supported = 1; + + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; + + if (priv->ant_tx_num > 1) + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; + + band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; + band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + + if (priv->chip_type == MWL8964) { + band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_160; + band->vht_cap.cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + } + + if (priv->ant_rx_num == 1) + band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffe); + else if (priv->ant_rx_num == 2) + band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffa); + else + band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xffea); + + if (priv->ant_tx_num == 1) { + band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffe); + } else if (priv->ant_tx_num == 2) { + band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffa); + } else + band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xffea); + + if (band->vht_cap.cap & (IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) { + band->vht_cap.cap |= + ((priv->ant_tx_num - 1) << + IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT) & + IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK; + } + + if (band->vht_cap.cap & (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | + IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)) { + band->vht_cap.cap |= + ((priv->ant_tx_num - 1) << + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT) & + IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK; + } +} + +static struct ieee80211_iface_combination* +mwl_alloc_intf_combos(const struct ieee80211_iface_combination *c, int n_c) +{ + int size = 0; + int x = 0; + u8 *end = 0; + struct ieee80211_iface_combination *combo = NULL; + + if (!c) { + return NULL; + } + + size = sizeof(struct ieee80211_iface_combination) * n_c; + for (x = 0; x < n_c; x++) { + //figure out size of limits + size += sizeof(struct ieee80211_iface_limit) * c[x].n_limits; + } + + //Allocate memory + combo = (struct ieee80211_iface_combination *)kzalloc(size, GFP_KERNEL); + + if (combo) { + //Copy interface combination array + memcpy(combo, c, sizeof(struct ieee80211_iface_combination) * n_c); + + end = ((u8*)combo) + (sizeof(struct ieee80211_iface_combination) * n_c); + + //Copy limit arrays + for (x = 0; x < combo->n_limits; x++) { + //Fix limits array to point to end of combo array + combo[x].limits = (struct ieee80211_iface_limit*)end; + memcpy((void*)combo[x].limits, c[x].limits, sizeof(struct ieee80211_iface_limit)*combo[x].n_limits); + end += sizeof(struct ieee80211_iface_limit) * combo[x].n_limits; + } + } + + return combo; +} + +static void lrd_adjust_iface_combo(struct mwl_priv *priv, struct ieee80211_iface_combination *combos, int n_c) +{ + int x = 0; + int y = 0; + u16 max_intf = (u16)(priv->radio_caps.num_mac); + + if (NULL == combos) { + return; + } + + for (x = 0; x < n_c; x++) { + if (combos[x].max_interfaces > max_intf) { + wiphy_info(priv->hw->wiphy,"Adjusting combo %d's number of supported interfaces to %d\n", x, max_intf); + + combos[x].max_interfaces = max_intf; + + for (y = 0; y < combos[x].n_limits; y++) { + if (combos[x].limits[y].max > max_intf) { + ((struct ieee80211_iface_limit*)combos[x].limits)[y].max = max_intf; + } + } + } + } +} + +static int lrd_set_su_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + + //Allocate and adjust interface combinations + hw->wiphy->iface_combinations = mwl_alloc_intf_combos(if_su_comb, ARRAY_SIZE(if_su_comb)); + if (hw->wiphy->iface_combinations) { + hw->wiphy->n_iface_combinations = ARRAY_SIZE(if_su_comb); + + lrd_adjust_iface_combo( priv, + (struct ieee80211_iface_combination *)hw->wiphy->iface_combinations, + hw->wiphy->n_iface_combinations); + } + +#ifdef CONFIG_PM + if (priv->wow.capable) { + hw->wiphy->wowlan = &lrd_wowlan_support; + /* max number of SSIDs device can scan for */ + hw->wiphy->max_sched_scan_ssids = 1; + } +#endif + + /* Register for monitor interfaces, to work around mac80211 bug */ + ieee80211_hw_set(hw, WANT_MONITOR_VIF); + + if (hw->wiphy->iface_combinations) { + /* Caller will do actual allocation */ + /*Note: This assumes first combo element contains the max number of interfaces */ + hw->wiphy->n_addresses = hw->wiphy->iface_combinations[0].max_interfaces; + } + + return 0; +} + +static int lrd_set_st_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + int x = 0; + + //Allocate and adjust interface combinations + hw->wiphy->iface_combinations = mwl_alloc_intf_combos(if_st_comb, ARRAY_SIZE(if_st_comb));; + if (hw->wiphy->iface_combinations) { + hw->wiphy->n_iface_combinations = ARRAY_SIZE(if_st_comb); + + lrd_adjust_iface_combo( priv, + (struct ieee80211_iface_combination *)hw->wiphy->iface_combinations, + hw->wiphy->n_iface_combinations); + } + + /* sterling fw supports fewer AP interfaces - adjust for that here*/ + priv->ap_macids_supported = 0; + for (x = 0; x < SYSADPT_NUM_OF_ST_AP; x++) { + priv->ap_macids_supported |= 1 << x; + } + + if (hw->wiphy->iface_combinations) { + /* Caller will do actual allocation */ + /*Note: This assumes first combo element contains the max number of interfaces */ + hw->wiphy->n_addresses = hw->wiphy->iface_combinations[0].max_interfaces; + } + + return 0; +} + +void mwl_ieee80211_free_hw(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + + if (hw) { + //Free driver allocated memory + if (hw->wiphy->iface_combinations) { + kfree(hw->wiphy->iface_combinations); + hw->wiphy->iface_combinations = NULL; + } + + if (hw->wiphy->n_addresses) { + kfree(hw->wiphy->addresses); + hw->wiphy->addresses = NULL; + } + } + + ieee80211_free_hw(hw); + +} +EXPORT_SYMBOL(mwl_ieee80211_free_hw); + +static void mwl_set_ieee_hw_caps(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + int x = 0; + u64 addr = 0; + u64 addr1 = 0; + + SET_IEEE80211_DEV(hw, priv->dev); + SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr); + + hw->extra_tx_headroom = SYSADPT_TX_MIN_BYTES_HEADROOM; + hw->queues = SYSADPT_TX_WMM_QUEUES; + + /* Set rssi values to dBm */ + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, HAS_RATE_CONTROL); + + /* Ask mac80211 not to trigger PS mode + * based on PM bit of incoming frames. + */ + ieee80211_hw_set(hw, AP_LINK_PS); + ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK); + ieee80211_hw_set(hw, MFP_CAPABLE); + ieee80211_hw_set(hw, SPECTRUM_MGMT); + if (priv->chip_type == MWL8997) { + ieee80211_hw_set(hw, SUPPORTS_PS); + } + + /* Ask mac80211 to use standard NULL data packets + * instead of QOS NULL data packets. + * This works around issues in both mac80211 and the driver + * that prevent QOS NULL data packets from being transmitted correctly + */ + ieee80211_hw_set(hw, DOESNT_SUPPORT_QOS_NDP); + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; + hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + + hw->wiphy->flags |= WIPHY_FLAG_4ADDR_AP; + hw->wiphy->flags |= WIPHY_FLAG_4ADDR_STATION; + + hw->wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN; + hw->wiphy->features |= NL80211_FEATURE_AP_SCAN; + hw->wiphy->features &= ~NL80211_FEATURE_MAC_ON_CREATE; + + hw->wiphy->max_remain_on_channel_duration = 5000; + + hw->vif_data_size = sizeof(struct mwl_vif); + hw->sta_data_size = sizeof(struct mwl_sta); + + hw->wiphy->available_antennas_tx = MWL_8997_DEF_TX_ANT_BMP; + hw->wiphy->available_antennas_rx = MWL_8997_DEF_RX_ANT_BMP; + + hw->wiphy->interface_modes = 0; + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_GO); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_CLIENT); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); + + /* set up band information for 2.4G */ + if (!priv->disable_2g) { + mwl_set_ht_caps(priv, &priv->band_24); + mwl_set_vht_caps(priv, &priv->band_24); + + hw->wiphy->bands[NL80211_BAND_2GHZ] = &priv->band_24; + } + + /* set up band information for 5G */ + if (!priv->disable_5g) { + mwl_set_ht_caps(priv, &priv->band_50); + mwl_set_vht_caps(priv, &priv->band_50); + + hw->wiphy->bands[NL80211_BAND_5GHZ] = &priv->band_50; + } + + if (priv->radio_caps.capability & LRD_CAP_SU60) { + lrd_set_su_caps(priv); + } + else { + lrd_set_st_caps(priv); + } + + /* Allocated interface address pool */ + hw->wiphy->addresses = (struct mac_address*)kzalloc(sizeof(struct mac_address) * hw->wiphy->n_addresses, GFP_KERNEL); + addr = be64_to_cpup((u64*)priv->hw_data.mac_addr); + addr = addr >> 16; + + for (x=0; x < hw->wiphy->n_addresses; x++) { + addr1 = cpu_to_be64(addr<<16); + memcpy(hw->wiphy->addresses[x].addr, &addr1, sizeof(struct mac_address)); + + //First address in pool must be the same as permanent adddress, increment after copy + if (priv->radio_caps.capability & LRD_CAP_BT_SEQ) { + if (x) { + //Add Locally Administered Bit + hw->wiphy->addresses[x].addr[0] |= 0x02; + addr++; + } + } + else { + if (priv->radio_caps.capability & LRD_CAP_BT_DUP && enable_bt_dup ) { + //Add Locally Administered Bit + hw->wiphy->addresses[x].addr[0] |= 0x02; + + //Fix up permanent address + hw->wiphy->perm_addr[0] |= 0x02; + } + addr++; + } + } + + lrd_set_vendor_commands(hw->wiphy); + lrd_set_vendor_events(hw->wiphy); +} + +static int lrd_regd_init(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + int rc = 0; + + /* hook regulatory domain change notification */ + hw->wiphy->reg_notifier = mwl_reg_notifier; + hw->wiphy->regulatory_flags |= REGULATORY_STRICT_REG; + + /*Reset alpha so we send driver hint if this is a reinit*/ + memset(priv->reg.cc.alpha2, 0, sizeof(priv->reg.cc.alpha2)); + priv->reg.regulatory_set = false; + priv->reg.pn = 0; + + if (mwl_fwcmd_get_fw_region_code(hw, &priv->reg.otp.region)) { + /* If we fail to retrieve region, default to WW */ + wiphy_err(priv->hw->wiphy, "Failed to retrieve regulatory region.\n"); + + priv->reg.otp.region = REG_CODE_WW; + memset(priv->reg.otp.alpha2,'9', sizeof(priv->reg.otp.alpha2)); + + if (!priv->mfg_mode) { + rc = -1; + } + + goto done; + } + + if (mwl_fwcmd_get_region_mapping(priv->hw, (struct mwl_region_mapping*)&priv->reg.otp.alpha2)) { + /* If we fail to retrieve mapping, default to 99 */ + wiphy_err(priv->hw->wiphy, "Failed to retrieve region mapping\n"); + memset(priv->reg.otp.alpha2,'9', sizeof(priv->reg.otp.alpha2)); + } + + if (!mwl_is_unknown_country(&priv->reg.otp)) { + //If we know the country, fixup channel set now and not + //wait for the callback. + lrd_fixup_channels(priv, &priv->reg.otp); + } + +done: + return rc; +} + +static void lrd_request_cc_db(struct mwl_priv *priv) +{ + if (!mwl_is_unknown_country(&priv->reg.otp)) { + lrd_send_hint(priv, &priv->reg.otp); + } + + request_firmware_nowait( THIS_MODULE, true, REG_PWR_DB_NAME, priv->dev, GFP_KERNEL, priv, lrd_cc_cb); +} + +static void remain_on_channel_expire(struct timer_list *t) +{ + struct mwl_priv *priv = from_timer(priv, t, roc.roc_timer); + struct ieee80211_hw *hw = priv->hw; + + priv->roc.tmr_running = false; + if (!priv->roc.in_progress) + return; + + ieee80211_remain_on_channel_expired(hw); +} + +void timer_routine(struct timer_list *t) +{ + struct mwl_priv *priv = from_timer(priv, t, period_timer); + struct mwl_ampdu_stream *stream; + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + int i; + + spin_lock_bh(&priv->stream_lock); + for (i = 0; i < SYSADPT_TX_AMPDU_QUEUES; i++) { + stream = &priv->ampdu[i]; + + if (stream->state == AMPDU_STREAM_ACTIVE) { + sta_info = mwl_dev_get_sta(stream->sta); + tx_stats = &sta_info->tx_stats[stream->tid]; + + if ((jiffies - tx_stats->start_time > HZ) && + (tx_stats->pkts < SYSADPT_AMPDU_PACKET_THRESHOLD)) { + ieee80211_stop_tx_ba_session(stream->sta, stream->tid); + } + + if (jiffies - tx_stats->start_time > HZ) { + tx_stats->pkts = 0; + tx_stats->start_time = jiffies; + } + } + } + spin_unlock_bh(&priv->stream_lock); + + mod_timer(&priv->period_timer, jiffies + + msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); +} + +void ds_routine(struct timer_list *t) +{ + struct mwl_priv *priv = from_timer(priv, t, ds_timer); + struct ieee80211_hw *hw = priv->hw; + struct ieee80211_conf *conf = &hw->conf; + + if( priv->ds_enable != DS_ENABLE_ON || priv->shutdown) { + return; + } + + if (conf->flags & IEEE80211_CONF_IDLE) { + queue_work(priv->lrd_workq, &priv->ds_work); + } +} + +void lrd_cc_event(struct work_struct *work) +{ + struct mwl_priv *priv = container_of(work,struct mwl_priv, reg.event); + struct ieee80211_hw *hw = priv->hw; + struct cc_info info; + + u32 pn = 0; + u32 result = 0; + bool valid = false; + + wiphy_debug(hw->wiphy, "Regulatory Event running...\n"); + + mutex_lock(&priv->reg.mutex); + + /* Add delay to allow firmware to decrypt power table blob sent + this especially important on multicore CPU where workqueue + is scheduled immediately on the other core */ + lrdmwl_delay(1000); + + if (!lrd_fwcmd_lrd_get_power_table_result(hw, &result, &pn)) + valid = !result && pn >= priv->reg.pn; + + if (!valid) { + wiphy_err(hw->wiphy, + "Power table entry failed firmware validation %s.\n", + priv->reg.regulatory_set ? "reverting to OTP" : ""); + goto exit; + } + + /* Get current country code and region */ + if (mwl_fwcmd_get_region_mapping(hw, (struct mwl_region_mapping *)info.alpha2)) { + /* If we fail to retrieve mapping, default to WW */ + wiphy_err(hw->wiphy, "Failed to retrieve region mapping.\n"); + valid = false; + goto exit; + } + + /* Get region code while we hold reg.mutex */ + if (mwl_fwcmd_get_fw_region_code(hw, &info.region)) { + /* If we fail to retrieve region, default to WW */ + wiphy_err(hw->wiphy, "Failed to retrieve regulatory region.\n"); + valid = false; + goto exit; + } + + /* Validate CC exists in CRDA */ + wiphy_debug(hw->wiphy, "Checking CRDA for Country Code %c%c.\n", + info.alpha2[0], info.alpha2[1]); + + rtnl_lock(); + + if (check_regdb(info.alpha2) < 0) { + wiphy_err(hw->wiphy, + "Country Code %x%x does not exist in CRDA database.\n", + info.alpha2[0], info.alpha2[1]); + valid = false; + } + + rtnl_unlock(); + +exit: + if (valid) { + wiphy_debug(hw->wiphy, "Country Code %c%c is valid.\n", + info.alpha2[0], info.alpha2[1]); + + /* Update driver hint */ + lrd_send_hint_no_lock(priv, &info); + } + else if (priv->reg.regulatory_set) { + /* Reset power table */ + lrd_fwcmd_lrd_reset_power_table(priv->hw); + + /* Update driver hint */ + lrd_send_hint_no_lock(priv, &priv->reg.otp); + } + + //lrd_dump_max_pwr_table(priv); + + mutex_unlock(&priv->reg.mutex); +} + +void lrd_awm_routine(struct timer_list *t) +{ + struct mwl_priv *priv = from_timer(priv, t, reg.timer_awm); + + queue_work(priv->lrd_workq, &priv->reg.awm); +} + +static void lrd_awm_expire(struct work_struct *work) +{ + struct mwl_priv *priv = container_of(work, + struct mwl_priv, reg.awm); + + wiphy_debug(priv->hw->wiphy, "AWM Event running %d...\n", + priv->reg.regulatory_set); + + mutex_lock(&priv->reg.mutex); + + if (priv->reg.regulatory_set) { + /* Reset power table */ + lrd_fwcmd_lrd_reset_power_table(priv->hw); + + lrd_send_hint_no_lock(priv, &priv->reg.otp); + } + + //lrd_dump_max_pwr_table(priv); + + mutex_unlock(&priv->reg.mutex); +} + +static void mwl_ds_work(struct work_struct *work) +{ + struct mwl_priv *priv = container_of(work, + struct mwl_priv, ds_work); + + if (priv->mfg_mode || priv->recovery_in_progress) { + return; + } + + mwl_fwcmd_enter_deepsleep(priv->hw); +} + +static void lrd_stop_shutdown_workq(struct work_struct *work) +{ + struct mwl_priv *priv = container_of(work, struct mwl_priv, + stop_shutdown_work.work); + + mwl_shutdown_sw(priv, true); + + if (priv->if_ops.down_pwr != NULL) + priv->if_ops.down_pwr(priv); +} + +static int mwl_wl_init(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_if_ops *if_ops = &priv->if_ops; + int rc = 0; + + /* Setup queues */ + priv->rx_defer_workq = alloc_workqueue("lrdwifi-rx_defer_workq", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1); + INIT_WORK(&priv->rx_defer_work, mwl_rx_defered_handler); + skb_queue_head_init(&priv->rx_defer_skb_q); + + priv->lrd_workq = alloc_workqueue("lrdwifi-workq", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1); + INIT_WORK(&priv->ds_work, mwl_ds_work); + INIT_DELAYED_WORK(&priv->stop_shutdown_work, lrd_stop_shutdown_workq); + INIT_WORK(&priv->reg.event, lrd_cc_event); + INIT_WORK(&priv->reg.awm, lrd_awm_expire); + + priv->reg.regulatory_set = false; + priv->disable_2g = false; + priv->disable_5g = false; + + if (!SISO_mode) + priv->ant_tx_bmp = if_ops->mwl_chip_tbl.antenna_tx; + else + priv->ant_tx_bmp = SISO_mode & MWL_8997_DEF_TX_ANT_BMP; + priv->ant_tx_num = MWL_TXANT_BMP_TO_NUM(priv->ant_tx_bmp); + + if (!SISO_mode) + priv->ant_rx_bmp = if_ops->mwl_chip_tbl.antenna_rx; + else + priv->ant_rx_bmp = SISO_mode & MWL_8997_DEF_RX_ANT_BMP; + priv->ant_rx_num = MWL_RXANT_BMP_TO_NUM(priv->ant_rx_bmp); + + priv->ds_enable = priv->host_if == MWL_IF_SDIO ? ds_enable : DS_ENABLE_OFF; + priv->ps_mode = 0; + + priv->ap_macids_supported = 0x0000ffff; + priv->sta_macids_supported = ((1UL << SYSADPT_NUM_OF_STA) - 1) << 16; + priv->adhoc_macids_supported = 0x00000001; + priv->macids_used = 0; + + INIT_LIST_HEAD(&priv->vif_list); + INIT_LIST_HEAD(&priv->sta_list); + + /* Set default radio state, preamble and wmm */ + priv->radio_on = false; + priv->radio_short_preamble = false; + priv->wmm_enabled = false; + + priv->powinited = 0; + priv->csa_active = false; + priv->dfs_chirp_count_min = 5; + priv->dfs_chirp_time_interval = 1000; + priv->dfs_pw_filter = 0; + priv->dfs_min_num_radar = 5; + priv->dfs_min_pri_count = 4; + + /* Handle watchdog ba events */ + INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events); + INIT_WORK(&priv->chnl_switch_handle, mwl_chnl_switch_event); + + priv->is_tx_done_schedule = false; + priv->qe_trigger_num = 0; + priv->qe_trigger_time = jiffies; + priv->txq_limit = SYSADPT_TX_QUEUE_LIMIT; + + priv->is_rx_defer_schedule = false; + priv->recv_limit = SYSADPT_RECEIVE_LIMIT; + + priv->cmd_timeout = false; + + mutex_init(&priv->fwcmd_mutex); + mutex_init(&priv->reg.mutex); + spin_lock_init(&priv->tx_desc_lock); + spin_lock_init(&priv->vif_lock); + spin_lock_init(&priv->sta_lock); + spin_lock_init(&priv->stream_lock); + + init_waitqueue_head(&priv->tx_flush_wq); + + atomic_set(&priv->null_scan_count, null_scan_count); + + /* set up band information for 2.4G */ + memset(&priv->band_24, 0,sizeof(struct ieee80211_supported_band)); + if (!priv->disable_2g) { + BUILD_BUG_ON(sizeof(priv->channels_24) != sizeof(mwl_channels_24)); + memcpy(priv->channels_24, mwl_channels_24,sizeof(mwl_channels_24)); + + BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24)); + memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24)); + + priv->band_24.band = NL80211_BAND_2GHZ; + priv->band_24.channels = priv->channels_24; + priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24); + priv->band_24.bitrates = priv->rates_24; + priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24); + } + + /* set up band information for 5G */ + memset(&priv->band_50, 0, sizeof(struct ieee80211_supported_band)); + if (!priv->disable_5g) { + BUILD_BUG_ON(sizeof(priv->channels_50) != sizeof(mwl_channels_50)); + memcpy(priv->channels_50, mwl_channels_50,sizeof(mwl_channels_50)); + + BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50)); + memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50)); + + priv->band_50.band = NL80211_BAND_5GHZ; + priv->band_50.channels = priv->channels_50; + priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); + priv->band_50.bitrates = priv->rates_50; + priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50); + } + + /* bus specific device registration */ + if (priv->if_ops.register_dev) + rc = priv->if_ops.register_dev(priv); + else + rc = -ENXIO; + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to register device\n", + MWL_DRV_NAME); + goto err_wl_init; + } + + /* Begin querying FW for information */ + rc = mwl_fwcmd_get_hw_specs(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to get HW specifications\n", + MWL_DRV_NAME); + goto err_wl_init; + } + else { + if (priv->hw_data.fw_release_num == NOT_LRD_HW) { + wiphy_err(hw->wiphy, + "Detected non Laird hardware: 0x%x\n", priv->hw_data.fw_release_num); + rc = -ENODEV; + goto err_wl_init; + } + } + + /* interface specific initialization after fw is loaded and hw specs retrieved .. */ + if (priv->if_ops.init_if_post) { + if (priv->if_ops.init_if_post(priv)) { + goto err_wl_init; + } + } + + if (!priv->mfg_mode) { + rc = lrd_fwcmd_lrd_get_caps(hw, &priv->radio_caps); + if (rc) { + wiphy_err(hw->wiphy, "Fail to retrieve radio capabilities %x\n", rc); + memset(&priv->radio_caps, 0, sizeof(priv->radio_caps)); + } + + if (priv->ant_gain_adjust) { + rc = lrd_fwcmd_lrd_set_ant_gain_adjust(hw, priv->ant_gain_adjust); + if (rc) { + wiphy_err(hw->wiphy, "Antenna gain adjustment 0x%x specified but failed to send!\n", priv->ant_gain_adjust); + goto err_wl_init; + } + } + } + + if (priv->radio_caps.capability & LRD_CAP_SU60) { + rc = mwl_thermal_register(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to register thermal framework\n", + MWL_DRV_NAME); + goto err_wl_init; + } + } + + rc = mwl_fwcmd_set_hw_specs(priv->hw); + if (rc) { + wiphy_err(priv->hw->wiphy, "%s: fail to set HW specifications\n", + MWL_DRV_NAME); + goto err_wl_init; + } + + rc = mwl_fwcmd_set_cfg_data(hw, 2); + if(rc) { + wiphy_err(hw->wiphy, "%s: fail to download calibaration data\n", + MWL_DRV_NAME); + } + + /* Initialize regulatory info */ + if (lrd_regd_init(priv)) { + wiphy_err(hw->wiphy, "%s: fail to register regulatory\n", MWL_DRV_NAME); + goto err_wl_init; + } + + rc = mwl_fwcmd_dump_otp_data(hw); + if (rc) { + wiphy_info(hw->wiphy, "OTP Dump failed\n"); + } + + mwl_fwcmd_radio_disable(hw); + mwl_fwcmd_rf_antenna(hw, priv->ant_tx_bmp, priv->ant_rx_bmp); + + if (priv->stop_shutdown) + queue_delayed_work(priv->lrd_workq, &priv->stop_shutdown_work, + msecs_to_jiffies(stop_shutdown_timeout)); + + /* Set IEEE HW Capabilities */ + mwl_set_ieee_hw_caps(priv); + + /* Register with MAC80211 */ + rc = ieee80211_register_hw(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to register hw\n", + MWL_DRV_NAME); + goto err_wl_init; + } + + /* Request Regulatory DB */ + lrd_request_cc_db(priv); + + /* Success - dump relevant info to log */ + wiphy_info(hw->wiphy, "Radio Type %s%s%s (0x%x)\n", (priv->radio_caps.capability & LRD_CAP_SU60)?"SU60":"ST60", + (priv->radio_caps.capability & LRD_CAP_440)?"_440":"", + (priv->radio_caps.capability & LRD_CAP_SOM8MP)?"_SOM8MP":"", + priv->radio_caps.capability); + wiphy_info(hw->wiphy, "Num mac %d : OTP Version (%d)\n", priv->radio_caps.num_mac, priv->radio_caps.version); + wiphy_info(hw->wiphy, "Firmware version: %d.%d.%d.%d\n", ((priv->hw_data.fw_release_num >> 24) & 0xff), + ((priv->hw_data.fw_release_num >> 16) & 0xff), + ((priv->hw_data.fw_release_num >> 8) & 0xff), + ((priv->hw_data.fw_release_num >> 0) & 0xff)); + wiphy_info(hw->wiphy, "Firmware OTP region: %x, country: %c%c\n", priv->reg.otp.region, priv->reg.otp.alpha2[0], priv->reg.otp.alpha2[1]); + wiphy_info(priv->hw->wiphy, "Deep Sleep is %s\n", + priv->ds_enable == DS_ENABLE_ON ? "enabled": "disabled"); + + wiphy_info(priv->hw->wiphy, "2G %s, 5G %s\n", + priv->disable_2g ? "disabled" : "enabled", + priv->disable_5g ? "disabled" : "enabled"); + + wiphy_info(priv->hw->wiphy, "%d TX antennas, %d RX antennas. (%08x)/(%08x)\n", + priv->ant_tx_num, priv->ant_rx_num, priv->ant_tx_bmp, priv->ant_rx_bmp); + + if (!priv->mfg_mode && priv->ant_gain_adjust) + wiphy_info(hw->wiphy, "Antenna gain adjustment in effect: 0x%x\n", priv->ant_gain_adjust); + + lrd_send_fw_event(priv->dev, true); + + return rc; + +err_wl_init: + destroy_workqueue(priv->lrd_workq); + destroy_workqueue(priv->rx_defer_workq); + + return rc; +} + +void mwl_wl_deinit(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + +#ifdef CONFIG_DEBUG_FS + mwl_debugfs_remove(hw); +#endif + + cancel_delayed_work_sync(&priv->stop_shutdown_work); + + device_init_wakeup(priv->dev, false); + lrd_send_fw_event(priv->dev, false); + + // priv->shutdown must be set prior to ieee80211_unregister_hw() so that + // it is set before call to mwl_mac80211_stop() + priv->shutdown = true; + + //Stop Timers + del_timer_sync(&priv->period_timer); + del_timer_sync(&priv->ds_timer); + del_timer_sync(&priv->roc.roc_timer); + del_timer_sync(&priv->reg.timer_awm); + + ieee80211_unregister_hw(hw); + + mwl_thermal_unregister(priv); + + /*Cancel iee80211 queue work itmes */ + cancel_work_sync(&priv->watchdog_ba_handle); + cancel_work_sync(&priv->chnl_switch_handle); + + /* Cancel/Destroy allocated queues */ + if (priv->restart_workq) { + cancel_work_sync(&priv->restart_work); + destroy_workqueue(priv->restart_workq); + } + + cancel_work_sync(&priv->rx_defer_work); + destroy_workqueue(priv->rx_defer_workq); + skb_queue_purge(&priv->rx_defer_skb_q); + + mwl_fwcmd_reset(hw); + + //cancel everything running on lrd work queue and destroy it + cancel_work_sync(&priv->ds_work); + cancel_work_sync(&priv->reg.event); + cancel_work_sync(&priv->reg.awm); + destroy_workqueue(priv->lrd_workq); + + if (priv->reg.db) { + kfree(priv->reg.db); + } + +#ifdef CONFIG_SYSFS + lrd_sysfs_remove(hw); +#endif +} +EXPORT_SYMBOL_GPL(mwl_wl_deinit); + + +void mwl_restart_ds_timer(struct mwl_priv *priv, bool force) +{ + struct ieee80211_conf *conf = &priv->hw->conf; + + if(priv->ds_enable != DS_ENABLE_ON || priv->shutdown || priv->recovery_in_progress) { + return; + } + + if ((conf->flags & IEEE80211_CONF_IDLE) || force) { + mod_timer(&priv->ds_timer, jiffies + msecs_to_jiffies(1000)); + } +} +EXPORT_SYMBOL_GPL(mwl_restart_ds_timer); + +void mwl_delete_ds_timer(struct mwl_priv *priv) +{ + del_timer_sync(&priv->ds_timer); + + //Make sure no work item outstanding + cancel_work_sync(&priv->ds_work); +} +EXPORT_SYMBOL_GPL(mwl_delete_ds_timer); + +void mwl_enable_ds(struct mwl_priv * priv) +{ + if (priv->ds_enable == DS_ENABLE_ON || priv->mfg_mode) { + return; + } + + priv->ds_enable = DS_ENABLE_ON; + mwl_restart_ds_timer(priv,false); + wiphy_info(priv->hw->wiphy, "Enabled DS\n"); +} +EXPORT_SYMBOL_GPL(mwl_enable_ds); + +void mwl_resume_ds(struct mwl_priv * priv) +{ + if (priv->ds_enable != DS_ENABLE_PAUSE || priv->mfg_mode) { + return; + } + + priv->ds_enable = DS_ENABLE_ON; + mwl_restart_ds_timer(priv,false); + wiphy_info(priv->hw->wiphy, "Resumed DS\n"); +} +EXPORT_SYMBOL_GPL(mwl_resume_ds); + +void mwl_pause_ds(struct mwl_priv* priv) +{ + struct ieee80211_hw *hw = priv->hw; + + if(priv->ds_enable != DS_ENABLE_ON) { + return; + } + + priv->ds_enable = DS_ENABLE_PAUSE; + + mwl_delete_ds_timer(priv); + + //Make sure card is awake when this function returns + mwl_fwcmd_exit_deepsleep(hw); + + wiphy_info(priv->hw->wiphy, "Paused DS\n"); +} +EXPORT_SYMBOL_GPL(mwl_pause_ds); + +void mwl_disable_ds(struct mwl_priv * priv) +{ + struct ieee80211_hw *hw = priv->hw; + + if(priv->ds_enable == DS_ENABLE_OFF) { + return; + } + + priv->ds_enable = DS_ENABLE_OFF; + + //Kill timer + mwl_delete_ds_timer(priv); + + // Don't try to access the radio while non-responsive + if (!priv->recovery_in_progress) { + //Make sure card is awake when this function returns + mwl_fwcmd_exit_deepsleep(hw); + } + + wiphy_info(priv->hw->wiphy, "Disabled DS\n"); +} + +EXPORT_SYMBOL_GPL(mwl_disable_ds); + +static void lrd_radio_recovery_work(struct work_struct *work) +{ + struct mwl_priv *priv = container_of(work, + struct mwl_priv, restart_work); + int ret; + + wiphy_debug(priv->hw->wiphy, "%s: Initiating radio recovery!!\n", + MWL_DRV_NAME); + + // Restart radio hardware + ret = priv->if_ops.hardware_restart(priv); + if (ret) { + wiphy_err(priv->hw->wiphy, "%s: Unable to restart radio!!\n", + MWL_DRV_NAME); + priv->recovery_in_progress = false; + priv->recovery_owner = NULL; + } +} + +void lrd_radio_recovery(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + + wiphy_info(priv->hw->wiphy, "%s: Radio recovery requested!\n", __func__); + if (priv->recovery_in_progress) + { + wiphy_info(priv->hw->wiphy, "recovery_in_progress, skipping\n"); + return; + } + + priv->recovery_in_progress = true; + priv->recovery_in_progress_full = true; + + if (!priv->if_ops.hardware_restart) + { + wiphy_info(hw->wiphy, "%s: Radio recovery requested but no restart handler configured!\n", + MWL_DRV_NAME); + return; + } + + // Initialize workq if it hasn't been already + if (!priv->restart_workq) + { + priv->restart_workq = alloc_workqueue("lrdwifi-restart_workq", + WQ_MEM_RECLAIM | WQ_UNBOUND | WQ_HIGHPRI, 1); + INIT_WORK(&priv->restart_work, lrd_radio_recovery_work); + } + + queue_work(priv->restart_workq, &priv->restart_work); +} +EXPORT_SYMBOL_GPL(lrd_radio_recovery); + +int mwl_add_card(void *card, struct mwl_if_ops *if_ops, + struct device_node *of_node) +{ + struct ieee80211_hw *hw; + struct mwl_priv *priv; + int rc = 0; + + hw = ieee80211_alloc_hw(sizeof(*priv), &mwl_mac80211_ops); + if (!hw) { + pr_err("%s: ieee80211 alloc failed\n", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_alloc_hw; + } + + priv = hw->priv; + priv->hw = hw; + priv->intf = card; + + priv->init_complete = false; + + priv->tx_amsdu_enable = tx_amsdu_enable; + +#ifdef CONFIG_LRDMWL_FIPS + priv->host_crypto = fips_enabled && fips_wifi_enabled; + if (priv->host_crypto) + host_crypto_mode = 1; +#endif + + // BZ19005: host_crypto_mode module parameter to force host crypto + // in most cases, driver will now automatically select hw/host crypto + // exception: station mode with invalid cipher combinations + // station mode with UC:HW/MC:Host (e.g. UC:CCMP-128, MC:GCMP-256) + if (host_crypto_mode) + priv->host_crypto = 1; + + if (priv->host_crypto) + wiphy_info(priv->hw->wiphy, "Using host crypto.\n"); + + /* Save interface specific operations in adapter */ + memmove(&priv->if_ops, if_ops, sizeof(struct mwl_if_ops)); + + /* card specific initialization has been deferred until now .. */ + if (priv->if_ops.init_if) { + if (priv->if_ops.init_if(priv)) + goto err_init_if; + } + + rc = lrd_probe_of_wowlan(priv, of_node); + if (rc == -EPROBE_DEFER) + goto err_of; + + priv->ant_gain_adjust = ant_gain_adjust; + if (of_node) { + priv->stop_shutdown = stop_shutdown || + of_property_read_bool(of_node, "remove-power-on-link-down"); + + /* + * If antenna gain adjustment is specified as both module parameter and + * in device tree, device tree wins + */ + of_property_read_u32(of_node, "ant-gain-adjust", &priv->ant_gain_adjust); + } + + /* Setup timers */ + timer_setup(&priv->roc.roc_timer, remain_on_channel_expire, 0); + timer_setup(&priv->period_timer, timer_routine, 0); + timer_setup(&priv->ds_timer, ds_routine, 0); + timer_setup(&priv->reg.timer_awm, lrd_awm_routine, 0); + + rc = mwl_fw_dnld_and_init(priv); + + // mwl_fw_dnld_and_init returns -EINPROGRESS when target is reset + // as part of the initialization sequence. This is normal behavior + // for USB at initial powerup, and for PCI in scenarios where firmware + // has already been loaded and a Function Level Reset is initiated to restart. + // In the case of PCI, the reset/restart handler is responsible for + // calling mwl_fw_dnld_and_init again, but remainder of this init sequence + // must complete successfully so driver remains loaded. + if (rc) { + if ((rc == -EINPROGRESS) && (priv->host_if == MWL_IF_PCIE)) + rc = 0; + else + goto err_dnld_and_init; + } + + return rc; + +err_dnld_and_init: + device_init_wakeup(priv->dev, false); + +err_of: + priv->if_ops.cleanup_if(priv); + +err_init_if: + mwl_ieee80211_free_hw(priv); + +err_alloc_hw: + return rc; +} +EXPORT_SYMBOL_GPL(mwl_add_card); + +int mwl_fw_dnld_and_init(struct mwl_priv *priv) +{ + int rc; + struct ieee80211_hw *hw; + + hw = priv->hw; + + rc = mwl_init_firmware(priv); + if (rc) { + if (rc != -EINPROGRESS) + wiphy_err(hw->wiphy, "%s: failed to initialize firmware\n", + MWL_DRV_NAME); + goto err_init_firmware; + } + + rc = mwl_wl_init(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: failed to initialize wireless lan\n", + MWL_DRV_NAME); + goto err_wl_init; + } + + /* Start Timers */ + mod_timer(&priv->period_timer, jiffies + msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); + + /* Start DS timer after init where radio is registered with mac802.11 + * and IEEE80211_CONF_IDLE flag is setup + */ + mwl_restart_ds_timer(priv, true); + +#ifdef CONFIG_SYSFS + lrd_sysfs_init(hw); +#endif + +#ifdef CONFIG_DEBUG_FS + mwl_debugfs_init(hw); +#endif + + priv->init_complete = true; + return 0; + +err_wl_init: + mwl_disable_ds(priv); + +err_init_firmware: + return rc; +} +EXPORT_SYMBOL_GPL(mwl_fw_dnld_and_init); + + +/* + * This function gets called during restart + */ +int mwl_shutdown_sw(struct mwl_priv *priv, bool suspend) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_vif *mwl_vif, *tmp_vif; + struct mwl_sta *mwl_sta, *tmp_sta; + + if (suspend) { + priv->recovery_in_progress = true; + priv->recovery_in_progress_full = true; + priv->mac_init_complete = true; + } + else + cancel_delayed_work_sync(&priv->stop_shutdown_work); + + WARN_ON(!priv->recovery_in_progress); + + lrd_send_fw_event(priv->dev, false); + + wiphy_debug(priv->hw->wiphy, "%s: Shutting down software...\n", MWL_DRV_NAME); + + /* + * Disable radio in error recovery scenarios + * Stop queues + * Disable tasklets + * Return completed TX SKBs + * Note - mwl_mac80211_stop is called directly by mac80211 in + * standard non-error recovery scenarios + */ + if (!suspend && priv->mac_started) + mwl_mac80211_stop(hw); + + del_timer_sync(&priv->roc.roc_timer); + del_timer_sync(&priv->period_timer); + del_timer_sync(&priv->reg.timer_awm); + mwl_disable_ds(priv); + + cancel_work_sync(&priv->watchdog_ba_handle); + cancel_work_sync(&priv->chnl_switch_handle); + cancel_work_sync(&priv->rx_defer_work); + cancel_work_sync(&priv->reg.event); + cancel_work_sync(&priv->reg.awm); + skb_queue_purge(&priv->rx_defer_skb_q); + + /* + * Existing interfaces and stations are re-added by ieee80211_reconfig + * with the expectation the driver is in a clean state. + * Remove private driver stations/interfaces here + */ + list_for_each_entry_safe(mwl_sta, tmp_sta, &priv->sta_list, list) + { + mwl_mac80211_sta_remove(hw, mwl_sta->vif, mwl_sta->sta); + } + + list_for_each_entry_safe(mwl_vif, tmp_vif, &priv->vif_list, list) + { + mwl_mac80211_remove_vif(priv, mwl_vif->vif); + } + + if (priv->if_ops.down_dev) + priv->if_ops.down_dev(priv); + + return 0; +} +EXPORT_SYMBOL_GPL(mwl_shutdown_sw); + +void lrd_send_restart_event(struct wiphy *wiphy, struct ieee80211_vif *vif, uint32_t reason) +{ + struct sk_buff *skb; + struct wireless_dev *wdev = 0; + + if (vif) + wdev = ieee80211_vif_to_wdev(vif); + + //Send Restart Event + skb = cfg80211_vendor_event_alloc(wiphy, wdev, 100, (LRD_VENDOR_EVENT_RESTART & 0x7FFF), GFP_KERNEL); + + if (skb) { + nla_put_u32(skb, LRD_ATTR_RESTART_REASON, reason); + + /* Send the event */ + cfg80211_vendor_event(skb, GFP_KERNEL); + } +} + +/* This function gets called during interface restart. Required + * code is extracted from mwl_add_card()/mwl_wl_init() + */ +int mwl_reinit_sw(struct mwl_priv *priv, bool suspend) +{ + struct ieee80211_hw *hw = priv->hw; + int rc; + + if (!priv->recovery_in_progress) { + cancel_delayed_work_sync(&priv->stop_shutdown_work); + return 0; + } + + wiphy_info(priv->hw->wiphy, "%s: Re-initializing software...\n", MWL_DRV_NAME); + + // Save task context as mechanism to identify calls made in context + // of restart sequence + priv->recovery_owner = current; + + if (priv->if_ops.up_dev) + priv->if_ops.up_dev(priv); + + // Re-initialize priv members that may have changed since initialization + priv->ps_mode = 0; + priv->radio_on = false; + priv->radio_short_preamble = false; + priv->wmm_enabled = false; + priv->csa_active = false; + priv->ds_enable = priv->host_if == MWL_IF_SDIO ? ds_enable : DS_ENABLE_OFF; + priv->is_tx_done_schedule = false; + priv->qe_trigger_num = 0; + priv->qe_trigger_time = jiffies; + priv->running_bsses = 0; + priv->sw_scanning = false; + + priv->cmd_timeout = false; + + atomic_set(&priv->null_scan_count, null_scan_count); + + rc = mwl_init_firmware(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize firmware\n", MWL_DRV_NAME); + goto err_init; + } + + rc = mwl_fwcmd_get_hw_specs(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to get HW specifications\n", + MWL_DRV_NAME); + goto err_init; + } + else { + if (priv->hw_data.fw_release_num == NOT_LRD_HW) { + wiphy_err(hw->wiphy, + "Detected non Laird hardware: 0x%x\n", priv->hw_data.fw_release_num); + rc = -ENODEV; + goto err_init; + } + else { + wiphy_info(hw->wiphy, + "firmware version: 0x%x\n", priv->hw_data.fw_release_num); + } + } + + /* card specific initialization after fw is loaded .. */ + if (priv->if_ops.init_if_post) { + if (priv->if_ops.init_if_post(priv)) { + goto err_init; + } + } + + if (!priv->mfg_mode) { + rc = lrd_fwcmd_lrd_get_caps(hw, &priv->radio_caps); + + if (rc) { + wiphy_err(hw->wiphy, "Fail to retrieve radio capabilities %x\n", rc); + memset(&priv->radio_caps, 0, sizeof(priv->radio_caps)); + } + + if (priv->ant_gain_adjust) { + rc = lrd_fwcmd_lrd_set_ant_gain_adjust(hw, priv->ant_gain_adjust); + if (rc) { + wiphy_err(hw->wiphy, "Antenna gain adjustment 0x%x specified but failed to send!\n", priv->ant_gain_adjust); + goto err_init; + } + } + } + + wiphy_info(hw->wiphy, "Radio Type %s%s%s (0x%x)\n", (priv->radio_caps.capability & LRD_CAP_SU60)?"SU60":"ST60", + (priv->radio_caps.capability & LRD_CAP_440)?"_440":"", + (priv->radio_caps.capability & LRD_CAP_SOM8MP)?"_SOM8MP":"", + priv->radio_caps.capability); + + rc = mwl_fwcmd_set_hw_specs(priv->hw); + if (rc) { + wiphy_err(priv->hw->wiphy, "%s: fail to set HW specifications\n", + MWL_DRV_NAME); + goto err_init; + } + + /* Initialize regulatory info */ + if (lrd_regd_init(priv)) { + wiphy_err(hw->wiphy, "%s: fail to register regulatory\n", MWL_DRV_NAME); + goto err_init; + } + + mwl_fwcmd_radio_disable(hw); + mwl_fwcmd_rf_antenna(hw, priv->ant_tx_bmp, priv->ant_rx_bmp); + + priv->recovery_in_progress = false; + priv->recovery_owner = NULL; + + if (!suspend) { + wiphy_err(priv->hw->wiphy, "%s: Restarting mac80211...\n", + MWL_DRV_NAME); + + ieee80211_restart_hw(hw); + } + else { + lrd_send_restart_event(priv->hw->wiphy, 0, LRD_REASON_RESUME); + } + + /* Request Regulatory DB */ + lrd_request_cc_db(priv); + + mod_timer(&priv->period_timer, jiffies + + msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); + mwl_restart_ds_timer(priv, true); + + lrd_send_fw_event(priv->dev, true); + + if (priv->stop_shutdown && !priv->mac_init_complete) + queue_delayed_work(priv->lrd_workq, &priv->stop_shutdown_work, + msecs_to_jiffies(2000)); + +err_init: + + if (rc) { + if (priv->if_ops.down_dev) + priv->if_ops.down_dev(priv); + + wiphy_err(hw->wiphy, "%s: fail to re-initialize wireless lan!\n", + MWL_DRV_NAME); + } + + return rc; +} +EXPORT_SYMBOL_GPL(mwl_reinit_sw); + +#ifdef CONFIG_PM +static irqreturn_t lrd_irq_wakeup_handler(int irq, void *dev_id) +{ + struct mwl_priv *priv = dev_id; + + /* Notify PM core we are wakeup source */ + pm_wakeup_event(priv->dev, 0); + + wiphy_dbg(priv->hw->wiphy, "Wake by Wi-Fi\n"); + + return IRQ_HANDLED; +} + +int lrd_probe_of_wowlan(struct mwl_priv *priv, struct device_node *of_node) +{ + int ret; + + if ((priv->host_if != MWL_IF_SDIO) || !of_node) { + priv->wow.irq_wakeup = -ENODEV; + return 0; + } + + ret = of_irq_get(of_node, 0); + if (ret <= 0) { + if (ret != -EPROBE_DEFER) + wiphy_err(priv->hw->wiphy, + "Fail to parse irq_wakeup from device tree\n"); + priv->wow.irq_wakeup = -ENODEV; + return ret; + } + + priv->wow.irq_wakeup = ret; + + irq_set_status_flags(priv->wow.irq_wakeup, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(priv->dev, priv->wow.irq_wakeup, NULL, + lrd_irq_wakeup_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "wifi_wake", priv); + if (ret) { + wiphy_err(priv->hw->wiphy, "Failed to request irq_wakeup %d (%d)\n", + priv->wow.irq_wakeup, ret); + return ret; + } + + ret = device_init_wakeup(priv->dev, true); + if (ret) { + wiphy_err(priv->hw->wiphy, "Fail to init wake source (%d)\n", ret); + return ret; + } + + wiphy_info(priv->hw->wiphy, "Added WoWLan interrupt\n"); + + return 0; +} + +/* Disable platform specific wakeup interrupt */ +void lrd_disable_wowlan(struct mwl_priv *priv) +{ + if (priv->wow.irq_wakeup >= 0) { + disable_irq_wake(priv->wow.irq_wakeup); + disable_irq_nosync(priv->wow.irq_wakeup); + } +} +EXPORT_SYMBOL_GPL(lrd_disable_wowlan); + +/* Enable platform specific wakeup interrupt */ +void lrd_enable_wowlan(struct mwl_priv *priv) +{ + /* Enable platform specific wakeup interrupt */ + if (priv->wow.irq_wakeup >= 0) { + enable_irq(priv->wow.irq_wakeup); + enable_irq_wake(priv->wow.irq_wakeup); + } +} +EXPORT_SYMBOL_GPL(lrd_enable_wowlan); + +void lrd_report_wowlan_wakeup(struct mwl_priv *priv) +{ + int x = 0; + struct ieee80211_vif *vif; + struct ieee80211_hw *hw = priv->hw; + struct cfg80211_wowlan_wakeup wakeup; + struct cfg80211_wowlan_nd_info *nd_info = NULL; + struct cfg80211_wowlan_nd_match *nd_match = NULL;; + + memset(&wakeup, 0, sizeof(wakeup)); + wakeup.pattern_idx = -1; + + switch( priv->wow.results.reason) { + case MWL_RX_EVENT_WOW_LINKLOSS_DETECT: + wiphy_info(hw->wiphy, "WOW link loss detected\n"); + wakeup.disconnect = true; + break; + + case MWL_RX_EVENT_WOW_AP_DETECT: + nd_info = kzalloc(sizeof(struct cfg80211_wowlan_nd_info) + + sizeof(struct cfg80211_wowlan_nd_match *) + + sizeof(struct cfg80211_wowlan_nd_match) + + sizeof(u32) * priv->wow.results.n_channels, + GFP_KERNEL); + + wiphy_info(hw->wiphy, "WOW AP in range detected\n"); + + /* Fill in nd_info */ + nd_info->n_matches = 1; + nd_match = (struct cfg80211_wowlan_nd_match*)((u8*)nd_info->matches + sizeof(struct cfg80211_wowlan_nd_info*)); + nd_info->matches[0] = nd_match; + + /* nd_match -> ssid*/ + nd_match->ssid.ssid_len = min(priv->wow.ssidList[0].ssidLen, (u8)sizeof(nd_match->ssid.ssid)); + memcpy(nd_match->ssid.ssid, priv->wow.ssidList[0].ssid, nd_match->ssid.ssid_len); + + /* nd_match->channels */ + nd_match->n_channels = priv->wow.results.n_channels; + for (x = 0; x < nd_match->n_channels; x++) { + nd_match->channels[x] = priv->wow.results.channels[x]; + } + + wakeup.net_detect = nd_info; + break; + + case MWL_RX_EVENT_WOW_RX_DETECT: + /* We are treating the packet as a flag, rather than data */ + wiphy_info(hw->wiphy, "WOW rx packet detected\n"); + wakeup.packet = (void*)true; + wakeup.packet_80211 = true; + wakeup.packet_present_len = 0; + break; + } + + vif = priv->wow.results.mwl_vif->vif; + ieee80211_report_wowlan_wakeup(vif, &wakeup, GFP_KERNEL); + + if (nd_info) { + kfree(nd_info); + } +} +#endif + +module_param(cal_data_cfg, charp, 0); +MODULE_PARM_DESC(cal_data_cfg, "Calibration data file name"); + +module_param(wmm_turbo, int, 0); +MODULE_PARM_DESC(wmm_turbo, "WMM Turbo mode 0:Disable 1:Enable"); + +module_param(EDMAC_Ctrl, int, 0); +MODULE_PARM_DESC(EDMAC_Ctrl, "EDMAC CFG: BIT0:2G_enbl, BIT1:5G_enbl, " \ + "BIT[4:11]: 2G_Offset, BIT[12:19]:5G_offset, " \ + "BIT[20:27]:Queue_lock, BIT[28]: MCBC_QLock, " \ + "BIT[29]: BCN_DSBL"); + +module_param(tx_amsdu_enable, int, 0); +MODULE_PARM_DESC(tx_amsdu_enable, "Tx AMSDU enable/disable"); + +module_param(SISO_mode, uint, 0444); +MODULE_PARM_DESC(SISO_mode, "SISO mode 0:Disable 1:Ant0 2:Ant1"); + +module_param(lrd_debug, uint, 0644); +MODULE_PARM_DESC(lrd_debug, "Debug mode 0:Disable 1:Enable"); + +module_param(ds_enable, uint, 0444); +MODULE_PARM_DESC(ds_enable, "Deep Sleep mode 0:Disable 1:Enable"); + +module_param(enable_bt_dup, uint, 0444); +MODULE_PARM_DESC(enable_bt_dup, "Enable coexistence with duplicate BT address 0:Disable 1:Enable"); + +module_param(mfg_mode, int, 0); +MODULE_PARM_DESC(mfg_mode, "MFG mode 0:disable 1:enable"); + +module_param(null_scan_count, int, 0); +MODULE_PARM_DESC(null_scan_count, "Null scan response recovery count"); + +module_param(ant_gain_adjust, uint, 0444); +MODULE_PARM_DESC(ant_gain_adjust, "Antenna gain adjustment"); + +module_param(stop_shutdown, uint, 0444); +MODULE_PARM_DESC(stop_shutdown, "Power off when stopped 0:Disable 1:Enable"); + +module_param(stop_shutdown_timeout, uint, 0644); +MODULE_PARM_DESC(stop_shutdown_timeout, "Timeout waiting for connection at boot, ms"); + +module_param(host_crypto_mode, uint, 0444); +MODULE_PARM_DESC(host_crypto_mode, "Use only host cryptography 0:hw/host 1:host"); + +/*Note: Enabling 2.4 band's 40MHz channels results in noncompliant WFA certification */ +module_param(enable_24_40mhz, uint, 0); +MODULE_PARM_DESC(enable_24_40mhz, "Enable 2.4 support for 40MHz channels 0:Disable 1:Enable"); + + +MODULE_DESCRIPTION(LRD_DESC); +MODULE_VERSION(LRD_DRV_VERSION); +MODULE_AUTHOR(LRD_AUTHOR); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/laird/lrdmwl/main.h b/drivers/net/wireless/laird/lrdmwl/main.h new file mode 100644 index 0000000000000..dfb5ed7ff2caa --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/main.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#define MWL_TXANT_BMP_TO_NUM(bmp) \ +(((bmp & MWL_8997_DEF_TX_ANT_BMP) == MWL_8997_DEF_TX_ANT_BMP)? 2 : 1) + +#define MWL_RXANT_BMP_TO_NUM(bmp) \ +(((bmp & MWL_8997_DEF_RX_ANT_BMP) == MWL_8997_DEF_RX_ANT_BMP)? 2 : 1) + + +/* WMM Turbo mode */ +extern int wmm_turbo; + +extern int EDMAC_Ctrl; +extern int tx_amsdu_enable; + +int mwl_add_card(void *, struct mwl_if_ops *, struct device_node *of_node); +void mwl_wl_deinit(struct mwl_priv *); +void mwl_set_ht_caps (struct mwl_priv *priv, struct ieee80211_supported_band *band); +void mwl_set_vht_caps(struct mwl_priv *priv, struct ieee80211_supported_band *band); +void mwl_ieee80211_free_hw(struct mwl_priv *); +extern void timer_routine(struct timer_list *t); +extern void mwl_restart_ds_timer(struct mwl_priv *priv, bool force); +extern void mwl_delete_ds_timer(struct mwl_priv *priv); +extern int mwl_fw_dnld_and_init(struct mwl_priv *priv); +extern int mwl_shutdown_sw(struct mwl_priv *priv, bool suspend); +extern int mwl_reinit_sw(struct mwl_priv *priv, bool suspend); +extern void mwl_mac80211_stop(struct ieee80211_hw *hw); +extern void mwl_mac80211_remove_vif(struct mwl_priv *priv, struct ieee80211_vif *vif); +extern int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta); + +#ifdef CONFIG_SCHED_HRTICK + #define lrdmwl_delay(d) usleep_range(d, d + 10); +#else + #define lrdmwl_delay(d) (d < (MAX_UDELAY_MS * 1000) ? udelay(d) : mdelay(d / 1000)) +#endif + +#ifdef CONFIG_PM + +extern void lrd_enable_wowlan(struct mwl_priv *priv); +extern void lrd_disable_wowlan(struct mwl_priv *priv); +int lrd_probe_of_wowlan(struct mwl_priv *priv, struct device_node *of_node); + +extern void lrd_report_wowlan_wakeup(struct mwl_priv *priv); + +#else + +static inline void lrd_enable_wowlan(struct mwl_priv *priv) {} +static inline void lrd_disable_wowlan(struct mwl_priv *priv) {} +static inline int lrd_probe_of_wowlan(struct mwl_priv *priv, + struct device_node *of_node) { return 0; } + +#endif + +void lrd_radio_recovery(struct mwl_priv *priv); +void lrd_dump_max_pwr_table(struct mwl_priv *priv); + +extern void lrd_cc_event(struct work_struct *work); +extern void lrd_send_restart_event(struct wiphy *wiphy, struct ieee80211_vif *vif, uint32_t reason); + +#endif diff --git a/drivers/net/wireless/laird/lrdmwl/pcie.c b/drivers/net/wireless/laird/lrdmwl/pcie.c new file mode 100644 index 0000000000000..f3c06e6f410a2 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/pcie.c @@ -0,0 +1,2453 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Laird, PLC. + * under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include +#include +#include +#include +#include +#include +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "hostcmd.h" +#include "main.h" +#include "pcie.h" +#include "isr.h" +#include "tx.h" +#include "rx.h" + +/*Note PCIEDRV_VERSION define was taken from SDIO since there + * was no PCI version defined in reference driver */ +#define MWL_PCIEDRV_VERSION "10.3.0.16-20160105" +#define LRD_PCIE_VERSION LRD_BLD_VERSION "-" MWL_PCIEDRV_VERSION +#define LRD_PCIE_DESC "Laird Connectivity 60 Series Wireless PCIE Network Driver" + +#define INTF_HEADER_LEN 0 +#define INTF_HEADER_LEN_MFG 4 + + +/* PCIe interrupt mode = MSI (default) */ +int pcie_intr_mode = 1; + +static struct mwl_chip_info mwl_chip_tbl[] = { + [MWL8864] = { + .part_name = "88W8864", + .fw_image = MWL_FW_ROOT"/88W8864_pcie.bin", + .antenna_tx = ANTENNA_TX_4_AUTO, + .antenna_rx = ANTENNA_RX_4_AUTO, + }, + [MWL8897] = { + .part_name = "88W8897", + .fw_image = MWL_FW_ROOT"/88W8897_pcie.bin", + .antenna_tx = ANTENNA_TX_2, + .antenna_rx = ANTENNA_RX_2, + }, + [MWL8964] = { + .part_name = "88W8964", + .fw_image = MWL_FW_ROOT"/88W8964_pcie.bin", + .antenna_tx = ANTENNA_TX_4_AUTO, + .antenna_rx = ANTENNA_RX_4_AUTO, + }, + [MWL8997] = { + .part_name = "88W8997", + .fw_image = MWL_FW_ROOT"/88W8997_pcie.bin", + .mfg_image = MWL_FW_ROOT"/88W8997_pcie_mfg.bin", + .antenna_tx = ANTENNA_TX_2, + .antenna_rx = ANTENNA_RX_2, + }, +}; + +static void mwl_pcie_tx_flush_amsdu(unsigned long data); +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 void mwl_set_bit(struct mwl_priv *priv, int bit_num, volatile void * addr); +static void mwl_clear_bit(struct mwl_priv *priv, int bit_num, volatile void * addr); +static int mwl_pcie_restart_handler(struct mwl_priv *priv); +static void mwl_pcie_up_dev(struct mwl_priv *priv); +static void mwl_pcie_down_dev(struct mwl_priv *priv); +static bool mwl_pcie_check_fw_status(struct mwl_priv *priv); + +#define MAX_WAIT_CMD_RESPONSE_ITERATIONS (10*4000) + +static irqreturn_t mwl_pcie_isr(int irq, void *dev_id); +static struct pci_device_id mwl_pci_id_tbl[] = { + { PCI_VDEVICE(MARVELL_EXT, 0x2b42), .driver_data = MWL8997, }, + { }, +}; + +static const struct of_device_id mwl_pcie_of_match_table[] = { + { .compatible = "pci1b4b,2b42" }, + { } +}; + +static int mwl_pcie_probe_of(struct device *dev) +{ + if (!of_match_node(mwl_pcie_of_match_table, dev_of_node(dev))) { + dev_err(dev, "required compatible string missing\n"); + return -EINVAL; + } + + return 0; +} + + + +static void lrd_pci_fw_reset_workfn(struct work_struct *work) +{ + int rc; + struct ieee80211_hw *hw; + struct mwl_pcie_card *card = container_of(work, + struct mwl_pcie_card, fw_reset_work); + + hw = pci_get_drvdata(card->pdev); + + rc = pci_reset_function(card->pdev); + + if (rc != 0) + { + wiphy_err(hw->wiphy, "%s: PCI reset failed! %d\n", + MWL_DRV_NAME, rc); + } +} + + +static void mwl_pcie_reset_prepare(struct pci_dev *pdev) +{ + struct mwl_priv *priv; + struct ieee80211_hw *hw; + + hw = pci_get_drvdata(pdev); + + if (!hw || !hw->priv) + return; + + priv = hw->priv; + + wiphy_err(hw->wiphy, "%s: Prepare for reset...\n", __func__); + + if (priv->recovery_in_progress) + mwl_shutdown_sw(priv, false); + + wiphy_err(hw->wiphy, "%s: Resetting...\n", __func__); +} + + +static void mwl_pcie_reset_done(struct pci_dev *pdev) +{ + struct mwl_priv *priv; + struct ieee80211_hw *hw; + + hw = pci_get_drvdata(pdev); + + if (!hw || !hw->priv) + return; + + priv = hw->priv; + + wiphy_err(hw->wiphy, "%s: Reset complete...\n", __func__); + + // Give FLR handler in firmware opportunity to run + // Typically takes ~125ms for FW Ready signature to be cleared after FLR + msleep(150); + + if (priv->recovery_in_progress) + mwl_reinit_sw(priv, false); + else + { + int rc; + rc = mwl_fw_dnld_and_init(priv); + if (rc) + { + wiphy_err(hw->wiphy, "%s: Initialization failed after reset!! %d\n", + MWL_DRV_NAME, rc); + } + } +} + +/* + * Cleanup all software without cleaning anything related to PCIe and HW. +*/ +static void mwl_free_pci_resource(struct mwl_priv *priv) +{ +#if 0 + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + + /* priv->pcmd_buf will be automatically freed on driver unload */ + if (priv->pcmd_buf) + dma_free_coherent(priv->dev, + CMD_BUF_SIZE, + priv->pcmd_buf, + priv->pphys_cmd_buf); + + if (pdev) { + iounmap((volatile void __iomem *)&pdev->resource[0]); + iounmap((volatile void __iomem *)&pdev->resource[card->next_bar_num]); + } + +#endif +} + +static int mwl_alloc_pci_resource(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + void __iomem *addr; + + card->next_bar_num = 1; /* 32-bit */ + if (pci_resource_flags(pdev, 0) & 0x04) + card->next_bar_num = 2; /* 64-bit */ + + addr = devm_ioremap_resource(priv->dev, &pdev->resource[0]); + if (IS_ERR(addr)) { + wiphy_err(priv->hw->wiphy, + "%s: cannot reserve PCI memory region 0\n", + MWL_DRV_NAME); + goto err; + } + card->iobase0 = addr; + wiphy_debug(priv->hw->wiphy, "card->iobase0 = %p\n", card->iobase0); + + addr = devm_ioremap_resource(priv->dev, + &pdev->resource[card->next_bar_num]); + if (IS_ERR(addr)) { + wiphy_err(priv->hw->wiphy, + "%s: cannot reserve PCI memory region 1\n", + MWL_DRV_NAME); + goto err; + } + card->iobase1 = addr; + wiphy_debug(priv->hw->wiphy, "card->iobase1 = %p\n", card->iobase1); + + priv->pcmd_buf = + (unsigned short *)dmam_alloc_coherent(priv->dev, + CMD_BUF_SIZE, + &priv->pphys_cmd_buf, + GFP_KERNEL); + if (!priv->pcmd_buf) { + wiphy_err(priv->hw->wiphy, + "%s: cannot alloc memory for command buffer\n", + MWL_DRV_NAME); + goto err; + } + wiphy_debug(priv->hw->wiphy, + "priv->pcmd_buf = %p priv->pphys_cmd_buf = %p\n", + priv->pcmd_buf, + (void *)priv->pphys_cmd_buf); + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + + return 0; + +err: + wiphy_err(priv->hw->wiphy, "pci alloc fail\n"); + + return -EIO; +} + +int mwl_tx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int rc; + + wiphy_info(hw->wiphy, "%s() called: ctype=%d\n", __FUNCTION__, priv->chip_type); + + if (IS_PFU_ENABLED(priv->chip_type)) { + rc = wlan_pcie_create_txbd_ring(hw); + if (rc) { + wiphy_err(hw->wiphy, "wlan_pcie_create_txbd_ring() failed\n"); + return rc; + } + } else { + rc = mwl_tx_ring_alloc(priv); + if (rc) { + wiphy_err(hw->wiphy, "allocating TX ring failed\n"); + return rc; + } + } + + rc = mwl_tx_ring_init(priv); + + if (rc) { + if (!IS_PFU_ENABLED(priv->chip_type)) { + + mwl_tx_ring_free(priv); + wiphy_err(hw->wiphy, "initializing TX ring failed\n"); + return rc; + } + } + + return 0; +} + +/* rx */ +#define MAX_NUM_RX_RING_BYTES (SYSADPT_MAX_NUM_RX_DESC * \ + sizeof(struct mwl_rx_desc)) + +#define MAX_NUM_RX_HNDL_BYTES (SYSADPT_MAX_NUM_RX_DESC * \ + sizeof(struct mwl_rx_hndl)) + +static int mwl_rx_ring_alloc(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + + desc = &priv->desc_data[0]; + desc->prx_ring = (struct mwl_rx_desc *) + dma_alloc_coherent(priv->dev, + MAX_NUM_RX_RING_BYTES, + &desc->pphys_rx_ring, + GFP_KERNEL); + if (!desc->prx_ring) { + wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); + return -ENOMEM; + } + + memset(desc->prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); + + desc->rx_hndl = kmalloc(MAX_NUM_RX_HNDL_BYTES, GFP_KERNEL); + + if (!desc->rx_hndl) { + dma_free_coherent(priv->dev, + MAX_NUM_RX_RING_BYTES, + desc->prx_ring, + desc->pphys_rx_ring); + return -ENOMEM; + } + + memset(desc->rx_hndl, 0x00, MAX_NUM_RX_HNDL_BYTES); + + return 0; +} + +static int mwl_rx_ring_init(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + struct mwl_desc_data *desc; + int i; + struct mwl_rx_hndl *rx_hndl; + dma_addr_t dma; + u32 val; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + desc->rx_buf_size = SYSADPT_MAX_AGGR_SIZE; + + for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) { + rx_hndl = &desc->rx_hndl[i]; + rx_hndl->psk_buff = + dev_alloc_skb(desc->rx_buf_size); + + if (!rx_hndl->psk_buff) { + wiphy_err(priv->hw->wiphy, + "rxdesc %i: no skbuff available\n", + i); + return -ENOMEM; + } + + skb_reserve(rx_hndl->psk_buff, + SYSADPT_MIN_BYTES_HEADROOM); + desc->prx_ring[i].rx_control = + EAGLE_RXD_CTRL_DRIVER_OWN; + desc->prx_ring[i].status = EAGLE_RXD_STATUS_OK; + desc->prx_ring[i].qos_ctrl = 0x0000; + desc->prx_ring[i].channel = 0x00; + desc->prx_ring[i].rssi = 0x00; + desc->prx_ring[i].pkt_len = + cpu_to_le16(SYSADPT_MAX_AGGR_SIZE); + dma = dma_map_single(&card->pdev->dev, + rx_hndl->psk_buff->data, + desc->rx_buf_size, + DMA_FROM_DEVICE); + if (dma_mapping_error(&card->pdev->dev, dma)) { + wiphy_err(priv->hw->wiphy, + "failed to map pci memory!\n"); + dev_kfree_skb_any(rx_hndl->psk_buff); + rx_hndl->psk_buff = NULL; + // Previously initialized ring entries handled in mwl_rx_ring_cleanup + return -ENOMEM; + } + +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + // Some platforms have been found to return 64 bit DMA addresses + // even though 32 bit addresses are required by the call to + // pcie_set_dma_mask() in mwl_pcie_init() + // Check for that here and fail gracefully with a hint instead of + // crashing later + if (dma >> 32) { + wiphy_err(priv->hw->wiphy, + "dma_map_single() returned 64 bit DMA address %llx\n", + (long long unsigned int)dma); + wiphy_err(priv->hw->wiphy, + "64 bit DMA is not supported, failing!!\n"); + dma_unmap_single(&card->pdev->dev, + dma, + desc->rx_buf_size, + DMA_FROM_DEVICE); + dev_kfree_skb_any(rx_hndl->psk_buff); + rx_hndl->psk_buff = NULL; + // Previously initialized ring entries handled in mwl_rx_ring_cleanup + return -ENOMEM; + } +#endif + + desc->prx_ring[i].pphys_buff_data = cpu_to_le32(dma); + val = (u32)desc->pphys_rx_ring + + ((i + 1) * sizeof(struct mwl_rx_desc)); + desc->prx_ring[i].pphys_next = cpu_to_le32(val); + rx_hndl->pdesc = &desc->prx_ring[i]; + if (i < (SYSADPT_MAX_NUM_RX_DESC - 1)) + rx_hndl->pnext = &desc->rx_hndl[i + 1]; + } + desc->prx_ring[SYSADPT_MAX_NUM_RX_DESC - 1].pphys_next = + cpu_to_le32((u32)desc->pphys_rx_ring); + desc->rx_hndl[SYSADPT_MAX_NUM_RX_DESC - 1].pnext = + &desc->rx_hndl[0]; + desc->pnext_rx_hndl = &desc->rx_hndl[0]; + + return 0; + } + + wiphy_err(priv->hw->wiphy, "no valid RX mem\n"); + + return -ENOMEM; +} + +static void mwl_rx_ring_cleanup(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + struct mwl_desc_data *desc; + int i; + struct mwl_rx_hndl *rx_hndl; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_RX_DESC; i++) { + rx_hndl = &desc->rx_hndl[i]; + if (!rx_hndl->psk_buff) + continue; + + dma_unmap_single(&card->pdev->dev, + le32_to_cpu + (rx_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size, + DMA_FROM_DEVICE); + + wiphy_dbg(priv->hw->wiphy, + "Rx: unmapped+free'd %i 0x%p 0x%x %i\n", + i, rx_hndl->psk_buff->data, + le32_to_cpu(rx_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size); + + dev_kfree_skb_any(rx_hndl->psk_buff); + rx_hndl->psk_buff = NULL; + } + } +} + +static void mwl_rx_ring_free(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + + desc = &priv->desc_data[0]; + + if (desc->prx_ring) { + mwl_rx_ring_cleanup(priv); + + dma_free_coherent(priv->dev, + MAX_NUM_RX_RING_BYTES, + desc->prx_ring, + desc->pphys_rx_ring); + + desc->prx_ring = NULL; + } + + kfree(desc->rx_hndl); + + desc->pnext_rx_hndl = NULL; +} + +static int mwl_rx_refill(struct mwl_priv *priv, struct mwl_rx_hndl *rx_hndl) +{ + struct mwl_pcie_card *card = priv->intf; + struct mwl_desc_data *desc; + dma_addr_t dma; + + desc = &priv->desc_data[0]; + + rx_hndl->psk_buff = dev_alloc_skb(desc->rx_buf_size); + + if (!rx_hndl->psk_buff) + return -ENOMEM; + + skb_reserve(rx_hndl->psk_buff, SYSADPT_MIN_BYTES_HEADROOM); + + rx_hndl->pdesc->status = EAGLE_RXD_STATUS_OK; + rx_hndl->pdesc->qos_ctrl = 0x0000; + rx_hndl->pdesc->channel = 0x00; + rx_hndl->pdesc->rssi = 0x00; + rx_hndl->pdesc->pkt_len = cpu_to_le16(desc->rx_buf_size); + + dma = dma_map_single(&card->pdev->dev, + rx_hndl->psk_buff->data, + desc->rx_buf_size, + DMA_FROM_DEVICE); + if (dma_mapping_error(&card->pdev->dev, dma)) { + dev_kfree_skb_any(rx_hndl->psk_buff); + wiphy_err(priv->hw->wiphy, + "failed to map pci memory!\n"); + return -ENOMEM; + } + + rx_hndl->pdesc->pphys_buff_data = cpu_to_le32(dma); + + return 0; +} + + +static void mwl_set_bit(struct mwl_priv *priv, int bit_num, volatile void * addr) +{ + struct mwl_pcie_card *card = priv->intf; + u32 intr_status; + unsigned long flags; + spin_lock_irqsave(&card->intr_status_lock,flags); + intr_status = readl(addr); + intr_status = intr_status | (1 << bit_num); + writel(intr_status, addr); + spin_unlock_irqrestore(&card->intr_status_lock,flags); +} + +static void mwl_clear_bit(struct mwl_priv *priv, int bit_num, volatile void *addr) +{ + struct mwl_pcie_card *card = priv->intf; + u32 intr_status; + unsigned long flags; + spin_lock_irqsave(&card->intr_status_lock, flags); + intr_status = readl(addr); + intr_status = intr_status & ~(1 << bit_num); + writel(intr_status, addr); + spin_unlock_irqrestore(&card->intr_status_lock, flags); +} + +void mwl_pcie_rx_recv(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + struct mwl_desc_data *desc; + struct mwl_rx_hndl *curr_hndl; + int work_done = 0; + struct sk_buff *prx_skb = NULL; + int pkt_len; + struct ieee80211_rx_status status; + struct mwl_vif *mwl_vif = NULL; + struct ieee80211_hdr *wh; + struct mwl_rx_event_data *rx_evnt; + + desc = &priv->desc_data[0]; + curr_hndl = desc->pnext_rx_hndl; + + if (!curr_hndl) { + mwl_set_bit(priv, MACREG_A2HRIC_BIT_NUM_RX_RDY, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + wiphy_warn(hw->wiphy, "busy or no receiving packets\n"); + return; + } + + while ((curr_hndl->pdesc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) && + (work_done < priv->recv_limit)) { + prx_skb = curr_hndl->psk_buff; + if (!prx_skb) + goto out; + dma_unmap_single(&card->pdev->dev, + le32_to_cpu(curr_hndl->pdesc->pphys_buff_data), + desc->rx_buf_size, + DMA_FROM_DEVICE); + pkt_len = le16_to_cpu(curr_hndl->pdesc->pkt_len); + + if (skb_tailroom(prx_skb) < pkt_len) { + dev_kfree_skb_any(prx_skb); + goto out; + } + + if (curr_hndl->pdesc->payldType == RX_PAYLOAD_TYPE_EVENT_INFO) { + rx_evnt = (struct mwl_rx_event_data *)prx_skb->data; + mwl_handle_rx_event(hw, rx_evnt); + dev_kfree_skb_any(prx_skb); + goto out; + } + + if ((curr_hndl->pdesc->channel != hw->conf.chandef.chan->hw_value) && + !(priv->roc.tmr_running && priv->roc.in_progress && + (curr_hndl->pdesc->channel == priv->roc.chan))) { + dev_kfree_skb_any(prx_skb); + goto out; + } + + mwl_rx_prepare_status(curr_hndl->pdesc, &status); + + priv->noise = curr_hndl->pdesc->noise_floor; + + wh = &((struct mwl_dma_data *)prx_skb->data)->wh; + + if (ieee80211_has_protected(wh->frame_control)) { + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + if (ieee80211_has_tods(wh->frame_control)) { + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1); + if (!mwl_vif && ieee80211_has_a4(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv,wh->addr2); + } + else if (ieee80211_has_fromds(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2); + else + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr3); + + if (mwl_vif && mwl_vif->is_hw_crypto_enabled) { + /* When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwlwifi Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + struct mwl_dma_data *tr; + + tr = (struct mwl_dma_data *) + prx_skb->data; + memset((void *)&tr->data, 0, 4); + pkt_len += 4; + /* IV is stripped in this case + * Indicate that, so mac80211 doesn't attempt to decrypt the + * packet and fail prior to handling the MMIC error indication + */ + status.flag |= RX_FLAG_IV_STRIPPED; + } + + if (!ieee80211_is_auth(wh->frame_control)) + /* For WPA2 frames, AES header/MIC are + ** present to enable mac80211 to check + ** for replay attacks + */ + status.flag |= RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + skb_put(prx_skb, pkt_len); + mwl_rx_remove_dma_header(prx_skb, curr_hndl->pdesc->qos_ctrl); + + wh = (struct ieee80211_hdr *)prx_skb->data; + + if (ieee80211_is_mgmt(wh->frame_control)) { + struct ieee80211_mgmt *mgmt; + __le16 capab; + + mgmt = (struct ieee80211_mgmt *)prx_skb->data; + + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == + WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_resp.action_code == + WLAN_ACTION_ADDBA_RESP)) { + capab = mgmt->u.action.u.addba_resp.capab; + if (le16_to_cpu(capab) & 1) + mwl_rx_enable_sta_amsdu(priv, mgmt->sa); + } + } + +#if 0 //def CONFIG_MAC80211_MESH + if (ieee80211_is_data_qos(wh->frame_control) && + ieee80211_has_a4(wh->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(wh); + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + if (mwl_rx_process_mesh_amsdu(priv, prx_skb, + &status)) + goto out; + } +#endif + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); + mwl_rx_upload_pkt(hw, prx_skb); +out: + mwl_rx_refill(priv, curr_hndl); + curr_hndl->pdesc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN; + curr_hndl->pdesc->qos_ctrl = 0; + curr_hndl = curr_hndl->pnext; + work_done++; + } + + desc->pnext_rx_hndl = curr_hndl; + + mwl_set_bit(priv, MACREG_A2HRIC_BIT_NUM_RX_RDY, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + return; +} + + +int mwl_rx_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int rc; + + rc = mwl_rx_ring_alloc(priv); + if (rc) { + wiphy_err(hw->wiphy, "allocating RX ring failed\n"); + return rc; + } + + rc = mwl_rx_ring_init(priv); + if (rc) { + mwl_rx_ring_free(priv); + wiphy_err(hw->wiphy, + "initializing RX ring failed\n"); + return rc; + } + + return 0; +} + +void mwl_rx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + mwl_rx_ring_cleanup(priv); + mwl_rx_ring_free(priv); +} + +/* tx */ +#define MAX_NUM_TX_RING_BYTES (SYSADPT_MAX_NUM_TX_DESC * \ + sizeof(struct mwl_tx_desc)) + +#define MAX_NUM_TX_HNDL_BYTES (SYSADPT_MAX_NUM_TX_DESC * \ + sizeof(struct mwl_tx_hndl)) +static int mwl_tx_ring_alloc(struct mwl_priv *priv) +{ + struct mwl_desc_data *desc; + int num; + u8 *mem; + + desc = &priv->desc_data[0]; + + mem = dma_alloc_coherent(priv->dev, + MAX_NUM_TX_RING_BYTES * + SYSADPT_NUM_OF_DESC_DATA, + &desc->pphys_tx_ring, + GFP_KERNEL); + + if (!mem) { + wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); + return -ENOMEM; + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + desc = &priv->desc_data[num]; + + desc->ptx_ring = (struct mwl_tx_desc *) + (mem + num * MAX_NUM_TX_RING_BYTES); + + desc->pphys_tx_ring = (dma_addr_t) + ((u32)priv->desc_data[0].pphys_tx_ring + + num * MAX_NUM_TX_RING_BYTES); + + memset(desc->ptx_ring, 0x00, + MAX_NUM_TX_RING_BYTES); + } + + mem = kmalloc(MAX_NUM_TX_HNDL_BYTES * SYSADPT_NUM_OF_DESC_DATA, + GFP_KERNEL); + + if (!mem) { + wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); + dma_free_coherent(priv->dev, + MAX_NUM_TX_RING_BYTES * + SYSADPT_NUM_OF_DESC_DATA, + priv->desc_data[0].ptx_ring, + priv->desc_data[0].pphys_tx_ring); + return -ENOMEM; + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + desc = &priv->desc_data[num]; + + desc->tx_hndl = (struct mwl_tx_hndl *) + (mem + num * MAX_NUM_TX_HNDL_BYTES); + + memset(desc->tx_hndl, 0x00, + MAX_NUM_TX_HNDL_BYTES); + } + + return 0; +} + +static int mwl_tx_ring_init(struct mwl_priv *priv) +{ + int num; + int i; + struct mwl_desc_data *desc; + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + skb_queue_head_init(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + + if (!IS_PFU_ENABLED(priv->chip_type)) { + desc = &priv->desc_data[num]; + + if (desc->ptx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) { + desc->ptx_ring[i].status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + desc->ptx_ring[i].pphys_next = + cpu_to_le32((u32)desc->pphys_tx_ring + + ((i + 1) * sizeof(struct mwl_tx_desc))); + desc->tx_hndl[i].pdesc = + &desc->ptx_ring[i]; + if (i < SYSADPT_MAX_NUM_TX_DESC - 1) + desc->tx_hndl[i].pnext = + &desc->tx_hndl[i + 1]; + } + desc->ptx_ring[SYSADPT_MAX_NUM_TX_DESC - 1].pphys_next = + cpu_to_le32((u32)desc->pphys_tx_ring); + desc->tx_hndl[SYSADPT_MAX_NUM_TX_DESC - 1].pnext = + &desc->tx_hndl[0]; + + desc->pstale_tx_hndl = &desc->tx_hndl[0]; + desc->pnext_tx_hndl = &desc->tx_hndl[0]; + } else { + wiphy_err(priv->hw->wiphy, "no valid TX mem\n"); + return -ENOMEM; + } + } + } + + return 0; +} + +static void mwl_tx_ring_cleanup(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + int cleaned_tx_desc = 0; + int num, i; + struct mwl_desc_data *desc; + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + skb_queue_purge(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + + if (!IS_PFU_ENABLED(priv->chip_type)) { + desc = &priv->desc_data[num]; + + if (desc->ptx_ring) { + for (i = 0; i < SYSADPT_MAX_NUM_TX_DESC; i++) { + if (!desc->tx_hndl[i].psk_buff) + continue; + + wiphy_dbg(priv->hw->wiphy, + "Tx: unmapped and free'd %i 0x%p 0x%x\n", + i, + desc->tx_hndl[i].psk_buff->data, + le32_to_cpu( + desc->ptx_ring[i].pkt_ptr)); + dma_unmap_single(&card->pdev->dev, + le32_to_cpu( + desc->ptx_ring[i].pkt_ptr), + desc->tx_hndl[i].psk_buff->len, + DMA_TO_DEVICE); + dev_kfree_skb_any(desc->tx_hndl[i].psk_buff); + desc->ptx_ring[i].status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + desc->ptx_ring[i].pkt_ptr = 0; + desc->ptx_ring[i].pkt_len = 0; + desc->tx_hndl[i].psk_buff = NULL; + cleaned_tx_desc++; + } + } + } + } + + wiphy_info(priv->hw->wiphy, "cleaned %i TX descr\n", cleaned_tx_desc); +} + +static void mwl_tx_ring_free(struct mwl_priv *priv) +{ + int num; + + if (priv->desc_data[0].ptx_ring) { + dma_free_coherent(priv->dev, + MAX_NUM_TX_RING_BYTES * + SYSADPT_NUM_OF_DESC_DATA, + priv->desc_data[0].ptx_ring, + priv->desc_data[0].pphys_tx_ring); + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + if (priv->desc_data[num].ptx_ring) + priv->desc_data[num].ptx_ring = NULL; + priv->desc_data[num].pstale_tx_hndl = NULL; + priv->desc_data[num].pnext_tx_hndl = NULL; + } + + kfree(priv->desc_data[0].tx_hndl); +} +void mwl_tx_deinit(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + + mwl_tx_ring_cleanup(priv); + + if (IS_PFU_ENABLED(priv->chip_type)) + wlan_pcie_delete_txbd_ring(hw); + else + mwl_tx_ring_free(priv); +} + +static bool mwl_pcie_is_tx_available(struct mwl_priv *priv, int desc_num) +{ + struct mwl_pcie_card *card = priv->intf; + struct mwl_tx_hndl *tx_hndl; + + if (IS_PFU_ENABLED(priv->chip_type)) + return PCIE_TXBD_NOT_FULL(priv->txbd_wrptr, priv->txbd_rdptr); + + tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl; + + if (!tx_hndl->pdesc) + return false; + + if (tx_hndl->pdesc->status != EAGLE_TXD_STATUS_IDLE) { + /* Interrupt F/W anyway */ + if (tx_hndl->pdesc->status & + cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) + writel(MACREG_H2ARIC_BIT_PPA_READY, + card->iobase1 + + MACREG_REG_H2A_INTERRUPT_EVENTS); + return false; + } + + return true; +} +static int mwl_pcie_init_post(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + int i; + + // Stop Shutdown mode not supported + priv->stop_shutdown = false; + + if (priv->mfg_mode) { + // Assume ST 60 with one interface + priv->radio_caps.capability = 0; + priv->radio_caps.num_mac = 1; + } + + if (!IS_PFU_ENABLED(priv->chip_type)) { + writel(priv->desc_data[0].pphys_tx_ring, + card->iobase0 + priv->desc_data[0].wcb_base); + for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) + writel(priv->desc_data[i].pphys_tx_ring, + card->iobase0 + priv->desc_data[i].wcb_base); + } + + writel(priv->desc_data[0].pphys_rx_ring, + card->iobase0 + priv->desc_data[0].rx_desc_read); + writel(priv->desc_data[0].pphys_rx_ring, + card->iobase0 + priv->desc_data[0].rx_desc_write); + + return 0; +} + +/* + * This function initializes the PCI-E host memory space, WCB rings, etc. + * + * The following initializations steps are followed - + * - Allocate TXBD ring buffers + * - Allocate RXBD ring buffers + * - Allocate event BD ring buffers + * - Allocate command response ring buffer + * - Allocate sleep cookie buffer + */ +static int mwl_pcie_init(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + struct ieee80211_hw *hw; + int rc = 0; + + priv->chip_type = card->chip_type; + priv->host_if = MWL_IF_PCIE; + + hw = priv->hw; + priv->irq=-1; + card->priv = priv; + + rc = pci_enable_device(pdev); + if (rc) { + wiphy_err(hw->wiphy, "%s: cannot enable new PCI device.\n", + MWL_DRV_NAME); + goto err_enable_dev; + } + + pci_set_master(pdev); + + rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (rc) { + wiphy_err(hw->wiphy, "%s: 32-bit PCI DMA not supported by host", + MWL_DRV_NAME); + goto err_set_dma; + } + + rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (rc) { + wiphy_err(hw->wiphy, "%s: 32-bit consistent PCI DMA not supported by host", + MWL_DRV_NAME); + goto err_set_dma; + } + + pci_set_drvdata(pdev, hw); + priv->dev = &pdev->dev; + rc = mwl_alloc_pci_resource(priv); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to allocate pci resource.\n", + MWL_DRV_NAME); + goto err_alloc_resource; + } + + spin_lock_init(&card->intr_status_lock); + rc = mwl_tx_init(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize TX\n", + MWL_DRV_NAME); + goto err_mwl_tx_init; + } + + rc = mwl_rx_init(hw); + if (rc) { + wiphy_err(hw->wiphy, "%s: fail to initialize RX\n", + MWL_DRV_NAME); + goto err_mwl_rx_init; + } + + return rc; + +err_mwl_rx_init: + mwl_tx_deinit(hw); + +err_mwl_tx_init: + mwl_free_pci_resource(priv); + +err_alloc_resource: + pci_set_drvdata(pdev, NULL); + +err_set_dma: + pci_disable_device(pdev); + +err_enable_dev: + return rc; +} + + +static int mwl_pcie_intr_init(struct mwl_priv *priv) +{ + int rc = 0, try_legacy_irq = 1; + struct mwl_pcie_card *card = priv->intf; + struct ieee80211_hw *hw = priv->hw; + + card->intr_mode = 0; + + if (pcie_intr_mode == 1) { + + rc = pci_enable_msi(card->pdev); + + if (rc) { + wiphy_err(hw->wiphy, "%s: pci_enable_msi failed %d\n", + MWL_DRV_NAME, rc); + } else { + rc = request_irq(card->pdev->irq, mwl_pcie_isr, + 0, MWL_DRV_NAME, priv->hw); + if (rc == 0) { + card->intr_mode = 1; + try_legacy_irq = 0; + } else { + wiphy_err(hw->wiphy, + "%s: request_irq MSI failed %d\n", + MWL_DRV_NAME, rc); + + pci_disable_msi(card->pdev); + + } + } + } + + if (try_legacy_irq) { + rc = request_irq(card->pdev->irq, mwl_pcie_isr, + IRQF_SHARED, MWL_DRV_NAME, priv->hw); + if (rc) { + priv->irq = -1; + wiphy_err(hw->wiphy, + "%s: request_irq Legacy failed %d\n", + MWL_DRV_NAME, rc); + + return rc; + } + } + + priv->irq = card->pdev->irq; + + return 0; +} + + +static int mwl_pcie_intr_deinit(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + if (priv->irq != -1) { + free_irq(priv->irq, priv->hw); + priv->irq = -1; + + if(card->intr_mode == 1) { + pci_disable_msi(card->pdev); + card->intr_mode = 0; + } + } + + return 0; +} + +static void mwl_pcie_cleanup(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + + mwl_rx_deinit(priv->hw); + mwl_tx_deinit(priv->hw); + + mwl_pcie_intr_deinit(priv); + + mwl_free_pci_resource(priv); + if (pdev) { + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + } +} + +static void mwl_fwdl_trig_pcicmd(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + writel(priv->pphys_cmd_buf, card->iobase1 + MACREG_REG_GEN_PTR); + + writel((u32)((u64)priv->pphys_cmd_buf >> 32), card->iobase1 + MACREG_REG_INT_CODE); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + card->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +static void mwl_fwdl_trig_pcicmd_bootcode(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *) priv->intf; + + /* Write the lower 32bits of the physical address to low command + * address scratch register + */ + writel((u32)priv->pphys_cmd_buf, card->iobase1 + MACREG_REG_GEN_PTR); + + /* Write the upper 32bits of the physical address to high command + * address scratch register + */ + writel((u32)((u64)priv->pphys_cmd_buf >> 32), card->iobase1 + MACREG_REG_INT_CODE); + + /* Ring the door bell */ + writel(MACREG_H2ARIC_BIT_DOOR_BELL, + card->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); +} + +static bool mwl_pcie_check_fw_status(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card; + u32 int_code = 0; + bool rc = false; + + u32 fwreadyReg = MACREG_REG_FW_STATUS; + u32 curr_iteration = 0; + + card = (struct mwl_pcie_card *)priv->intf; + + /* TBD do we need to disable interrupts before this */ + if (priv->mfg_mode) { + u32 fwreadysignature = MFG_FW_READY_SIGNATURE; + + writel(fwreadysignature, card->iobase1 + MACREG_REG_DRV_READY); + curr_iteration = 10; + + do { + curr_iteration--; + lrdmwl_delay(10); + + int_code = readl(card->iobase1 + fwreadyReg); + } while ((curr_iteration) && + ((int_code != MFG_FW_READY_SIGNATURE) && (int_code != HOSTCMD_SOFTAP_FWRDY_SIGNATURE))); + + if (curr_iteration) { + rc = true; + } + } + else + { + int_code = readl(card->iobase1 + fwreadyReg); + + if ((int_code == MFG_FW_READY_SIGNATURE) || + (int_code == HOSTCMD_SOFTAP_FWRDY_SIGNATURE)) + { + rc = true; + } + } + + return rc; +} + + +/* Combo firmware image is a combination of + * (1) combo crc heaer, start with CMD5 + * (2) bluetooth image, start with CMD7, end with CMD6, data wrapped in CMD1. + * (3) wifi image. + * + * This function bypass the header and bluetooth part, return + * the offset of tail wifi-only part. If the image is already wifi-only, + * that is start with CMD1, return 0. + */ + +static int mwl_extract_wifi_fw(struct mwl_priv *priv) { + struct ieee80211_hw *hw; + const void *firmware; + u32 firmware_len; + const struct mwifiex_fw_data *fwdata; + u32 offset = 0, data_len, dnld_cmd; + int ret = 0; + bool cmd7_before = false, first_cmd = false; + + hw = priv->hw; + + firmware = priv->fw_ucode->data; + firmware_len = priv->fw_ucode->size; + + while (1) { + /* Check for integer and buffer overflow */ + if (offset + sizeof(fwdata->header) < sizeof(fwdata->header) || + offset + sizeof(fwdata->header) >= firmware_len) { + wiphy_err(hw->wiphy, + "extract wifi-only fw failure!\n"); + ret = -1; + goto done; + } + + fwdata = firmware + offset; + dnld_cmd = le32_to_cpu(fwdata->header.dnld_cmd); + data_len = le32_to_cpu(fwdata->header.data_length); + + /* Skip past header */ + offset += sizeof(fwdata->header); + + switch (dnld_cmd) { + case MWIFIEX_FW_DNLD_CMD_1: + if (offset + data_len < data_len) { + wiphy_err(hw->wiphy, "bad FW parse\n"); + ret = -1; + goto done; + } + + /* Image start with cmd1, already wifi-only firmware */ + if (!first_cmd) { + wiphy_info(hw->wiphy, + "input wifi-only firmware\n"); + return 0; + } + + if (!cmd7_before) { + wiphy_err(hw->wiphy, + "no cmd7 before cmd1!\n"); + ret = -1; + goto done; + } + offset += data_len; + break; + case MWIFIEX_FW_DNLD_CMD_5: + first_cmd = true; + /* Check for integer overflow */ + if (offset + data_len < data_len) { + wiphy_err(hw->wiphy, "bad FW parse\n"); + ret = -1; + goto done; + } + offset += data_len; + break; + case MWIFIEX_FW_DNLD_CMD_6: + first_cmd = true; + /* Check for integer overflow */ + if (offset + data_len < data_len) { + wiphy_err(hw->wiphy, "bad FW parse\n"); + ret = -1; + goto done; + } + offset += data_len; + if (offset >= firmware_len) { + wiphy_err(hw->wiphy, + "extract wifi-only fw failure!\n"); + ret = -1; + } else { + ret = offset; + } + goto done; + case MWIFIEX_FW_DNLD_CMD_7: + first_cmd = true; + cmd7_before = true; + break; + default: + wiphy_err(hw->wiphy, "unknown dnld_cmd %d\n", + dnld_cmd); + ret = -1; + goto done; + } + } + +done: + return ret; +} + + +static int mwl_pcie_program_firmware(struct mwl_priv *priv) +{ + const struct firmware *fw; + struct ieee80211_hw *hw; + struct mwl_pcie_card *card; + u32 curr_iteration; + u32 size_fw_downloaded = 0; + u32 int_code = 0; + u32 len = 0; + u32 regval = MACREG_A2HRIC_BIT_MASK; + + + u32 fwreadysignature = priv->mfg_mode ? + MFG_FW_READY_SIGNATURE : HOSTCMD_SOFTAP_FWRDY_SIGNATURE; + u32 fwreadyReg = MACREG_REG_FW_STATUS; + + fw = priv->fw_ucode; + card = (struct mwl_pcie_card *)priv->intf; + hw = priv->hw; + + if (priv->mfg_mode) { + wiphy_info(hw->wiphy, "mfg_mode: overriding I/F header len to %d\n",INTF_HEADER_LEN_MFG); + priv->if_ops.inttf_head_len = INTF_HEADER_LEN_MFG; + } + + /* FW before jumping to boot rom, it will enable PCIe transaction retry, + * wait for boot code to stop it. + */ + lrdmwl_delay(5*1000); + + if(mwl_pcie_check_fw_status(priv)) + { + wiphy_err(hw->wiphy, "%s: FW already running - resetting\n", __func__); + INIT_WORK(&card->fw_reset_work, lrd_pci_fw_reset_workfn); + schedule_work(&card->fw_reset_work); + return -EINPROGRESS; + } + + if (!priv->tx_amsdu_enable) + regval &= ~MACREG_A2HRIC_BIT_QUE_EMPTY; + + writel(regval, card->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL); + writel(0x00, card->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + writel(0x00, card->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + writel(regval, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + /* this routine interacts with SC2 bootrom to download firmware binary + * to the device. After DMA'd to SC2, the firmware could be deflated to + * reside on its respective blocks such as ITCM, DTCM, SQRAM, + * (or even DDR, AFTER DDR is init'd before fw download + */ + wiphy_info(hw->wiphy, "Starting fw download\n"); + + /* make sure SCRATCH2 C40 (MACREG_REG_CMD_SIZE) is clear, in case we are too quick + * Worst case observed is ~150ms after FLR reset, which should already have been + * accounted for by the time we reach here. + */ + curr_iteration = 10; + while (((readl(card->iobase1 + MACREG_REG_CMD_SIZE) == 0) || + (readl(card->iobase1 + MACREG_REG_CMD_SIZE) == 0xffffffff)) && curr_iteration) + { + msleep(50); + curr_iteration--; + } + + if (!curr_iteration) + { + wiphy_err(hw->wiphy, "err polling MACREG_REG_CMD_SIZE!\n"); + goto err_download; + } + + int_code = readl(card->iobase1 + PCIE_SCRATCH_13_REG); + if (int_code == MWIFIEX_PCIE_FLR_HAPPENS) + { + int ret; + + wiphy_info(hw->wiphy, "Function Level Reset detected! Downloading WiFi FW only...\n"); + ret = mwl_extract_wifi_fw(priv); + if (ret < 0) + { + wiphy_err(hw->wiphy, "Failed to extract wifi fw\n"); + goto err_download; + } + + size_fw_downloaded = ret; + wiphy_info(hw->wiphy, "info: dnld wifi firmware from %d bytes\n", size_fw_downloaded); + } + + while (size_fw_downloaded < fw->size) { + len = readl(card->iobase1 + MACREG_REG_CMD_SIZE); + + if (!len) { + break; + } + + /* this copies the next chunk of fw binary to be delivered */ + memcpy((char *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(INTF_HEADER_LEN)], + (fw->data + size_fw_downloaded), len); + + /* Write the command length to cmd_size scratch register */ + writel(len, card->iobase1 + MACREG_REG_CMD_SIZE); + + /* this function writes pdata to c10, then write 2 to c18 */ + mwl_fwdl_trig_pcicmd_bootcode(priv); + + curr_iteration = 1000; + /* Wait for cmd done + * Worst case observed is ~1ms + */ + do { + int_code = readl(card->iobase1 + MACREG_REG_H2A_INTERRUPT_CAUSE); + if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) != + MACREG_H2ARIC_BIT_DOOR_BELL) + break; + + lrdmwl_delay(10); + curr_iteration--; + } while (curr_iteration); + + if (curr_iteration == 0) { + /* This limited loop check allows you to exit gracefully + * without locking up your entire system just because fw + * download failed + */ + wiphy_err(hw->wiphy, + "\nExhausted fw segment download\n"); + goto err_download; + } + + size_fw_downloaded += len; + } + + wiphy_info(hw->wiphy, + "FwSize = %d downloaded Size = %d\n", + (int)fw->size, size_fw_downloaded); + + /* 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 + */ + + if (!priv->mfg_mode) { + *((u32 *)&priv->pcmd_buf[INTF_CMDHEADER_LEN(INTF_HEADER_LEN)+1]) = 0; + mwl_fwdl_trig_pcicmd(priv); + } + else { + writel(fwreadysignature, card->iobase1 + MACREG_REG_DRV_READY); + } + + // Firmware initialization has been observed to take ~2.5 seconds + curr_iteration = 200; + do { + curr_iteration--; + if (!priv->mfg_mode) { + writel(HOSTCMD_SOFTAP_MODE, card->iobase1 + MACREG_REG_GEN_PTR); + } + + msleep(50); + + int_code = readl(card->iobase1 + fwreadyReg); + + } while ((curr_iteration) && (int_code != fwreadysignature)); + + if (curr_iteration == 0) { + wiphy_err(hw->wiphy, "No response from firmware! sig = 0x%x\n", int_code); + goto err_download; + } + + wiphy_info(hw->wiphy, "fw download complete\n"); + writel(0x00, card->iobase1 + MACREG_REG_INT_CODE); + + return 0; + +err_download: + + return -EIO; +} + +static bool mwl_pcie_check_card_status(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + u32 regval; + + regval = readl(card->iobase1 + MACREG_REG_INT_CODE); + if (regval == 0xffffffff) { + wiphy_err(priv->hw->wiphy, "adapter does not exist\n"); + return false; + } + + return true; +} + +static void mwl_pcie_enable_int(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + u32 regval = MACREG_A2HRIC_BIT_MASK; + + if (mwl_pcie_check_card_status(priv)) { + writel(0x00, card->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + + if (!priv->tx_amsdu_enable) + regval &= ~MACREG_A2HRIC_BIT_QUE_EMPTY; + writel(regval, card->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); + } +} + +static void mwl_pcie_disable_int(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + if (mwl_pcie_check_card_status(priv)) + writel(0x00, card->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); +} + +static int mwl_pcie_send_command(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + struct hostcmd_header *pcmd; + + pcmd = (struct hostcmd_header *)&priv->pcmd_buf[INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)]; + + + if (priv->mfg_mode) { + + if (pcmd->cmd != cpu_to_le16(HOSTCMD_CMD_MFG)) { + return 0; + } + + // XXX: Keeping i/f hdr as 0s for now + writel(le16_to_cpu(pcmd->len) + priv->if_ops.inttf_head_len, card->iobase1 + MACREG_REG_CMD_SIZE); + writel(priv->pphys_cmd_buf, card->iobase1 + MACREG_REG_CMDRSP_BUF_LO); + writel(0x0, card->iobase1 + MACREG_REG_CMDRSP_BUF_HI); + } + + writel(priv->pphys_cmd_buf, card->iobase1 + MACREG_REG_GEN_PTR); + + writel(MACREG_H2ARIC_BIT_DOOR_BELL, card->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + return 0; +} + +/* Check command response back or not */ +static int mwl_pcie_cmd_resp_wait_completed(struct mwl_priv *priv, + unsigned short cmd) +{ + unsigned int curr_iteration = MAX_WAIT_CMD_RESPONSE_ITERATIONS; + unsigned short int_code = 0; + + if (priv->mfg_mode && cmd != HOSTCMD_CMD_MFG) { + usleep_range(250, 500); + return 0; + } + + do { + usleep_range(250, 500); + int_code = le16_to_cpu(*((__le16 *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(priv->if_ops.inttf_head_len)])); + } while ((int_code != cmd) && (--curr_iteration)); + + if (curr_iteration == 0) { + wiphy_err(priv->hw->wiphy, "cmd 0x%04x=%s timed out\n", + cmd, mwl_fwcmd_get_cmd_string(cmd)); + wiphy_err(priv->hw->wiphy, "return code: 0x%04x\n", int_code); + return -EIO; + } + + if (priv->chip_type != MWL8997) + usleep_range(3000, 5000); + + return 0; +} + +static int mwl_pcie_host_to_card(struct mwl_priv *priv, int desc_num, + struct sk_buff *tx_skb) +{ + struct mwl_pcie_card *card = priv->intf; + struct mwl_tx_hndl *tx_hndl = NULL; + struct mwl_tx_desc *tx_desc; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_tx_info *tx_info; + + dma_addr_t dma; + unsigned int wrindx; + const unsigned int num_tx_buffs = MLAN_MAX_TXRX_BD << PCIE_TX_START_PTR; + tx_info = IEEE80211_SKB_CB(tx_skb); + tx_ctrl = (struct mwl_tx_ctrl *)&IEEE80211_SKB_CB(tx_skb)->status; + + if (!IS_PFU_ENABLED(priv->chip_type)) { + tx_hndl = priv->desc_data[desc_num].pnext_tx_hndl; + tx_hndl->psk_buff = tx_skb; + tx_desc = tx_hndl->pdesc; + } else { + struct mwl_tx_pfu_dma_data *dma_data = + (struct mwl_tx_pfu_dma_data *)tx_skb->data; + tx_desc = &dma_data->tx_desc; + } + + // clear flags then set if required + tx_desc->flags = 0; + if (tx_info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_DONT_ENCRYPT); + } + + if (tx_info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_NO_CCK_RATE); + } + + tx_desc->tx_priority = tx_ctrl->tx_priority; + tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl); + tx_desc->pkt_len = cpu_to_le16(tx_skb->len); + tx_desc->packet_info = 0; + tx_desc->data_rate = 0; + tx_desc->type = tx_ctrl->type; + tx_desc->xmit_control = tx_ctrl->xmit_control; + tx_desc->sap_pkt_info = 0; + + // Note - When PFU is enabled tx_skb contains the tx_desc + // Therefore must not touch the descriptor after the call to dma_map_single() + // + // This limitation does not exist when PFU is not enabled since the + // tx_desc is located in a separate coherent memory buffer + if (IS_PFU_ENABLED(priv->chip_type)) + tx_desc->pkt_ptr = cpu_to_le32(sizeof(struct mwl_tx_desc)); + + tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED); + + dma = dma_map_single(&card->pdev->dev, tx_skb->data, tx_skb->len, DMA_TO_DEVICE); + if (dma_mapping_error(&card->pdev->dev, dma)) { + dev_kfree_skb_any(tx_skb); + wiphy_err(priv->hw->wiphy, + "failed to map pci memory!\n"); + return -ENOMEM; + } + + if (!IS_PFU_ENABLED(priv->chip_type)) + tx_desc->pkt_ptr = cpu_to_le32(dma); + + if (IS_PFU_ENABLED(priv->chip_type)) { + wrindx = (priv->txbd_wrptr & MLAN_TXBD_MASK) >> PCIE_TX_START_PTR; +#if 0 + wiphy_err(priv->hw->wiphy, + "SEND DATA: Attach pmbuf %p at txbd_wridx=%d\n", tx_skb, wrindx); +#endif + priv->tx_buf_list[wrindx] = tx_skb; + priv->txbd_ring[wrindx]->paddr = cpu_to_le64(dma); + priv->txbd_ring[wrindx]->len = cpu_to_le16((unsigned short)tx_skb->len); + priv->txbd_ring[wrindx]->flags = cpu_to_le16(MLAN_BD_FLAG_FIRST_DESC | MLAN_BD_FLAG_LAST_DESC); + + priv->txbd_ring[wrindx]->frag_len = cpu_to_le16((unsigned short)tx_skb->len); + priv->txbd_ring[wrindx]->offset = 0; + priv->txbd_wrptr += MLAN_BD_FLAG_TX_START_PTR; + + if ((priv->txbd_wrptr & MLAN_TXBD_MASK) == num_tx_buffs) + priv->txbd_wrptr = ((priv->txbd_wrptr & MLAN_BD_FLAG_TX_ROLLOVER_IND) ^ + MLAN_BD_FLAG_TX_ROLLOVER_IND); + + /* + * Memory barrier required to ensure write to PCI does not pass writes to memory + */ + wmb(); + + /* Write the TX ring write pointer in to REG_TXBD_WRPTR */ + writel(priv->txbd_wrptr, card->iobase1 + REG_TXBD_WRPTR); + +#if 0 + wiphy_err(priv->hw->wiphy, + "SEND DATA: Updated \n", + priv->txbd_rdptr, priv->txbd_wrptr); +#endif + + } else { + /* + * Memory barrier required to ensure write to PCI does not pass writes to memory + */ + wmb(); + + writel(MACREG_H2ARIC_BIT_PPA_READY, card->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); + priv->desc_data[desc_num].pnext_tx_hndl = tx_hndl->pnext; + priv->fw_desc_cnt[desc_num]++; + } + + return 0; +} + +void mwl_non_pfu_tx_done(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + int num; + struct mwl_desc_data *desc; + struct mwl_tx_hndl *tx_hndl; + struct mwl_tx_desc *tx_desc; + struct sk_buff *done_skb; + u32 rate; + struct mwl_dma_data *tr; + struct ieee80211_tx_info *info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + int hdrlen; + + spin_lock_bh(&priv->tx_desc_lock); + for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) { + desc = &priv->desc_data[num]; + tx_hndl = desc->pstale_tx_hndl; + tx_desc = tx_hndl->pdesc; + + if ((tx_desc->status & + cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) && + (tx_hndl->pnext->pdesc->status & + cpu_to_le32(EAGLE_TXD_STATUS_OK))) + tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_OK); + + while (tx_hndl && + (tx_desc->status & cpu_to_le32(EAGLE_TXD_STATUS_OK)) && + (!(tx_desc->status & + cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)))) { + dma_unmap_single(&card->pdev->dev, + le32_to_cpu(tx_desc->pkt_ptr), + le16_to_cpu(tx_desc->pkt_len), + DMA_TO_DEVICE); + done_skb = tx_hndl->psk_buff; + rate = le32_to_cpu(tx_desc->rate_info); + tx_desc->pkt_ptr = 0; + tx_desc->pkt_len = 0; + tx_desc->status = + cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + tx_hndl->psk_buff = NULL; + wmb(); /* memory barrier */ + + tr = (struct mwl_dma_data *)done_skb->data; + info = IEEE80211_SKB_CB(done_skb); + + if (ieee80211_is_data(tr->wh.frame_control) || + ieee80211_is_data_qos(tr->wh.frame_control)) { + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + mwl_tx_ack_amsdu_pkts(hw, rate, + amsdu_pkts); + dev_kfree_skb_any(done_skb); + done_skb = NULL; + } else { + mwl_tx_prepare_info(hw, rate, info); + } + } else { + mwl_tx_prepare_info(hw, 0, info); + } + + if (done_skb) { + /* Remove H/W dma header */ + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + skb_pull(done_skb, sizeof(*tr) - hdrlen); + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(hw, done_skb); + } + + tx_hndl = tx_hndl->pnext; + tx_desc = tx_hndl->pdesc; + priv->fw_desc_cnt[num]--; + } + + desc->pstale_tx_hndl = tx_hndl; + } + spin_unlock_bh(&priv->tx_desc_lock); + + if (priv->is_tx_done_schedule) { + + mwl_set_bit(priv, MACREG_A2HRIC_BIT_NUM_TX_DONE, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + tasklet_schedule(priv->if_ops.ptx_task); + priv->is_tx_done_schedule = false; + } + + return; +} + +/* XXX: Tx Rate to be indicated to mac80211 - For KF2 PCIe & SDIO, +** driver has no way of knowing the rate at which the pkt was Tx'ed. +** Use hardcoded max value for this +*/ +static void mwl_tx_complete_skb(struct sk_buff *done_skb, + struct _mlan_pcie_data_buf *tx_ring_entry, + struct ieee80211_hw *hw) +{ + struct mwl_tx_desc *tx_desc; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + struct ieee80211_tx_info *info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + u32 rate; + struct mwl_tx_pfu_dma_data *tr = + (struct mwl_tx_pfu_dma_data *)done_skb->data; + int hdrlen; + + tx_desc = &tr->tx_desc; + +#if 0 +wiphy_err(priv->hw->wiphy, "unmap: skb=%p vdata=%p pdata=%p len=%d!\n", + done_skb, + done_skb->data, + le64_to_cpu(tx_ring_entry->paddr), + le16_to_cpu(tx_ring_entry->len)); +#endif + + dma_unmap_single(&card->pdev->dev, + le64_to_cpu(tx_ring_entry->paddr), + le16_to_cpu(tx_ring_entry->len), + DMA_TO_DEVICE); + +#if 0 + rate = le32_to_cpu(tx_desc->rate_info); +#endif + + tx_desc->pkt_ptr = 0; + tx_desc->pkt_len = 0; + tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_IDLE); + wmb(); /* memory barrier */ + + info = IEEE80211_SKB_CB(done_skb); + + if (ieee80211_is_data(tr->wh.frame_control) || + ieee80211_is_data_qos(tr->wh.frame_control)) { + rate = TX_COMP_RATE_FOR_DATA; + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + mwl_tx_ack_amsdu_pkts(hw, rate, + amsdu_pkts); + dev_kfree_skb_any(done_skb); + done_skb = NULL; + } else { + mwl_tx_prepare_info(hw, rate, info); + } + } else { + mwl_tx_prepare_info(hw, 0, info); + } + + if (done_skb) { + /* Remove H/W dma header */ + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + skb_pull(done_skb, sizeof(*tr) - hdrlen); + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(hw, done_skb); + } +} + +void mwl_pfu_tx_done(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + struct sk_buff *done_skb; + u32 wrdoneidx, rdptr; + const unsigned int num_tx_buffs = MLAN_MAX_TXRX_BD << PCIE_TX_START_PTR; + + spin_lock_bh(&priv->tx_desc_lock); + + /* Read the TX ring read pointer set by firmware */ + rdptr = readl(card->iobase1 + REG_TXBD_RDPTR); + +#if 0 + wiphy_err(hw->wiphy, "SEND DATA COMP: rdptr_prev=0x%x, rdptr=0x%x\n", + priv->txbd_rdptr, rdptr); +#endif + + /* free from previous txbd_rdptr to current txbd_rdptr */ + while (((priv->txbd_rdptr & MLAN_TXBD_MASK) + != (rdptr & MLAN_TXBD_MASK)) + || ((priv->txbd_rdptr & MLAN_BD_FLAG_TX_ROLLOVER_IND) + != (rdptr & MLAN_BD_FLAG_TX_ROLLOVER_IND))) { + wrdoneidx = priv->txbd_rdptr & MLAN_TXBD_MASK; + wrdoneidx >>= PCIE_TX_START_PTR; + + done_skb = priv->tx_buf_list[wrdoneidx]; + if (done_skb) + mwl_tx_complete_skb(done_skb, priv->txbd_ring[wrdoneidx], hw); + + priv->tx_buf_list[wrdoneidx] = MNULL; + priv->txbd_ring[wrdoneidx]->paddr = 0; + priv->txbd_ring[wrdoneidx]->len = 0; + priv->txbd_ring[wrdoneidx]->flags = 0; + priv->txbd_ring[wrdoneidx]->frag_len = 0; + priv->txbd_ring[wrdoneidx]->offset = 0; + priv->txbd_rdptr += MLAN_BD_FLAG_TX_START_PTR; + if ((priv->txbd_rdptr & MLAN_TXBD_MASK) == num_tx_buffs) + priv->txbd_rdptr = ((priv->txbd_rdptr & + MLAN_BD_FLAG_TX_ROLLOVER_IND) ^ + MLAN_BD_FLAG_TX_ROLLOVER_IND); + } + + spin_unlock_bh(&priv->tx_desc_lock); + if (priv->is_tx_done_schedule) { + + mwl_set_bit(priv, MACREG_A2HRIC_BIT_NUM_TX_DONE, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + + tasklet_schedule(priv->if_ops.ptx_task); + priv->is_tx_done_schedule = false; + } +} + +void mwl_pcie_tx_done(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + + /* Return all skbs to mac80211 */ + if (IS_PFU_ENABLED(priv->chip_type)) + mwl_pfu_tx_done((unsigned long)hw); + else + mwl_non_pfu_tx_done((unsigned long)hw); +} + + +#ifdef BG4CT_A0_WORKAROUND +#define MAX_ISR_ITERATION 2 +#endif +static +irqreturn_t mwl_pcie_isr(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct mwl_priv *priv = hw->priv; + void __iomem *int_status_mask; + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + unsigned int int_status; + + if (card->surprise_removed || priv->recovery_in_progress) { + return IRQ_HANDLED; + } + + int_status_mask = card->iobase1 + + MACREG_REG_A2H_INTERRUPT_STATUS_MASK; + + int_status = readl(card->iobase1 + + MACREG_REG_A2H_INTERRUPT_CAUSE); + + if (int_status == 0x00000000) + return IRQ_NONE; + + if (int_status == 0xffffffff) { + wiphy_warn(hw->wiphy, "card unplugged?\n"); + } else { + writel(~int_status, + card->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); + +#ifdef CONFIG_DEBUG_FS + priv->valid_interrupt_cnt++; +#endif + + if (int_status & MACREG_A2HRIC_BIT_TX_DONE) { + if (!priv->is_tx_done_schedule) { + mwl_clear_bit(priv, MACREG_A2HRIC_BIT_NUM_TX_DONE, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(priv->if_ops.ptx_done_task); + priv->is_tx_done_schedule = true; + } + } + + if (int_status & MACREG_A2HRIC_BIT_RX_RDY) { + if (priv->mac_started) { + mwl_clear_bit(priv, MACREG_A2HRIC_BIT_NUM_RX_RDY, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + tasklet_schedule(&priv->rx_task); + } + } + + if (int_status & MACREG_A2HRIC_BIT_RADAR_DETECT) { + wiphy_info(hw->wiphy, "radar detected by firmware\n"); + ieee80211_radar_detected(hw); + } + + if (int_status & MACREG_A2HRIC_BIT_QUE_EMPTY) { + if (priv->mac_started) { + if (time_after(jiffies, (priv->qe_trigger_time + 1))) { + mwl_clear_bit(priv, MACREG_A2HRIC_BIT_NUM_QUE_EMPTY, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + if (priv->tx_amsdu_enable) { + tasklet_schedule(priv->if_ops.pqe_task); + priv->qe_trigger_num++; + priv->qe_trigger_time = jiffies; + } + } + } + } + + if (int_status & MACREG_A2HRIC_BIT_CHAN_SWITCH) + ieee80211_queue_work(hw, &priv->chnl_switch_handle); + + if (int_status & MACREG_A2HRIC_BA_WATCHDOG) + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + } + + return IRQ_HANDLED; +} + +static void mwl_pcie_read_register(struct mwl_priv *priv, + int index, int reg, u32 *data) +{ + struct mwl_pcie_card *card = priv->intf; + + if (index == 0) + *data = readl(card->iobase0 + reg); + else + *data = readl(card->iobase1 + reg); +} + +static void mwl_pcie_write_register(struct mwl_priv *priv, + int index, int reg, u32 data) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + if (index == 0) + writel(data, card->iobase0 + reg); + else + writel(data, card->iobase1 + reg); +} + +static int mwl_pcie_register_dev(struct mwl_priv *priv) +{ + struct mwl_pcie_card *card; + int rc = 0; + + card = (struct mwl_pcie_card *)priv->intf; + + rc = mwl_pcie_intr_init(priv); + + if (rc) { + return rc; + } + +#ifndef NEW_DP + priv->if_ops.ptx_task = &card->tx_task; + tasklet_init(priv->if_ops.ptx_task, (void *)mwl_tx_skbs, + (unsigned long)priv->hw); + tasklet_disable(priv->if_ops.ptx_task); + + priv->if_ops.ptx_done_task = &card->tx_done_task; + tasklet_init(priv->if_ops.ptx_done_task, + (void *)mwl_pcie_tx_done, (unsigned long)priv->hw); + tasklet_disable(priv->if_ops.ptx_done_task); + + if (priv->tx_amsdu_enable) { + priv->if_ops.pqe_task = &card->qe_task; + tasklet_init(priv->if_ops.pqe_task, (void *)mwl_pcie_tx_flush_amsdu, + (unsigned long)priv->hw); + tasklet_disable(priv->if_ops.pqe_task); + } +#endif + + tasklet_init(&priv->rx_task, (void *)mwl_pcie_rx_recv, + (unsigned long)priv->hw); + tasklet_disable(&priv->rx_task); + + return rc; +} + +static void mwl_pcie_unregister_dev(struct mwl_priv *priv) +{ + wiphy_warn(priv->hw->wiphy, + "%s(): tasklet_pending[txd=%d]\n", + __FUNCTION__, + priv->is_tx_done_schedule); + +#ifndef NEW_DP + if (priv->if_ops.ptx_task != NULL) + tasklet_kill(priv->if_ops.ptx_task); + + if (priv->if_ops.ptx_done_task != NULL) + tasklet_kill(priv->if_ops.ptx_done_task); + + if (priv->if_ops.pqe_task != NULL) + tasklet_kill(priv->if_ops.pqe_task); +#endif /* NEW_DP */ + + tasklet_kill(&priv->rx_task); +} + +static void mwl_pcie_tx_flush_amsdu(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + struct mwl_sta *sta_info; + int i; + struct mwl_amsdu_frag *amsdu_frag; + +// TODO: RR: Take spin_lock_bh() here ?? + spin_lock(&priv->tx_desc_lock); + spin_lock(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + spin_lock(&sta_info->amsdu_lock); + for (i = 0; i < SYSADPT_TX_WMM_QUEUES; i++) { + amsdu_frag = &sta_info->amsdu_ctrl.frag[i]; + if (amsdu_frag->num) { + if (time_after(jiffies, + (amsdu_frag->jiffies + 1))) { + if (mwl_pcie_is_tx_available(priv, i)) { + /* wiphy_err(priv->hw->wiphy, + * "%s()\n", __FUNCTION__); + */ + mwl_tx_skb(priv, i, + amsdu_frag->skb); + amsdu_frag->num = 0; + amsdu_frag->cur_pos = NULL; + } + } + } + } + spin_unlock(&sta_info->amsdu_lock); + } + spin_unlock(&priv->sta_lock); + spin_unlock(&priv->tx_desc_lock); + + mwl_set_bit(priv, MACREG_A2HRIC_BIT_NUM_QUE_EMPTY, card->iobase1 + MACREG_REG_A2H_INTERRUPT_STATUS_MASK); + +} + +#ifdef CONFIG_DEBUG_FS +static int mwl_pcie_dbg_info(struct mwl_priv *priv, char *p, int size, int len) +{ + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + len += scnprintf(p + len, size - len, "irq number: %d\n", priv->irq); + len += scnprintf(p + len, size - len, "iobase0: %p\n", card->iobase0); + len += scnprintf(p + len, size - len, "iobase1: %p\n", card->iobase1); + + return len; +} + +static int mwl_pcie_debugfs_reg_access(struct mwl_priv *priv, bool write) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + u8 set; + u32 *addr_val; + int ret = 0; + + set = write ? WL_SET : WL_GET; + + switch (priv->reg_type) { + case MWL_ACCESS_ADDR0: + if (set == WL_GET) + priv->reg_value = readl(card->iobase0 + priv->reg_offset); + else + writel(priv->reg_value, card->iobase0 + priv->reg_offset); + break; + case MWL_ACCESS_ADDR1: + if (set == WL_GET) + priv->reg_value = readl(card->iobase1 + priv->reg_offset); + else + writel(priv->reg_value, card->iobase1 + priv->reg_offset); + break; + case MWL_ACCESS_ADDR: + addr_val = kmalloc(64 * sizeof(u32), GFP_KERNEL); + if (addr_val) { + memset(addr_val, 0, 64 * sizeof(u32)); + addr_val[0] = priv->reg_value; + ret = mwl_fwcmd_get_addr_value(hw, priv->reg_offset, 4, addr_val, set); + + if ((!ret) && (set == WL_GET)) { + priv->reg_value = addr_val[0]; + } + + kfree(addr_val); + } else { + ret = -ENOMEM; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +#endif + +static int mwl_pcie_wakeup_card(struct mwl_priv *priv) +{ + return 0; +} + +static int mwl_pcie_is_deepsleep(struct mwl_priv * priv) +{ + return 0; +} + +static void mwl_pcie_enter_deepsleep(struct mwl_priv *priv) +{ + return; +} + +/* + * This function initializes the PCI-E host memory space, WCB rings, etc., + * similar to mwl_pcie_init(), but without resetting PCI-E state. + */ +static void mwl_pcie_up_dev(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + + wiphy_info(priv->hw->wiphy, "%s: Bringing up adapter...\n", MWL_DRV_NAME); + + /* + * Initialize tx/rx buffers, enable bus mastering + */ + mwl_tx_init(hw); + mwl_rx_init(hw); + pci_set_master(pdev); + + return; +} + +/* This function cleans up the PCI-E host memory space. */ +static void mwl_pcie_down_dev(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_pcie_card *card = priv->intf; + struct pci_dev *pdev = card->pdev; + + wiphy_info(priv->hw->wiphy, "%s: Taking down adapter...\n", MWL_DRV_NAME); + + pci_clear_master(pdev); + mwl_rx_deinit(hw); + mwl_tx_deinit(hw); +} + + +static struct mwl_if_ops pcie_ops = { + .inttf_head_len = INTF_HEADER_LEN, + //Tasklets are assigned per instance during device registration + .ptx_task = NULL, + .ptx_done_task = NULL, + .pqe_task = NULL, + .init_if = mwl_pcie_init, + .init_if_post = mwl_pcie_init_post, + .cleanup_if = mwl_pcie_cleanup, + .check_card_status = mwl_pcie_check_card_status, + .prog_fw = mwl_pcie_program_firmware, + .enable_int = mwl_pcie_enable_int, + .disable_int = mwl_pcie_disable_int, + .send_cmd = mwl_pcie_send_command, + .cmd_resp_wait_completed = mwl_pcie_cmd_resp_wait_completed, + .register_dev = mwl_pcie_register_dev, +// .unregister_dev = mwl_pcie_unregister_dev, + .is_tx_available = mwl_pcie_is_tx_available, + .host_to_card = mwl_pcie_host_to_card, + .read_reg = mwl_pcie_read_register, + .write_reg = mwl_pcie_write_register, + .tx_done = mwl_pcie_tx_done, +#ifdef CONFIG_DEBUG_FS + .dbg_info = mwl_pcie_dbg_info, + .dbg_reg_access = mwl_pcie_debugfs_reg_access, +#endif + .enter_deepsleep = mwl_pcie_enter_deepsleep, + .wakeup_card = mwl_pcie_wakeup_card, + .is_deepsleep = mwl_pcie_is_deepsleep, + .down_dev = mwl_pcie_down_dev, + .up_dev = mwl_pcie_up_dev, + + .hardware_restart = mwl_pcie_restart_handler, +}; + + +static int mwl_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + static bool printed_version; + struct mwl_pcie_card *card; + int rc = 0; + + if (id->driver_data >= MWLUNKNOWN) + return -ENODEV; + + if (!printed_version) { + pr_info("<<%s version %s>>", + LRD_DESC, LRD_DRV_VERSION); + printed_version = true; + } + + card = kzalloc(sizeof(struct mwl_pcie_card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->chip_type = id->driver_data; + card->pdev = pdev; + card->surprise_removed = false; + + memcpy(&pcie_ops.mwl_chip_tbl, &mwl_chip_tbl[card->chip_type], + sizeof(struct mwl_chip_info)); + + /* device tree node parsing and platform specific configuration*/ + if (dev_of_node(&pdev->dev)) { + rc = mwl_pcie_probe_of(&pdev->dev); + if (rc) + goto err_add_card; + } + + rc = mwl_add_card(card, &pcie_ops, dev_of_node(&pdev->dev)); + if (rc) { + pr_err("%s: add card failed", MWL_DRV_NAME); + goto err_add_card; + } + return rc; + +err_add_card: + kfree(card); + return rc; + +} + +static void mwl_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct mwl_priv *priv; + struct mwl_pcie_card *card; + + if (!hw) + return; + + priv = hw->priv; + card = (struct mwl_pcie_card *)priv->intf; + + card->surprise_removed = true; + + if (priv->init_complete) + mwl_wl_deinit(priv); + + mwl_pcie_cleanup(priv); + + mwl_pcie_unregister_dev(priv); + + mwl_ieee80211_free_hw(priv); + kfree(card); + +#if 0 + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +#endif + +} + +static int mwl_pcie_restart_handler(struct mwl_priv *priv) +{ + int rc = 0; + struct mwl_pcie_card *card = (struct mwl_pcie_card *)priv->intf; + + /* We can't afford to wait here; remove() might be waiting on us. If we + * can't grab the device lock, maybe we'll get another chance later. + */ + rc = pci_reset_function(card->pdev); + + if (rc != 0) + pr_err("%s: PCI reset attempt failed with error %d!", MWL_DRV_NAME, rc); + + return rc; +} + + +#if 0 +static u32 pci_read_mac_reg(struct mwl_priv *priv, u32 offset) +{ + if (priv->chip_type == MWL8964) { + u32 *addr_val = kmalloc(64 * sizeof(u32), GFP_ATOMIC); + u32 val; + + if (addr_val) { + mwl_fwcmd_get_addr_value(priv->hw, + 0x8000a000 + offset, 4, + addr_val, 0); + val = addr_val[0]; + kfree(addr_val); + return val; + } + return 0; + } else + return le32_to_cpu(*(__le32 * __force) + (MAC_REG_ADDR_PCI(offset))); +} +#endif + +static const struct pci_error_handlers mwl_pcie_err_handler = { + .reset_prepare = mwl_pcie_reset_prepare, + .reset_done = mwl_pcie_reset_done, +}; + +static struct pci_driver mwl_pci_driver = { + .name = MWL_DRV_NAME, + .id_table = mwl_pci_id_tbl, + .probe = mwl_probe, + .remove = mwl_remove, + + .err_handler = &mwl_pcie_err_handler, +}; + +module_pci_driver(mwl_pci_driver); +MODULE_DEVICE_TABLE(pci, mwl_pci_id_tbl); + + +MODULE_DESCRIPTION(LRD_PCIE_DESC); +MODULE_VERSION(LRD_PCIE_VERSION); +MODULE_AUTHOR(LRD_AUTHOR); +MODULE_LICENSE("GPL v2"); + +module_param(pcie_intr_mode, int, 0); +MODULE_PARM_DESC(pcie_intr_mode, "0: Legacy, 1: MSI"); diff --git a/drivers/net/wireless/laird/lrdmwl/pcie.h b/drivers/net/wireless/laird/lrdmwl/pcie.h new file mode 100644 index 0000000000000..d1759cc526748 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/pcie.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _PCIE_H_ +#define _PCIE_H_ + +#include +#include + +#include "main.h" + +#define MAC_REG_ADDR(offset) (offset + 0xA000) +#define MAC_REG_ADDR_PCI(offset) ((card->iobase1 + 0xA000) + offset) + +#define MCU_CCA_CNT MAC_REG_ADDR(0x06A0) +#define MCU_TXPE_CNT MAC_REG_ADDR(0x06A4) +#define MCU_LAST_READ MAC_REG_ADDR(0x06A8) + +#define MAC_REG_TCQ0_WRPTR MAC_REG_ADDR(0x0040) +#define MAC_REG_TCQ0_RDPTR MAC_REG_ADDR(0x0044) + +#define MAC_REG_CW0_MIN MAC_REG_ADDR(0x00a0) +#define MAC_REG_CW0_MAX MAC_REG_ADDR(0x00a4) +#define MAC_REG_TXOP0 MAC_REG_ADDR(0x0260) +#define MAC_REG_AIFSN0 MAC_REG_ADDR(0x0680) + +#define MWIFIEX_PCIE_FLR_HAPPENS 0xFEDCBABA + +struct mwl_pcie_card { + struct mwl_priv *priv; + struct pci_dev *pdev; + bool surprise_removed; + int chip_type; + void __iomem *iobase0; /* MEM Base Address Register 0 */ + void __iomem *iobase1; /* MEM Base Address Register 1 */ + u32 next_bar_num; + struct mwl_desc_data desc_data[SYSADPT_NUM_OF_DESC_DATA]; + /* number of descriptors owned by fw at any one time */ + int fw_desc_cnt[SYSADPT_NUM_OF_DESC_DATA]; + int intr_mode; + spinlock_t intr_status_lock; + + struct tasklet_struct tx_task; + struct tasklet_struct tx_done_task; + struct tasklet_struct qe_task; + + struct work_struct fw_reset_work; +}; + +void mwl_pcie_tx_done(unsigned long data); +void mwl_pcie_rx_recv(unsigned long data); + +#endif diff --git a/drivers/net/wireless/laird/lrdmwl/pfu.c b/drivers/net/wireless/laird/lrdmwl/pfu.c new file mode 100644 index 0000000000000..a464f7717b247 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/pfu.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "sysadpt.h" +#include "dev.h" +#include "pcie.h" + +int wlan_pcie_create_txbd_ring(struct ieee80211_hw *hw) +{ + int ret = 0; + unsigned int i; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + + /* + * driver maintaines the write pointer and firmware maintaines the read + * pointer. + */ + priv->txbd_wrptr = 0; + priv->txbd_rdptr = 0; + + /* allocate shared memory for the BD ring and divide the same in to + * several descriptors + */ + priv->txbd_ring_size = + sizeof(struct _mlan_pcie_data_buf) * MLAN_MAX_TXRX_BD; + wiphy_info(hw->wiphy, "TX ring: allocating %d bytes\n", + priv->txbd_ring_size); + + priv->txbd_ring_vbase = dma_alloc_coherent(&card->pdev->dev, + priv->txbd_ring_size, (dma_addr_t *)&priv->txbd_ring_pbase, + GFP_ATOMIC); + + if (!priv->txbd_ring_vbase) { + wiphy_err(hw->wiphy, + "%s: No free moal_malloc_consistent\n", __func__); + return MLAN_STATUS_FAILURE; + } + + wiphy_info(hw->wiphy, + "TX ring: - base: %p, pbase: %#x:%x,len: %x\n", + priv->txbd_ring_vbase, + (unsigned int)((unsigned long long)priv->txbd_ring_pbase >> 32), + (unsigned int)priv->txbd_ring_pbase, + priv->txbd_ring_size); + + for (i = 0; i < MLAN_MAX_TXRX_BD; i++) { + priv->txbd_ring[i] = (struct _mlan_pcie_data_buf *) + (priv->txbd_ring_vbase + + (sizeof(struct _mlan_pcie_data_buf) * i)); + priv->tx_buf_list[i] = MNULL; + priv->txbd_ring[i]->paddr = 0; + priv->txbd_ring[i]->len = 0; + priv->txbd_ring[i]->flags = 0; + priv->txbd_ring[i]->frag_len = 0; + priv->txbd_ring[i]->offset = 0; + } + + return ret; +} + +int wlan_pcie_delete_txbd_ring(struct ieee80211_hw *hw) +{ + unsigned int i; + struct sk_buff *skb; + struct mwl_tx_desc *tx_desc; + struct mwl_priv *priv = hw->priv; + struct mwl_pcie_card *card = priv->intf; + + for (i = 0; i < MLAN_MAX_TXRX_BD; i++) { + if (priv->tx_buf_list[i]) { + skb = priv->tx_buf_list[i]; + tx_desc = (struct mwl_tx_desc *)skb->data; + + dma_unmap_single(&card->pdev->dev, + le32_to_cpu(tx_desc->pkt_ptr), + le16_to_cpu(tx_desc->pkt_len), + DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + } + priv->tx_buf_list[i] = MNULL; + priv->txbd_ring[i]->paddr = 0; + priv->txbd_ring[i]->len = 0; + priv->txbd_ring[i]->flags = 0; + priv->txbd_ring[i]->frag_len = 0; + priv->txbd_ring[i]->offset = 0; + priv->txbd_ring[i] = MNULL; + } + + if (priv->txbd_ring_vbase) { + dma_free_coherent(&card->pdev->dev, + priv->txbd_ring_size, + priv->txbd_ring_vbase, + priv->txbd_ring_pbase); + } + priv->txbd_ring_size = 0; + priv->txbd_wrptr = 0; + priv->txbd_rdptr = 0; + priv->txbd_ring_vbase = MNULL; + priv->txbd_ring_pbase = 0; + + return MLAN_STATUS_SUCCESS; +} diff --git a/drivers/net/wireless/laird/lrdmwl/pfu.h b/drivers/net/wireless/laird/lrdmwl/pfu.h new file mode 100644 index 0000000000000..37cc1c274ff80 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/pfu.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "port.h" + +#define IS_PFU_ENABLED(ct) (ct == MWL8997) + +/* PCIE read data pointer for queue 0 and 1 */ +#define PCIE_RD_DATA_PTR_Q0_Q1 0xC1A4 /* 0x8000C1A4 */ +/* PCIE read data pointer for queue 2 and 3 */ +#define PCIE_RD_DATA_PTR_Q2_Q3 0xC1A8 /* 0x8000C1A8 */ +/* PCIE write data pointer for queue 0 and 1 */ +#define PCIE_WR_DATA_PTR_Q0_Q1 0xC174 /* 0x8000C174 */ +/* PCIE write data pointer for queue 2 and 3 */ +#define PCIE_WR_DATA_PTR_Q2_Q3 0xC178 /* 0x8000C178 */ + + +/* TX buffer description read pointer */ +#define REG_TXBD_RDPTR PCIE_RD_DATA_PTR_Q0_Q1 +/* TX buffer description write pointer */ +#define REG_TXBD_WRPTR PCIE_WR_DATA_PTR_Q0_Q1 + + +#define PCIE_TX_START_PTR 16 + +#define MLAN_TXBD_MASK 0x0FFF0000 +#define MLAN_TXBD_WRAP_MASK 0x1FFF0000 + + +#define MLAN_BD_FLAG_RX_ROLLOVER_IND MBIT(12) +#define MLAN_BD_FLAG_TX_START_PTR MBIT(16) +#define MLAN_BD_FLAG_TX_ROLLOVER_IND MBIT(28) +#define MLAN_BD_FLAG_TX2_START_PTR MBIT(0) +#define MLAN_BD_FLAG_TX2_ROLLOVER_IND MBIT(12) + +#define MLAN_BD_FLAG_FIRST_DESC MBIT(0) +#define MLAN_BD_FLAG_LAST_DESC MBIT(1) + + +#define MLAN_MAX_TXRX_BD 0x20 + + + +#define PCIE_TXBD_NOT_FULL(wrptr, rdptr) \ + (((wrptr & MLAN_TXBD_MASK) != (rdptr & MLAN_TXBD_MASK)) \ + || ((wrptr & MLAN_BD_FLAG_TX_ROLLOVER_IND) == \ + (rdptr & MLAN_BD_FLAG_TX_ROLLOVER_IND))) + + +MLAN_PACK_START struct _mlan_pcie_data_buf { + /** Buffer descriptor flags */ + unsigned short flags; + /** Offset of fragment/pkt to start of ip header */ + unsigned short offset; + /** Fragment length of the buffer */ + unsigned short frag_len; + /** Length of the buffer */ + unsigned short len; + /** Physical address of the buffer */ + unsigned long long paddr; + /** Reserved */ + unsigned int reserved; +} MLAN_PACK_END; + +int wlan_pcie_create_txbd_ring(struct ieee80211_hw *hw); +int wlan_pcie_delete_txbd_ring(struct ieee80211_hw *hw); diff --git a/drivers/net/wireless/laird/lrdmwl/port.h b/drivers/net/wireless/laird/lrdmwl/port.h new file mode 100644 index 0000000000000..347f5a783627f --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/port.h @@ -0,0 +1,58 @@ + +/****************************************************** +* Change log: +* 11/07/2008: initial version +******************************************************/ + +#ifndef _MLAN_DECL_H_ +#define _MLAN_DECL_H_ + +/** MLAN release version */ +#define MLAN_RELEASE_VERSION "C204" + + +/** Constants below */ + +#ifdef __GNUC__ +/** Structure packing begins */ +#define MLAN_PACK_START +/** Structure packeing end */ +#define MLAN_PACK_END __attribute__((packed)) +#else /* !__GNUC__ */ +#ifdef PRAGMA_PACK +/** Structure packing begins */ +#define MLAN_PACK_START +/** Structure packeing end */ +#define MLAN_PACK_END +#else /* !PRAGMA_PACK */ +/** Structure packing begins */ +#define MLAN_PACK_START __packed +/** Structure packing end */ +#define MLAN_PACK_END +#endif /* PRAGMA_PACK */ +#endif /* __GNUC__ */ + +#ifndef INLINE +#ifdef __GNUC__ +/** inline directive */ +#define INLINE inline +#else +/** inline directive */ +#define INLINE inline +#endif +#endif + +#define MNULL NULL +#define MLAN_STATUS_FAILURE (-1) +#define MLAN_STATUS_SUCCESS (0) + +/** MLAN TRUE */ +#define MTRUE (1) +/** MLAN FALSE */ +#define MFALSE (0) + +/** BIT value */ +#define MBIT(x) (((unsigned int)1) << (x)) + + +#endif diff --git a/drivers/net/wireless/laird/lrdmwl/rx.c b/drivers/net/wireless/laird/lrdmwl/rx.c new file mode 100644 index 0000000000000..571e70b2fbdf4 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/rx.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements receive related functions. */ + +#include +#include + +#include "sysadpt.h" +#include "dev.h" +#include "rx.h" +#include "main.h" + + +#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 W8997_RSSI_OFFSET 0 + +/* Receive rate information constants */ +#define RX_RATE_INFO_FORMAT_11A 0 +#define RX_RATE_INFO_FORMAT_11B 1 +#define RX_RATE_INFO_FORMAT_11N 2 +#define RX_RATE_INFO_FORMAT_11AC 4 + +#define RX_RATE_INFO_HT20 0 +#define RX_RATE_INFO_HT40 1 +#define RX_RATE_INFO_HT80 2 +#define RX_RATE_INFO_HT160 3 + +#define RX_RATE_INFO_LONG_INTERVAL 0 +#define RX_RATE_INFO_SHORT_INTERVAL 1 + +struct mwl_vif *mwl_find_first_sta(struct mwl_priv *priv); + +void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, + struct ieee80211_rx_status *status) +{ + u16 rate, format, nss, bw, gi, rt; + + memset(status, 0, sizeof(*status)); + + status->signal = pdesc->rssi - W8997_RSSI_OFFSET; + + rate = le16_to_cpu(pdesc->rate); + format = rate & MWL_RX_RATE_FORMAT_MASK; + nss = (rate & MWL_RX_RATE_NSS_MASK) >> MWL_RX_RATE_NSS_SHIFT; + bw = (rate & MWL_RX_RATE_BW_MASK) >> MWL_RX_RATE_BW_SHIFT; + gi = (rate & MWL_RX_RATE_GI_MASK) >> MWL_RX_RATE_GI_SHIFT; + rt = (rate & MWL_RX_RATE_RT_MASK) >> MWL_RX_RATE_RT_SHIFT; + + switch (format) { + case RX_RATE_INFO_FORMAT_11N: + status->encoding = RX_ENC_HT; + if (bw == RX_RATE_INFO_HT40) + status->bw |= RATE_INFO_BW_40; + if (gi == RX_RATE_INFO_SHORT_INTERVAL) + status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + break; + case RX_RATE_INFO_FORMAT_11AC: + status->encoding |= RX_ENC_VHT; + if (bw == RX_RATE_INFO_HT40) + status->bw |= RATE_INFO_BW_40; + if (bw == RX_RATE_INFO_HT80) + status->bw |= RATE_INFO_BW_80; + if (bw == RX_RATE_INFO_HT160) + status->bw |= RATE_INFO_BW_160; + if (gi == RX_RATE_INFO_SHORT_INTERVAL) + status->enc_flags |= RX_ENC_FLAG_SHORT_GI; + status->nss = (nss + 1); + break; + } + + // Firmware is encoding legacy rates using an internal low level + // value instead of the sequential table index value the driver + // is expecting. + + // Translate the values the firmware is sending to the value the driver was expecting to see here + switch (format) { + case RX_RATE_INFO_FORMAT_11B: + switch(rt) { + case 10: status->rate_idx = 0; break; + case 4: status->rate_idx = 1; break; + case 7: status->rate_idx = 2; break; + case 14: status->rate_idx = 3; break; + // Note - Firmware supports 22Mbps and reports it as 12 + // at what should be index 4. We should never see that + // and don't have a rate entry for it + case 12: + default: status->rate_idx = 0; break; + }; + break; + case RX_RATE_INFO_FORMAT_11A: + // Note - accounting for the additional 22Mbps rate definition + // in firmware here by removing it from the adjusted firmware value + switch(rt) { + case 11: status->rate_idx = 5-1; break; + case 15: status->rate_idx = 6-1; break; + case 10: status->rate_idx = 7-1; break; + case 14: status->rate_idx = 8-1; break; + case 9: status->rate_idx = 9-1; break; + case 13: status->rate_idx = 10-1; break; + case 8: status->rate_idx = 11-1; break; + case 12: status->rate_idx = 12-1; break; + // Note - Firmware suports 72Mbps and reports it as 7 + // at what should be index 13. We should never see that + // and don't have a rate entry for it + case 7: + default: status->rate_idx = 5-1; break; + }; + break; + case RX_RATE_INFO_FORMAT_11N: + case RX_RATE_INFO_FORMAT_11AC: + // These values are sent as expected + status->rate_idx = rt; + break; + }; + + if (pdesc->channel > BAND_24_CHANNEL_NUM) { + status->band = NL80211_BAND_5GHZ; + if ((!(status->encoding & RX_ENC_HT)) && + (!(status->encoding & RX_ENC_VHT))) { + // Translate to 5G rate table index which does not + // include 802.11b rate entries + status->rate_idx -= 4; + if (status->rate_idx >= BAND_50_RATE_NUM) + status->rate_idx = BAND_50_RATE_NUM - 1; + } + } else { + status->band = NL80211_BAND_2GHZ; + if ((!(status->encoding & RX_ENC_HT)) && + (!(status->encoding & RX_ENC_VHT))) { + if (status->rate_idx >= BAND_24_RATE_NUM) + status->rate_idx = BAND_24_RATE_NUM - 1; + } + } + + status->freq = ieee80211_channel_to_frequency(pdesc->channel, + status->band); + + /* check if status has a specific error bit (bit 7) set or indicates + * a general decrypt error + */ + if ((pdesc->status == GENERAL_DECRYPT_ERR) || + (pdesc->status & DECRYPT_ERR_MASK)) { + /* check if status is not equal to 0xFF + * the 0xFF check is for backward compatibility + */ + if (pdesc->status != GENERAL_DECRYPT_ERR) { + if (((pdesc->status & (~DECRYPT_ERR_MASK)) & + TKIP_DECRYPT_MIC_ERR) && !((pdesc->status & + (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) { + status->flag |= RX_FLAG_MMIC_ERROR; + } + } + } +} +EXPORT_SYMBOL_GPL(mwl_rx_prepare_status); + +static inline unsigned int lrd_elapsed_jiffies_msecs(unsigned long start) +{ + unsigned long end = jiffies; + + if (end >= start) + return jiffies_to_msecs(end - start); + + return jiffies_to_msecs(end + (ULONG_MAX - start) + 1); +} + +void mwl_handle_rx_event(struct ieee80211_hw *hw, + struct mwl_rx_event_data *rx_evnt) +{ + struct mwl_priv *priv = hw->priv; + + if (rx_evnt->event_id == MWL_RX_EVNT_RADAR_DETECT) { + wiphy_info(hw->wiphy, "radar detected by firmware\n"); + ieee80211_radar_detected(hw); + } + else if (rx_evnt->event_id == MWL_RX_EVENT_LINKLOSS_DETECT) { + wiphy_info(hw->wiphy, "link loss detected by firmware\n"); + } + else if (rx_evnt->event_id == MWL_RX_EVENT_REG) { + wiphy_info(hw->wiphy, "regulatory event %x\n", rx_evnt->event_info); + + cancel_work_sync(&priv->reg.awm); + mod_timer(&priv->reg.timer_awm, jiffies + msecs_to_jiffies(1)); + } +#ifdef CONFIG_PM + else if (rx_evnt->event_id == MWL_RX_EVENT_WOW_LINKLOSS_DETECT || + rx_evnt->event_id == MWL_RX_EVENT_WOW_AP_DETECT || + rx_evnt->event_id == MWL_RX_EVENT_WOW_RX_DETECT) { + /* WOW event */ + priv->wow.results.reason = rx_evnt->event_id; + + /* ToDo: Revisit when FW support returing interface */ + priv->wow.results.mwl_vif = mwl_find_first_sta(priv); + + if (priv->wow.state & WOWLAN_STATE_HS_SENT) { + /* report event after resume notificaiton is sent */ + } + else if (priv->wow.jiffies && lrd_elapsed_jiffies_msecs(priv->wow.jiffies) < WOWLAN_JIFFIES) { + priv->wow.jiffies = 0; + lrd_report_wowlan_wakeup(priv); + } + } +#endif +} +EXPORT_SYMBOL_GPL(mwl_handle_rx_event); + +void mwl_rx_enable_sta_amsdu(struct mwl_priv *priv, + u8 *sta_addr) +{ + struct mwl_sta *sta_info; + struct ieee80211_sta *sta; + + spin_lock_bh(&priv->sta_lock); + list_for_each_entry(sta_info, &priv->sta_list, list) { + sta = container_of((void *)sta_info, struct ieee80211_sta, + drv_priv); + if (ether_addr_equal(sta->addr, sta_addr)) { + sta_info->is_amsdu_allowed = true; + break; + } + } + spin_unlock_bh(&priv->sta_lock); +} +EXPORT_SYMBOL_GPL(mwl_rx_enable_sta_amsdu); + +struct mwl_vif *mwl_find_first_sta(struct mwl_priv *priv) +{ + struct mwl_vif *mwl_vif; + + spin_lock_bh(&priv->vif_lock); + list_for_each_entry(mwl_vif, &priv->vif_list, list) { + if (NL80211_IFTYPE_STATION == mwl_vif->vif->type) { + spin_unlock_bh(&priv->vif_lock); + return mwl_vif; + } + } + + spin_unlock_bh(&priv->vif_lock); + + return NULL; +} + +struct mwl_vif *mwl_rx_find_vif_bss(struct mwl_priv *priv, + u8 *bssid) +{ + struct mwl_vif *mwl_vif; + + spin_lock_bh(&priv->vif_lock); + list_for_each_entry(mwl_vif, &priv->vif_list, list) { + if (ether_addr_equal(bssid, mwl_vif->bssid)) { + spin_unlock_bh(&priv->vif_lock); + return mwl_vif; + } + } + spin_unlock_bh(&priv->vif_lock); + + return NULL; +} +EXPORT_SYMBOL_GPL(mwl_rx_find_vif_bss); + +void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos) +{ + struct mwl_dma_data *tr; + int hdrlen; + + tr = (struct mwl_dma_data *)skb->data; + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + + if (hdrlen != sizeof(tr->wh)) { + if (ieee80211_is_data_qos(tr->wh.frame_control)) { + memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2); + *((__le16 *)(tr->data - 2)) = qos; + } else { + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + } + } + + if (hdrlen != sizeof(*tr)) + skb_pull(skb, sizeof(*tr) - hdrlen); +} +EXPORT_SYMBOL_GPL(mwl_rx_remove_dma_header); + +void mwl_rx_defered_handler(struct work_struct *work) +{ + struct ieee80211_hw *hw; + struct mwl_priv *priv = container_of(work, + struct mwl_priv, rx_defer_work); + struct sk_buff *rx_skb; + + hw = priv->hw; + + priv->is_rx_defer_schedule = false; + + while ((rx_skb = skb_dequeue(&priv->rx_defer_skb_q))) { + struct ieee80211_hdr *wh; + wh = (struct ieee80211_hdr *)rx_skb->data; + + wiphy_err(hw->wiphy, "%s(): mgmt=%d stype=%d\n", + __FUNCTION__, + ieee80211_is_mgmt(wh->frame_control), + (wh->frame_control & IEEE80211_FCTL_STYPE)); + + /* TODO: Add defered processing code here */ + kfree_skb(rx_skb); + } +} + +inline bool mwl_rx_needs_defered_processing(struct sk_buff *rx_skb) +{ + /* TODO: Choose conditions for selecting defered pkts here */ + return 0; +} + +#if 1 +unsigned int dbgRxPrbResp, dbgRxBcn; +#endif + +void mwl_rx_upload_pkt(struct ieee80211_hw *hw, + struct sk_buff *rx_skb) +{ + struct ieee80211_hdr *wh; + struct mwl_priv *priv = hw->priv; + struct sk_buff *skb_save; + + wh = (struct ieee80211_hdr *)rx_skb->data; + + // We've received at least one packet + // Clear the scan count to prevent recovery logic from kicking in + if (atomic_read(&priv->null_scan_count)) { + atomic_set(&priv->null_scan_count, 0); + } + + if (unlikely(ieee80211_is_mgmt(wh->frame_control)) && + mwl_rx_needs_defered_processing(rx_skb) && + ((skb_save = skb_copy(rx_skb, GFP_ATOMIC)) != NULL)){ + skb_queue_tail(&priv->rx_defer_skb_q, skb_save); + + if (!priv->is_rx_defer_schedule) { + priv->is_rx_defer_schedule = true; + queue_work(priv->rx_defer_workq, &priv->rx_defer_work); + } + } + +#if 1 +if (ieee80211_is_mgmt(wh->frame_control)) { + + if (ieee80211_is_probe_resp(wh->frame_control)) { + dbgRxPrbResp++; + } else if (ieee80211_is_beacon(wh->frame_control)) { + dbgRxBcn++; + } +} +#endif + + /* Upload pkts to mac80211 */ + ieee80211_rx(hw, rx_skb); +} +EXPORT_SYMBOL_GPL(mwl_rx_upload_pkt); diff --git a/drivers/net/wireless/laird/lrdmwl/rx.h b/drivers/net/wireless/laird/lrdmwl/rx.h new file mode 100644 index 0000000000000..412acc37c5cee --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/rx.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines receive related functions. */ + +#ifndef _RX_H_ +#define _RX_H_ + +int mwl_rx_init(struct ieee80211_hw *hw); +void mwl_rx_deinit(struct ieee80211_hw *hw); +void mwl_rx_prepare_status(struct mwl_rx_desc *pdesc, + struct ieee80211_rx_status *status); +struct mwl_vif *mwl_rx_find_vif_bss(struct mwl_priv *priv, + u8 *bssid); +void mwl_rx_remove_dma_header(struct sk_buff *skb, __le16 qos); +void mwl_rx_enable_sta_amsdu(struct mwl_priv *priv, + u8 *sta_addr); + +extern void mwl_rx_upload_pkt(struct ieee80211_hw *hw, + struct sk_buff *rx_skb); +extern void mwl_rx_defered_handler(struct work_struct *work); +extern void mwl_handle_rx_event(struct ieee80211_hw *hw, + struct mwl_rx_event_data *rx_evnt); + +#endif /* _RX_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/sdio.c b/drivers/net/wireless/laird/lrdmwl/sdio.c new file mode 100644 index 0000000000000..1e0e8be030137 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/sdio.c @@ -0,0 +1,3219 @@ +/* + * Marvell Wireless LAN device driver: SDIO specific handling + * + * Copyright (C) 2011-2014, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ +#include +#include +#include +#include +#include +#include +#include +#include "sysadpt.h" +#include "dev.h" +#include "main.h" +#include "fwcmd.h" +#include "rx.h" +#include "tx.h" +#include "sdio.h" +#include "hostcmd.h" + +#define MMC_STATE_SUSPENDED (1<<5) /* card is suspended */ +#define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED) + +#define MWL_SDIODRV_VERSION "10.3.0.16-20160105" +#define LRD_SDIO_VERSION LRD_BLD_VERSION "-" MWL_SDIODRV_VERSION +#define LRD_SDIO_DESC "Laird 60 Series Wireless SDIO Network Driver" + +#define INTF_HEADER_LEN 4 + +enum mwl_pm_action { + MWL_PM_PREPARE, + MWL_PM_COMPLETE, + MWL_PM_RESUME, + MWL_PM_RESUME_EARLY, + MWL_PM_RESTORE_EARLY, + MWL_PM_SUSPEND, + MWL_PM_SUSPEND_LATE, + MWL_PM_FREEZE_LATE, + MWL_PM_POWEROFF +}; + +static struct mwl_chip_info mwl_chip_tbl[] = { + [MWL8864] = { + .part_name = "88W8864", + .fw_image = MWL_FW_ROOT"/88W8864_sdio.bin", + .antenna_tx = ANTENNA_TX_4_AUTO, + .antenna_rx = ANTENNA_RX_4_AUTO, + }, + [MWL8897] = { + .part_name = "88W8897", + .fw_image = MWL_FW_ROOT"/88W8897_sdio.bin", + .antenna_tx = ANTENNA_TX_2, + .antenna_rx = ANTENNA_RX_2, + }, + [MWL8997] = { + .part_name = "88W8997", + .fw_image = MWL_FW_ROOT"/88W8997_sdio.bin", + .mfg_image = MWL_FW_ROOT"/88W8997_sdio_mfg.bin", + .antenna_tx = ANTENNA_TX_2, + .antenna_rx = ANTENNA_RX_2, + }, +}; + +static unsigned int reset_pwd_gpio = ARCH_NR_GPIOS; + +static int mwl_write_data_sync(struct mwl_priv *priv, + u8 *buffer, u32 pkt_len, u32 port); +static int mwl_sdio_enable_int(struct mwl_priv *priv, bool enable); + +static int mwl_sdio_event(struct mwl_priv *priv); +static void mwl_sdio_tx_workq(struct work_struct *work); +static void mwl_sdio_rx_recv(unsigned long data); +static int mwl_sdio_read_fw_status(struct mwl_priv *priv, u16 *dat); + +static int mwl_sdio_reset(struct mwl_priv *priv); +static int mwl_sdio_set_gpio(struct mwl_sdio_card *card, int value); +static int mwl_sdio_restart_handler(struct mwl_priv *priv); + +/* Device ID for SD8897 */ +#define SDIO_DEVICE_ID_MARVELL_8897 (0x912d) +#define SDIO_DEVICE_ID_MARVELL_8997 (0x9141) + +/* 88W8997 datasheet requires PMIC_EN/PMU_EN to remain de-asserted for a minimum of 100ms */ +#define SDIO_DEFAULT_POWERDOWN_DELAY_MS 100 + +static void +mwl_sdio_interrupt(struct sdio_func *func); + +/* WLAN IDs */ +static const struct sdio_device_id mwl_sdio_id_tbl[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8997), + .driver_data = MWL8997}, + { }, +}; + +static const struct of_device_id mwl_sdio_of_match_table[] = { + { .compatible = "marvell,sd8997" }, + { } +}; + +/* + * This function reads data from SDIO card register. + */ +inline int mwl_read_reg(struct mwl_sdio_card *card, u32 reg, u8 *data) +{ + int rc = -1; + + *data = sdio_readb(card->func, reg, &rc); + + return rc; +} + +/* + * This function writes data into SDIO card register. + */ +inline int mwl_write_reg(struct mwl_sdio_card *card, u32 reg, u8 data) +{ + int rc = -1; + + sdio_writeb(card->func, data, reg, &rc); + + return rc; +} + + +static void mwl_free_sdio_mpa_buffers(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + + kfree(card->mpa_tx.buf); + card->mpa_tx.buf = NULL; + card->mpa_tx.buf_size = 0; + + kfree(card->mpa_rx.buf); + card->mpa_rx.buf = NULL; + card->mpa_rx.buf_size = 0; +} + + +/* + * This function allocates the MPA Tx and Rx buffers. + */ +static int mwl_alloc_sdio_mpa_buffers(struct mwl_priv *priv, + u32 mpa_tx_buf_size, u32 mpa_rx_buf_size) +{ + struct mwl_sdio_card *card = priv->intf; + u32 rx_buf_size; + + card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL); + if (!card->mpa_tx.buf) + goto error; + + card->mpa_tx.buf_size = mpa_tx_buf_size; + + rx_buf_size = max_t(u32, mpa_rx_buf_size, + (u32)SDIO_MAX_AGGR_BUF_SIZE); + card->mpa_rx.buf = kzalloc(rx_buf_size, GFP_KERNEL); + if (!card->mpa_rx.buf) + goto error; + + card->mpa_rx.buf_size = rx_buf_size; + + card->tx_pkt_unaligned_cnt = 0; + + return 0; + +error: + mwl_free_sdio_mpa_buffers(priv); + return -ENOMEM; +} + +static void *mwl_alloc_dma_align_buf(int rx_len, gfp_t flags) +{ + struct sk_buff *skb; + int buf_len, pad; + + buf_len = rx_len + MWL_RX_HEADROOM + MWL_DMA_ALIGN_SZ; + + skb = __dev_alloc_skb(buf_len, flags); + + if (!skb) + return NULL; + + skb_reserve(skb, MWL_RX_HEADROOM); + + pad = MWL_ALIGN_ADDR(skb->data, MWL_DMA_ALIGN_SZ) - + (long)skb->data; + + skb_reserve(skb, pad); + + return skb; +} + +/* + * This function polls the card status. + */ +static int +mwl_sdio_poll_card_status(struct mwl_priv *priv, u8 bits) +{ + struct mwl_sdio_card *card = priv->intf; + u32 tries; + u8 cs; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + if (mwl_read_reg(card, card->reg->poll_reg, &cs)) + break; + + if ((cs & bits) == bits) + return 0; + + lrdmwl_delay(10); + } + + wiphy_err(priv->hw->wiphy, + "poll card status failed, tries = %d\n", tries); + + return -1; +} + + + +/* + * This function is used to initialize IO ports for the + * chipsets supporting SDIO new mode eg SD8897. + */ +static int mwl_init_sdio_new_mode(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + int rc = 0; + u8 reg; + + card->ioport = MEM_PORT; + + /* enable sdio new mode */ + if (mwl_read_reg(card, card->reg->card_cfg_2_1_reg, ®)) { + rc = -EIO; + goto err_new_mode; + } + + if (mwl_write_reg(card, card->reg->card_cfg_2_1_reg, + reg | CMD53_NEW_MODE)) { + rc = -EIO; + goto err_new_mode; + } + + /* Configure cmd port and enable reading rx length from the register */ + if (mwl_read_reg(card, card->reg->cmd_cfg_0, ®)) { + rc = -EIO; + goto err_new_mode; + } + + if (mwl_write_reg(card, card->reg->cmd_cfg_0, + reg | CMD_PORT_RD_LEN_EN)) { + rc = -EIO; + goto err_new_mode; + } + + /* Enable Dnld/Upld ready auto reset for cmd port after cmd53 is + * completed + */ + if (mwl_read_reg(card, card->reg->cmd_cfg_1, ®)) { + rc = -EIO; + goto err_new_mode; + } + + if (mwl_write_reg(card, card->reg->cmd_cfg_1, + reg | CMD_PORT_AUTO_EN)) { + rc = -EIO; + goto err_new_mode; + } + +err_new_mode: + return rc; +} + + + +/* This function initializes the IO ports. + * + * The following operations are performed - + * - Read the IO ports (0, 1 and 2) + * - Set host interrupt Reset-To-Read to clear + * - Set auto re-enable interrupt + */ +static int mwl_init_sdio_ioport(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + int rc; + u8 reg; + + card->ioport = 0; + + rc = mwl_init_sdio_new_mode(priv); + if (rc) + goto cont; + + /* Read the IO port */ + rc = mwl_read_reg(card, card->reg->io_port_0_reg, ®); + if (rc) + goto err_init_ioport; + + card->ioport |= (reg & 0xff); + + rc = mwl_read_reg(card, card->reg->io_port_1_reg, ®); + if (rc) + goto err_init_ioport; + + card->ioport |= ((reg & 0xff) << 8); + + rc = mwl_read_reg(card, card->reg->io_port_2_reg, ®); + if (rc) + goto err_init_ioport; + + card->ioport |= ((reg & 0xff) << 16); + +cont: + wiphy_info(priv->hw->wiphy, "%s: SDIO FUNC1 IO port: %#x\n", + MWL_DRV_NAME, card->ioport); + + /* Set Host interrupt reset to read to clear */ + rc = mwl_read_reg(card, card->reg->host_int_rsr_reg, ®); + if (rc) + goto err_init_ioport; + + mwl_write_reg(card, card->reg->host_int_rsr_reg, + reg | card->reg->sdio_int_mask); + + /* Dnld/Upld ready set to auto reset */ + rc = mwl_read_reg(card, card->reg->card_misc_cfg_reg, ®); + if (rc) + goto err_init_ioport; + + mwl_write_reg(card, card->reg->card_misc_cfg_reg, + reg | AUTO_RE_ENABLE_INT); + +err_init_ioport: + return rc; +} + +static int mwl_sdio_enable_int(struct mwl_priv *priv, bool enable) +{ + struct mwl_sdio_card *card = priv->intf; + struct sdio_func *func = card->func; + int ret; + + sdio_claim_host(func); + sdio_writeb(func, card->reg->host_int_enable, + card->reg->host_int_mask_reg ^ + (enable ? 0 : card->reg->host_int_mask_reg), + &ret); + sdio_release_host(func); + + wiphy_dbg(priv->hw->wiphy, + "=>%s(): %s host interrupt %s\n", __func__, + enable ? "enable" : "disable", ret ? "failed" : "ok"); + + return ret; +} + +static int mwl_sdio_init_irq(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + struct sdio_func *func = card->func; + int ret; + + wiphy_info(priv->hw->wiphy, + "%s, register IRQ\n", __func__); + + sdio_claim_host(func); + /* Request the SDIO IRQ */ + ret = sdio_claim_irq(func, mwl_sdio_interrupt); + if (ret) + wiphy_err(priv->hw->wiphy, + "claim irq failed: ret=%d\n", ret); + + sdio_release_host(func); + return ret; +} + +static int mwl_sdio_init_post(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + struct sdio_func *func = card->func; + + if (priv->stop_shutdown && (mmc_card_is_removable(func->card->host) || + !gpio_is_valid(card->reset_pwd_gpio))) { + priv->stop_shutdown = false; + + dev_err(&func->dev, + "Power down when network down mode supported only, " + "when card set to non-removable and driver has direct control " + "over PMU Enable GPIO\n"); + } + + if (priv->mfg_mode) { + priv->ds_enable = DS_ENABLE_OFF; + + //Assume ST 60 with one interface + priv->radio_caps.capability = 0; + priv->radio_caps.num_mac = 1; + priv->stop_shutdown = false; + } + + return 0; +} + +static int mwl_sdio_init(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + struct sdio_func *func = card->func; + const struct mwl_sdio_card_reg *reg = card->reg; + int rc; + u8 sdio_ireg; + int num; + + /* not sure this patch is needed or not?? */ + func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; + + priv->host_if = MWL_IF_SDIO; + card->priv = priv; + priv->dev = &func->dev; + sdio_set_drvdata(card->func, priv->hw); + + priv->if_ops.hardware_restart = mwl_sdio_restart_handler; + + sdio_claim_host(func); + + rc = sdio_enable_func(func); + if (rc != 0) { + sdio_release_host(func); + dev_err(&func->dev, "%s: failed to enable sdio function\n", __func__); + return rc; + } + + /* + * Read the host_int_status_reg for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + mwl_read_reg(card, card->reg->host_int_status_reg, &sdio_ireg); + + /* Get SDIO ioport */ + mwl_init_sdio_ioport(priv); + + /* Set block size */ + rc = sdio_set_block_size(card->func, MWL_SDIO_BLOCK_SIZE); + if (rc) { + sdio_release_host(func); + wiphy_err(priv->hw->wiphy, + "cannot set SDIO block size rc 0x%04x\n", rc); + return rc; + } + + sdio_release_host(func); + + /* Initialize SDIO variables in card */ + card->mp_rd_bitmap = 0; + card->mp_wr_bitmap = 0; + card->curr_rd_port = reg->start_rd_port; + card->curr_wr_port = reg->start_wr_port; + + card->mp_data_port_mask = reg->data_port_mask; + + card->mpa_tx.buf_len = 0; + card->mpa_tx.pkt_cnt = 0; + card->mpa_tx.start_port = 0; + + card->mpa_tx.enabled = 1; + card->mpa_tx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + card->mpa_rx.buf_len = 0; + card->mpa_rx.pkt_cnt = 0; + card->mpa_rx.start_port = 0; + + card->mpa_rx.enabled = 1; + card->mpa_rx.pkt_aggr_limit = card->mp_agg_pkt_limit; + + /* Allocate buffers for SDIO MP-A */ + card->mp_regs = kzalloc(reg->max_mp_regs, GFP_KERNEL); + if (!card->mp_regs) { + rc = -ENOMEM; + goto err_return; + } + + /* Allocate skb pointer buffers */ + card->mpa_rx.skb_arr = kzalloc((sizeof(void *)) * + card->mp_agg_pkt_limit, GFP_KERNEL); + if (!card->mpa_rx.skb_arr) { + rc = -ENOMEM; + goto err_return; + } + + card->mpa_rx.len_arr = kzalloc(sizeof(*card->mpa_rx.len_arr) * + card->mp_agg_pkt_limit, GFP_KERNEL); + + if (!card->mpa_rx.len_arr) { + rc = -ENOMEM; + goto err_return; + } + + rc = mwl_alloc_sdio_mpa_buffers(priv, + card->mp_tx_agg_buf_size, + card->mp_rx_agg_buf_size); + + /* Allocate 32k MPA Tx/Rx buffers if 64k memory allocation fails */ + if (rc && (card->mp_tx_agg_buf_size == MWL_MP_AGGR_BUF_SIZE_MAX || + card->mp_rx_agg_buf_size == MWL_MP_AGGR_BUF_SIZE_MAX)) { + /* Disable rx single port aggregation */ + card->host_disable_sdio_rx_aggr = true; + + rc = mwl_alloc_sdio_mpa_buffers + (priv, MWL_MP_AGGR_BUF_SIZE_32K, + MWL_MP_AGGR_BUF_SIZE_32K); + if (rc) { + /* Disable multi port aggregation */ + card->mpa_tx.enabled = 0; + card->mpa_rx.enabled = 0; + } + } + + spin_lock_init(&card->int_lock); + spin_lock_init(&card->rx_proc_lock); + init_waitqueue_head(&card->cmd_wait_q.wait); + skb_queue_head_init(&card->rx_data_q); + + card->cmd_wait_q.status = 0; + card->cmd_resp_recvd = false; + card->cmd_id = 0; + card->int_status = 0; + init_waitqueue_head(&card->wait_deepsleep); + + priv->chip_type = card->chip_type; + + /* This routine is called during restart scenarios, but we keep the + * original pcmd_buf/pcmd_event_buf until driver unload + */ + if (!priv->pcmd_buf) { + priv->pcmd_buf = kzalloc(CMD_BUF_SIZE, GFP_KERNEL); + if (!priv->pcmd_buf) { + wiphy_err(priv->hw->wiphy, + "%s: cannot alloc memory for command buffer\n", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_return; + } + } + else + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + + if (!priv->pcmd_event_buf) { + priv->pcmd_event_buf = kzalloc(CMD_BUF_SIZE, GFP_KERNEL); + if (!priv->pcmd_event_buf) { + wiphy_err(priv->hw->wiphy,"%s: cannot alloc memory for command_event buffer\n", + MWL_DRV_NAME); + rc = -ENOMEM; + goto err_return; + } + } + else + memset(priv->pcmd_event_buf, 0x00, CMD_BUF_SIZE); + + /* Initialize the tasklet first in case there are tx/rx interrupts */ + if (!priv->recovery_in_progress) { + // Do not reinitialize the tasklet; it could be on the CPU queue + // Tasklet has been disabled in mwl_mac80211_stop + tasklet_init(&priv->rx_task, (void *)mwl_sdio_rx_recv, + (unsigned long)priv->hw); + tasklet_disable(&priv->rx_task); + } + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) + skb_queue_head_init(&priv->txq[num]); + +#ifdef CONFIG_PM + priv->wow.capable = + (sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER) != 0; +#endif + + // Re-initialization needed in case of restart + card->is_deepsleep = 0; + + card->dnld_cmd_failure = 0; + card->is_running = true; + + return 0; + +err_return: + mwl_free_sdio_mpa_buffers(priv); + kfree(priv->pcmd_buf); priv->pcmd_buf = NULL; + kfree(priv->pcmd_event_buf); priv->pcmd_event_buf = NULL; + kfree(card->mpa_rx.skb_arr); card->mpa_rx.skb_arr = NULL; + kfree(card->mpa_rx.len_arr); card->mpa_rx.len_arr = NULL; + kfree(card->mp_regs); card->mp_regs = NULL; + + return rc; +} + +/* + * This function sends data to the card. + */ +static int mwl_write_data_to_card(struct mwl_priv *priv, + u8 *payload, u32 pkt_len, u32 port) +{ + struct mwl_sdio_card *card = priv->intf; + int i, ret; + + sdio_claim_host(card->func); + + for (i = 0; i <= MAX_WRITE_IOMEM_RETRY; ++i) { + ret = mwl_write_data_sync(priv, payload, pkt_len, port); + if (!ret || ret == -ENOMEDIUM || ret == -ENETDOWN) + break; + + wiphy_err(priv->hw->wiphy, + "host_to_card, write iomem (%d) failed: %d\n", i, ret); + + if (mwl_write_reg(card, CONFIGURATION_REG, 0x04)) + wiphy_err(priv->hw->wiphy, "write CFG reg failed\n"); + + ret = -1; + } + + sdio_release_host(card->func); + + return ret; +} + +static int mwl_sdio_send_command(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + struct cmd_header *cmd_hdr = (struct cmd_header *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(INTF_HEADER_LEN)]; + u32 buf_block_len; + u32 blk_size; + u16 len; + u32 pkt_len; + u32 port; + int rc; + __le16 *pbuf = (__le16 *)priv->pcmd_buf; + int status; + unsigned long flags; + + /* Wait till the card informs CMD_DNLD_RDY interrupt except + * for get HW spec command */ + if (cmd_hdr->command != cpu_to_le16(HOSTCMD_CMD_GET_HW_SPEC)) { + status = wait_event_timeout(card->cmd_wait_q.wait, + (card->int_status & DN_LD_CMD_PORT_HOST_INT_STATUS), + (12 * HZ)); + if (!card->is_running) + return -ENETDOWN; + + if (status == 0) { + wiphy_err(priv->hw->wiphy, "CMD_DNLD failure\n"); + priv->cmd_timeout = true; + return -1; + } else { + spin_lock_irqsave(&card->int_lock, flags); + card->int_status &= ~DN_LD_CMD_PORT_HOST_INT_STATUS; + spin_unlock_irqrestore(&card->int_lock, flags); + } + } + + len = le16_to_cpu(cmd_hdr->len) + + INTF_CMDHEADER_LEN(INTF_HEADER_LEN)*sizeof(unsigned short); + port = CMD_PORT_SLCT; + blk_size = MWL_SDIO_BLOCK_SIZE; + buf_block_len = (len + blk_size - 1) / blk_size; + pkt_len = buf_block_len * blk_size; + card->cmd_resp_recvd = false; + card->cmd_id = (u16)(le16_to_cpu(cmd_hdr->command) & ~HOSTCMD_RESP_BIT); + + pbuf[0] = cpu_to_le16(pkt_len); + pbuf[1] = cpu_to_le16(MWL_TYPE_CMD); + + rc = mwl_write_data_to_card(priv, (u8 *)&priv->pcmd_buf[0], + pkt_len, (card->ioport + port)); + + if (rc < 0) { + /* If command fails to write, reset the int status so next command can be processed. */ + spin_lock_irqsave(&card->int_lock, flags); + card->int_status |= DN_LD_CMD_PORT_HOST_INT_STATUS; + spin_unlock_irqrestore(&card->int_lock, flags); + + card->dnld_cmd_failure++; + if (card->dnld_cmd_failure > MAX_DNLD_CMD_FAILURES) { + wiphy_err(priv->hw->wiphy, "CMD_DNLD threshold failure\n"); + priv->cmd_timeout = true; + } + } + else { + card->dnld_cmd_failure = 0; + } + + return rc; +} + + +static void mwl_sdio_cleanup(struct mwl_priv *priv) +{ + int num; + struct mwl_sdio_card *card = priv->intf; + + /* Disable Interrupt before tx/rx cleanup */ + sdio_claim_host(card->func); + sdio_release_irq(card->func); + sdio_release_host(card->func); + wiphy_info(priv->hw->wiphy, "%s, unregister IRQ\n", __func__); + + /* Abandon wait on queue */ + card->is_running = false; + card->int_status |= DN_LD_CMD_PORT_HOST_INT_STATUS; + card->cmd_resp_recvd = true; + wake_up_all(&card->cmd_wait_q.wait); + + /* mwl_sdio_cleanup is called both in driver shutdown and recovery scenarios + Only kill tasklet (which just means remove from CPU queue if it exists there) in shutdown scenario + Ensure tasklet is not both disabled and queued to avoid CPU hang */ + if (priv->shutdown) { + /* delay to allow threads to exit */ + mdelay(10); + tasklet_enable(&priv->rx_task); + tasklet_kill(&priv->rx_task); + } + + /* Free Tx bufs */ + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + skb_queue_purge(&priv->txq[num]); + priv->fw_desc_cnt[num] = 0; + } + + /* Free Rx bufs */ + skb_queue_purge(&card->rx_data_q); + mwl_free_sdio_mpa_buffers(priv); + kfree(card->mpa_rx.skb_arr); card->mpa_rx.skb_arr = NULL; + kfree(card->mpa_rx.len_arr); card->mpa_rx.len_arr = NULL; + kfree(card->mp_regs); card->mp_regs = NULL; + + sdio_claim_host(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); +} + +static bool mwl_sdio_check_card_status(struct mwl_priv *priv) +{ + return true; +} + +/* + * This function writes multiple data into SDIO card memory. + * + * This does not work in suspended mode. + */ +static int +mwl_write_data_sync(struct mwl_priv *priv, + u8 *buffer, u32 pkt_len, u32 port) +{ + struct mwl_sdio_card *card = priv->intf; + int ret; + u8 blk_mode = (port & MWL_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? MWL_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (pkt_len / MWL_SDIO_BLOCK_SIZE) : pkt_len; + u32 ioport = (port & MWL_SDIO_IO_PORT_MASK); + + if (card->is_suspended && !priv->recovery_in_progress) { + wiphy_err(priv->hw->wiphy, + "%s: not allowed while suspended\n", __func__); + return -ENETDOWN; + } + + ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size); + + if (ret && ret != -ENOMEDIUM) { + wiphy_err(priv->hw->wiphy," %s : sdio_writesb failed,buffer ptr = 0x%p ioport = 0x%x," + " size = %d, error = %d\n", __func__, buffer, ioport, blk_cnt * blk_size,ret); + } + + return ret; +} + +/* + * This function reads the firmware status. + */ +static int +mwl_sdio_read_fw_status(struct mwl_priv *priv, u16 *dat) +{ + struct mwl_sdio_card *card = priv->intf; + const struct mwl_sdio_card_reg *reg = card->reg; + int rc; + u8 fws0, fws1; + + sdio_claim_host(card->func); + + rc = mwl_read_reg(card, reg->status_reg_0, &fws0); + if (rc) + goto err; + + rc = mwl_read_reg(card, reg->status_reg_1, &fws1); + if (rc) + goto err; + + *dat = (u16) ((fws1 << 8) | fws0); + +err: + sdio_release_host(card->func); + + return rc; +} + + +/* + * This function checks the firmware status in card. + * + * The winner interface is also determined by this function. + */ +static int mwl_check_fw_status(struct mwl_priv *priv, u32 poll_num) +{ + int ret = 0, tries; + u16 firmware_stat = 0; + + wiphy_info(priv->hw->wiphy,"Checking fw status %d\n", poll_num); + + /* Wait for firmware initialization event */ + for (tries = 1; tries <= poll_num; tries++) { + ret = mwl_sdio_read_fw_status(priv, &firmware_stat); + if (ret) + { + if (!(tries % 10) ) { + wiphy_err(priv->hw->wiphy, "fw read failed %d\n", ret); + } + continue; + } + + if (firmware_stat == FIRMWARE_READY_SDIO) { + ret = 0; + wiphy_info(priv->hw->wiphy, + "firmware is ready %d\n", tries); + break; + } + + if (!(tries % 10)) + wiphy_info(priv->hw->wiphy, + "Waiting on fw status %d 0x%x\n", + tries, firmware_stat); + + msleep(100); + ret = -1; + } + + return ret; +} + + + +/* + * This function downloads the firmware to the card. + * + * Firmware is downloaded to the card in blocks. Every block download + * is tested for CRC errors, and retried a number of times before + * returning failure. + */ +static int mwl_sdio_program_firmware(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + const struct mwl_sdio_card_reg *reg = card->reg; + const struct firmware *fw; + u8 *fw_data; + u32 fw_len; + int ret; + u32 offset = 0; + u8 base0, base1; + u8 *fwbuf; + u16 len = 0; + u32 txlen, tx_bytes = 0, tries; + u32 i = 0; + u32 ioport = (card->ioport & MWL_SDIO_IO_PORT_MASK); + u16 firmware_status = 0; + + fw = priv->fw_ucode; + fw_len = fw->size; + fw_data = (u8 *)fw->data; + + if (!fw_len) { + wiphy_err(priv->hw->wiphy, + "Firmware image not found! Terminating download\n"); + return -1; + } + + /* Assume that the allocated buffer is 8-byte aligned */ + fwbuf = kzalloc(MWL_UPLD_SIZE, GFP_KERNEL); + if (!fwbuf) { + wiphy_err(priv->hw->wiphy,"buffer allocation failed\n"); + return -ENOMEM; + } + + wiphy_info(priv->hw->wiphy, + "Downloading FW image (%d bytes)\n", fw_len); + + mwl_sdio_enable_int(priv, false); + + for (i = 0; i < 2; ++i) { + ret = mwl_sdio_read_fw_status(priv, &firmware_status); + + if (ret == 0 && firmware_status == FIRMWARE_READY_SDIO) { + wiphy_err(priv->hw->wiphy, + "Firmware already initialized! Resetting radio...\n"); + + if (priv->if_ops.down_dev) + priv->if_ops.down_dev(priv); + + ret = mwl_sdio_reset(priv); + + if (priv->if_ops.up_dev) + priv->if_ops.up_dev(priv); + + if (ret) { + kfree(fwbuf); + return ret; + } + } + else + break; + } + + if (i >= 2) { + kfree(fwbuf); + return -1; + } + + sdio_claim_host(card->func); + + /* Perform firmware data transfer */ + i = 0; + for(;;) { + /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY + bits */ + ret = mwl_sdio_poll_card_status(priv, CARD_IO_READY | + DN_LD_CARD_RDY); + if (ret) { + wiphy_err(priv->hw->wiphy, + "FW downloading \t" + "poll status timeout @ %d\n", offset); + goto err_dnld; + } + + /* More data? */ + if (offset >= fw_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = mwl_read_reg(card, reg->base_0_reg, + &base0); + if (ret) { + wiphy_err(priv->hw->wiphy, + "dev BASE0 register read failed:\t" + "base0=%#04X(%d). Terminating dnld\n", + base0, base0); + goto err_dnld; + } + ret = mwl_read_reg(card, reg->base_1_reg, + &base1); + if (ret) { + wiphy_err(priv->hw->wiphy, + "dev BASE1 register read failed:\t" + "base1=%#04X(%d). Terminating dnld\n", + base1, base1); + goto err_dnld; + } + len = (u16) (((base1 & 0xff) << 8) | (base0 & 0xff)); + + if (len) + break; + + lrdmwl_delay(10); + } + + if (!len) { + break; + } else if (len > MWL_UPLD_SIZE) { + wiphy_err(priv->hw->wiphy, + "FW dnld failed @ %d, invalid length %d\n", + offset, len); + ret = -1; + goto err_dnld; + } + + txlen = len; + + if (len & BIT(0)) { + if (++i > MAX_WRITE_IOMEM_RETRY) { + wiphy_err(priv->hw->wiphy, + "FW dnld failed @ %d, over max retry\n", + offset); + ret = -1; + goto err_dnld; + } + wiphy_err(priv->hw->wiphy, + "CRC indicated by the helper:\t" + "len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + i = 0; + + /* Set blocksize to transfer - checking for last + block */ + if (fw_len - offset < txlen) + txlen = fw_len - offset; + + tx_bytes = ALIGN(txlen, MWL_SDIO_BLOCK_SIZE); + + /* Copy payload to buffer */ + memcpy(fwbuf, &fw_data[offset], txlen); + } + + ret = sdio_writesb(card->func, ioport, fwbuf, tx_bytes); + if (ret) { + if (ret != -ENOMEDIUM) { + wiphy_err(priv->hw->wiphy, + "FW download, write iomem (%d) failed @ %d\n", + i, offset); + if (mwl_write_reg(card, CONFIGURATION_REG, 0x04)) + wiphy_err(priv->hw->wiphy, "write CFG reg failed\n"); + } + + ret = -1; + goto err_dnld; + } + + offset += txlen; + } + + sdio_release_host(card->func); + + wiphy_info(priv->hw->wiphy, + "FW download over, size %d bytes\n", offset); + + kfree(fwbuf); + + ret = mwl_check_fw_status(priv, MAX_FIRMWARE_POLL_TRIES); + if (ret) { + wiphy_err(priv->hw->wiphy, + "FW status is not ready\n"); + return ret; + } + + mwl_sdio_init_irq(priv); + + /* Enabling interrupt after firmware is ready. + * Otherwise there may be abnormal interrupt DN_LD_HOST_INT_MASK + */ + mwl_sdio_enable_int(priv, true); + + return ret; + + +err_dnld: + sdio_release_host(card->func); + kfree(fwbuf); + return ret; +} + +/* + * This function reads multiple data from SDIO card memory. + */ +static int mwl_read_data_sync(struct mwl_priv *priv, u8 *buffer, + u32 len, u32 port) +{ + struct mwl_sdio_card *card = priv->intf; + int ret; + u8 blk_mode = (port & MWL_SDIO_BYTE_MODE_MASK) ? BYTE_MODE + : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? MWL_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / MWL_SDIO_BLOCK_SIZE) + : len; + u32 ioport = (port & MWL_SDIO_IO_PORT_MASK); + + ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size); + + if (ret) + wiphy_err(priv->hw->wiphy," %s : sdio_readsb buffer ptr = 0x%p ioport: 0x%x" + " for size %d failed with error %d\n", + __func__, buffer, ioport,blk_cnt * blk_size, ret); + return ret; +} + +static char *mwl_sdio_event_strn(u16 event_id) +{ + int max_entries = 0; + int curr_id = 0; + + static const struct { + u16 id; + char *id_string; + } events[] = { + { SDEVENT_RADAR_DETECT, "SDEVENT_RADAR_DETECT" }, + { SDEVENT_CHNL_SWITCH, "SDEVENT_CHNL_SWITCH" }, + { SDEVENT_BA_WATCHDOG, "SDEVENT_BA_WATCHDOG" }, + { SDEVENT_WAKEUP, "SDEVENT_WAKEUP" }, + { SDEVENT_PS_SLEEP, "SDEVENT_PS_SLEEP" }, + { SDEVENT_IBSS_LAST_BCN,"SDEVENT_IBSS_LAST_BCN_TXTSF" }, + }; + + max_entries = ARRAY_SIZE(events); + + for (curr_id = 0; curr_id < max_entries; curr_id++) + if ((event_id & 0x7fff) == events[curr_id].id) + return events[curr_id].id_string; + + return "unknown"; +} + +/*Note: When calling this function, it is expected that the fwcmd_mutex is already held */ +int mwl_sdio_wakeup_card(struct mwl_priv *priv) +{ + int status; + struct mwl_sdio_card *card = priv->intf; + u8 cr; + + sdio_claim_host(card->func); + + if (mwl_read_reg(card, CONFIGURATION_REG, &cr)) + wiphy_err(priv->hw->wiphy, "read CFG reg failed\n"); + + wiphy_dbg(priv->hw->wiphy, "Initiate Card wakeup\n"); + + if (mwl_write_reg(card, CONFIGURATION_REG, (cr | 0x2))) { + sdio_release_host(card->func); + wiphy_err(priv->hw->wiphy, "write CFG reg failed\n"); + return -EIO; + } + + if (mwl_read_reg(card, CONFIGURATION_REG, &cr)) + wiphy_err(priv->hw->wiphy, "read CFG reg failed\n"); + + sdio_release_host(card->func); + + status = wait_event_timeout(card->wait_deepsleep,( + card->is_deepsleep == 0), 12 * HZ); + if (!status) { + wiphy_err(priv->hw->wiphy, "info: Card Wakeup failed\n"); + return -EIO; + } + + mwl_restart_ds_timer(priv, false); + + wiphy_dbg(priv->hw->wiphy, "info: Card Wakeup complete\n"); + return 0; +} + +/*Note: The thread that triggered the wakeup request is holding the fwcmd_mutex */ +void mwl_sdio_wakeup_complete(struct work_struct *work) +{ + struct mwl_sdio_card *card = container_of(work, + struct mwl_sdio_card, event_work); + struct mwl_priv *priv = card->priv; + + sdio_claim_host(card->func); + + if (mwl_write_reg(card, CONFIGURATION_REG, 0)) + wiphy_err(priv->hw->wiphy, "write CFG reg failed\n"); + + sdio_release_host(card->func); + + card->is_deepsleep = 0; + wake_up(&card->wait_deepsleep); +} + +void mwl_sdio_enter_ps_sleep(struct work_struct *work) +{ + struct mwl_sdio_card *card = container_of(work, + struct mwl_sdio_card, cmd_work); + struct mwl_priv *priv = card->priv; + int num,ret; + + if (priv->recovery_in_progress) + return; + + /* Grab fwcmd mutex here so we can be sure nobody else changes the devices sleep state */ + if (!mutex_trylock(&priv->fwcmd_mutex)) + return; + + wiphy_dbg(priv->hw->wiphy,"In ps sleep enter:\n"); + + /* check if tx empty,if not do not enter PS */ + for (num = SYSADPT_TX_WMM_QUEUES - 1; num >= 0; num--) { + if (skb_queue_len(&priv->txq[num]) > 0) { + wiphy_dbg(priv->hw->wiphy, + "Sleep fail due to tx not empty %d\n", + skb_queue_len(&priv->txq[num])); + goto done; + } + } + + /* check if Rx has any packets */ + if (skb_queue_len(&card->rx_data_q) > 0) { + wiphy_dbg(priv->hw->wiphy, "Sleep fail due to rx not empty\n"); + goto done; + } + + ret = mwl_fwcmd_confirm_ps(priv->hw); + +done: + mutex_unlock(&priv->fwcmd_mutex); +} + +static int mwl_sdio_event(struct mwl_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_sdio_card *card = priv->intf; +#if 0 + struct mwl_hostevent *host_event = (struct mwl_hostevent *)( + &priv->pcmd_buf[INTF_CMDHEADER_LEN(INTF_HEADER_LEN)]); +#else + struct mwl_hostevent *host_event = (struct mwl_hostevent *)( + &priv->pcmd_event_buf[0]); +#endif + u16 event_id = le16_to_cpu(host_event->mac_event.event_id); + + wiphy_dbg(hw->wiphy,"=> sd_event: %s\n", mwl_sdio_event_strn(event_id)); + + switch (event_id) { + case SDEVENT_RADAR_DETECT: + ieee80211_radar_detected(hw); + break; + case SDEVENT_CHNL_SWITCH: + ieee80211_queue_work(hw, &priv->chnl_switch_handle); + break; + case SDEVENT_BA_WATCHDOG: + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + break; + case SDEVENT_WAKEUP: + queue_work(card->cmd_workq, &card->event_work); + break; + case SDEVENT_PS_SLEEP: + queue_work(card->cmd_workq, &card->cmd_work); + break; + case SDEVENT_IBSS_LAST_BCN: + if(host_event->BcnPayload.event) + { + priv->LastBeaconTime = get_unaligned_le64(host_event->BcnPayload.bssTsf); + } + break; + default: + wiphy_info(hw->wiphy,"Unknown event, id=%04xh\n", event_id); + } + + return 0; +} + +/* + * This function sends a data buffer to the card. + */ +static int mwl_sdio_card_to_host(struct mwl_priv *priv, u32 *type, + u8 *buffer, u32 npayload, u32 ioport) +{ + int ret; + + if (!buffer) { + wiphy_err(priv->hw->wiphy, + "%s: buffer is NULL\n", __func__); + return -1; + } + + ret = mwl_read_data_sync(priv, buffer, npayload, ioport); + if (type != NULL) + *type = le16_to_cpu(*(__le16 *)(buffer + 2)); + + return ret; +} + + +/* + * This function gets the read port. + * + * If control port bit is set in MP read bitmap, the control port + * is returned, otherwise the current read port is returned and + * the value is increased (provided it does not reach the maximum + * limit, in which case it is reset to 1) + */ +static int mwl_get_rd_port(struct mwl_priv *priv, u8 *port) +{ + struct mwl_sdio_card *card = priv->intf; + const struct mwl_sdio_card_reg *reg = card->reg; + u32 rd_bitmap = card->mp_rd_bitmap; + + if (!(rd_bitmap & reg->data_port_mask)) + return -1; + + if (!(card->mp_rd_bitmap & (1 << card->curr_rd_port))) + return -1; + + /* We are now handling the SDIO data ports */ + card->mp_rd_bitmap &= (u32)(~(1 << card->curr_rd_port)); + *port = card->curr_rd_port; + + /* + * card->curr_rd_port is 0 ~ 31 (= start_rd_port ~ card->max_ports-1) + */ + if (++card->curr_rd_port == card->max_ports) + card->curr_rd_port = reg->start_rd_port; + + return 0; +} + +/* + * This function decode sdio aggreation pkt. + * + * Based on the the data block size and pkt_len, + * skb data will be decoded to few packets. + */ +static void mwl_deaggr_sdio_pkt(struct mwl_priv *priv, + struct sk_buff *skb) +{ + struct mwl_sdio_card *card = priv->intf; + u32 total_pkt_len, pkt_len; + struct sk_buff *skb_deaggr; + u32 pkt_type; + u16 blk_size; + u8 blk_num; + u8 *data; + + data = skb->data; + total_pkt_len = skb->len; + + while (total_pkt_len >= (SDIO_HEADER_OFFSET + INTF_HEADER_LEN)) { + if (total_pkt_len < card->sdio_rx_block_size) + break; + blk_num = *(data + BLOCK_NUMBER_OFFSET); + blk_size = card->sdio_rx_block_size * blk_num; + if (blk_size > total_pkt_len) { + wiphy_err(priv->hw->wiphy, + "%s: error in blk_size,\t" + "blk_num=%d, blk_size=%d, total_pkt_len=%d\n", + __func__, blk_num, blk_size, total_pkt_len); + break; + } + pkt_len = le16_to_cpu(*(__le16 *)(data + SDIO_HEADER_OFFSET)); + pkt_type = le16_to_cpu(*(__le16 *)(data + SDIO_HEADER_OFFSET + + 2)); + if ((pkt_len + SDIO_HEADER_OFFSET) > blk_size) { + wiphy_err(priv->hw->wiphy, + "%s: error in pkt_len,\t" + "pkt_len=%d, blk_size=%d\n", + __func__, pkt_len, blk_size); + break; + } + skb_deaggr = mwl_alloc_dma_align_buf(pkt_len, GFP_KERNEL); + if (!skb_deaggr) + break; + skb_put(skb_deaggr, pkt_len); + memcpy(skb_deaggr->data, data + SDIO_HEADER_OFFSET, pkt_len); + skb_pull(skb_deaggr, INTF_HEADER_LEN); + + mwl_handle_rx_packet(priv, skb_deaggr); + data += blk_size; + total_pkt_len -= blk_size; + } +} + + +/* + * This function decodes a received packet. + * + * Based on the type, the packet is treated as either a data, or + * a command response, or an event, and the correct handler + * function is invoked. + */ +static int mwl_decode_rx_packet(struct mwl_priv *priv, + struct sk_buff *skb, u32 upld_typ) +{ + struct mwl_sdio_card *card = priv->intf; + __le16 *curr_ptr = (__le16 *)skb->data; + u16 pkt_len = le16_to_cpu(*curr_ptr); + struct mwl_rxinfo *rx_info; + + switch (upld_typ) { + case MWL_TYPE_AGGR_DATA: + rx_info = MWL_SKB_RXCB(skb); + rx_info->buf_type = MWL_TYPE_AGGR_DATA; + break; + case MWL_TYPE_DATA: + skb_trim(skb, pkt_len); + /* Remove the header (len:2 + type:2) */ + skb_pull(skb, INTF_HEADER_LEN); + + break; + case MWL_TYPE_MGMT: + skb_trim(skb, pkt_len); + /* Remove the header (len:2 + type:2) */ + skb_pull(skb, INTF_HEADER_LEN); + rx_info = MWL_SKB_RXCB(skb); + rx_info->buf_type = MWL_TYPE_MGMT; + break; + case MWL_TYPE_BEACON: + skb_trim(skb, pkt_len); + /* Remove the header (len:2 + type:2) */ + skb_pull(skb, INTF_HEADER_LEN); + rx_info = MWL_SKB_RXCB(skb); + rx_info->buf_type = MWL_TYPE_BEACON; + break; + default: + wiphy_err(priv->hw->wiphy, + "unknown upload type %#x\n", upld_typ); + goto error; + break; + } + skb_queue_tail(&card->rx_data_q, skb); + atomic_inc(&card->rx_pending); + card->data_received = true; + +error: + return 0; +} + + +/* + * This function transfers received packets from card to driver, performing + * aggregation if required. + * + * For data received on control port, or if aggregation is disabled, the + * received buffers are uploaded as separate packets. However, if aggregation + * is enabled and required, the buffers are copied onto an aggregation buffer, + * provided there is space left, processed and finally uploaded. + */ +static int mwl_sdio_card_to_host_mp_aggr(struct mwl_priv *priv, + u16 rx_len, u8 port) +{ + struct mwl_sdio_card *card = priv->intf; + s32 f_do_rx_aggr = 0; + s32 f_do_rx_cur = 0; + s32 f_aggr_cur = 0; + s32 f_post_aggr_cur = 0; + struct sk_buff *skb_deaggr; + struct sk_buff *skb = NULL; + u32 pkt_len, pkt_type, mport, pind; + u8 *curr_ptr; + int i; + u32 port_count; + + if (!card->mpa_rx.enabled) { + wiphy_err(priv->hw->wiphy, + "info: %s: rx aggregation disabled\n", + __func__); + + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (card->mp_rd_bitmap & + card->reg->data_port_mask) { + /* Some more data RX pending */ + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) { + f_aggr_cur = 1; + } else { + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_aggr = 1; + f_post_aggr_cur = 1; + } + } else { + /* Rx aggr not in progress */ + f_aggr_cur = 1; + } + + } else { + /* No more data RX pending */ + if (MP_RX_AGGR_IN_PROGRESS(card)) { + f_do_rx_aggr = 1; + if (MP_RX_AGGR_BUF_HAS_ROOM(card, rx_len)) + f_aggr_cur = 1; + else + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_cur = 1; + } else { + f_do_rx_cur = 1; + } + } + + if (f_aggr_cur != 0) { + /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + + if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) || + mp_rx_aggr_port_limit_reached(card)) { + /* wiphy_err(priv->hw->wiphy, + "info: %s: aggregated packet\t" + "limit reached\n", __func__);*/ + /* No more pkts allowed in Aggr buf, rx it */ + f_do_rx_aggr = 1; + } + } + + if (f_do_rx_aggr) { + /* do aggr RX now */ + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_rx.ports & BIT(i)) + port_count++; + /* Reading data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (card->ioport | SDIO_MPA_ADDR_BASE | + (port_count << 8)) + card->mpa_rx.start_port; + + if (mwl_read_data_sync(priv, card->mpa_rx.buf, + card->mpa_rx.buf_len, mport)) + goto error; + + + /* + * Get the data from bus (in mpa_rx.buf) + * => put to the buffer array packet by packet + */ + curr_ptr = card->mpa_rx.buf; + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + u32 *len_arr = card->mpa_rx.len_arr; + + /* get curr PKT len & type */ + pkt_len = le16_to_cpu(*(__le16 *) &curr_ptr[0]); + pkt_type = le16_to_cpu(*(__le16 *) &curr_ptr[2]); + + /* copy pkt to deaggr buf */ + skb_deaggr = mwl_alloc_dma_align_buf(len_arr[pind], + GFP_KERNEL); + if (!skb_deaggr) { + wiphy_err(priv->hw->wiphy, + "skb allocation failure\t"\ + "drop pkt len=%d type=%d\n", + pkt_len, pkt_type); + curr_ptr += len_arr[pind]; + continue; + } + + skb_put(skb_deaggr, len_arr[pind]); + + if (((pkt_type == MWL_TYPE_DATA) || + (pkt_type == MWL_TYPE_AGGR_DATA && + card->sdio_rx_aggr_enable) || + (pkt_type == MWL_TYPE_MGMT) || + (pkt_type == MWL_TYPE_BEACON) + ) && + (pkt_len <= len_arr[pind])) { + + memcpy(skb_deaggr->data, curr_ptr, pkt_len); + + skb_trim(skb_deaggr, pkt_len); + /* Process de-aggr packet */ + mwl_decode_rx_packet(priv, skb_deaggr, + pkt_type); + } else { + wiphy_err(priv->hw->wiphy, + "drop wrong aggr pkt:\t" + "sdio_single_port_rx_aggr=%d\t" + "type=%d len=%d max_len=%d\n", + card->sdio_rx_aggr_enable, + pkt_type, pkt_len, len_arr[pind]); + dev_kfree_skb_any(skb_deaggr); + } + curr_ptr += len_arr[pind]; + } + MP_RX_AGGR_BUF_RESET(card); + } + +rx_curr_single: + if (f_do_rx_cur) { + skb = mwl_alloc_dma_align_buf(rx_len, GFP_KERNEL); + if (!skb) { + wiphy_err(priv->hw->wiphy, + "single skb allocated fail,\t" + "drop pkt port=%d len=%d\n", port, rx_len); + if (mwl_sdio_card_to_host(priv, &pkt_type, + card->mpa_rx.buf, rx_len, card->ioport + port)) + goto error; + + return 0; + } + skb_put(skb, rx_len); + + if (mwl_sdio_card_to_host(priv, &pkt_type, skb->data, skb->len, + card->ioport + port)) + goto error; + mwl_decode_rx_packet(priv, skb, pkt_type); + } + if (f_post_aggr_cur) { + wiphy_err(priv->hw->wiphy, + "info: current packet aggregation\n"); + /* Curr pkt can be aggregated */ + mp_rx_aggr_setup(card, rx_len, port); + } + + return 0; + +error: + if (MP_RX_AGGR_IN_PROGRESS(card)) + MP_RX_AGGR_BUF_RESET(card); + + if (f_do_rx_cur && skb) + /* Single transfer pending. Free curr buff also */ + dev_kfree_skb_any(skb); + + return -1; +} + +/* +static char *mwl_pktstrn(char* pkt) +{ + static char msg[80]; + char pkthd = pkt[0]; + char pkt_type = (pkthd&0x0c)>>2; + char pkt_subtype = (pkthd&0xf0) >> 4; + + memset(msg, 0, sizeof(msg)); + if (pkt_type == 0) { //mgmt pkt + switch (pkt_subtype) { + case 0x0: + strcpy(msg, "assoc_req"); + break; + case 0x01: + strcpy(msg, "assoc_resp"); + break; + case 0x2: + strcpy(msg, "reassoc_req"); + break; + case 0x3: + strcpy(msg, "reassoc_resp"); + break; + case 0x4: + strcpy(msg, "probe_req"); + break; + case 0x5: + strcpy(msg, "prob_resp"); + break; + case 0x8: + strcpy(msg, "beacon"); + break; + case 0xa: + strcpy(msg, "disassoc"); + break; + case 0xb: + strcpy(msg, "auth"); + break; + case 0xc: + strcpy(msg, "deauth"); + break; + case 0xd: + strcpy(msg, "action"); + break; + default: + break; + } + } + + if (pkt_type == 1) { //ctrl pkt + switch (pkt_subtype) { + case 0xb: + strcpy(msg, "rts"); + break; + case 0xc: + strcpy(msg, "cts"); + break; + case 0xd: + strcpy(msg, "ack"); + break; + case 0x8: + strcpy(msg, "BAR"); + break; + case 0x9: + strcpy(msg, "BA"); + break; + default: + break; + } + } + + if (pkt_type == 2) { //data pkt + char* pllc; + switch (pkt_subtype) { + case 0x8: + strcpy(msg, "QOS"); + break; + case 0x00: + strcpy(msg, "data"); + break; + case 0x4: + strcpy(msg, "null_data"); + return msg; + default: + strcpy(msg, "data"); + return msg; + } + if (pkt_subtype == 0x00) { + pllc = &pkt[24]; + } else if (pkt_subtype == 0x8) { + pllc = &pkt[26]; + } + + if ((pllc[0] == '\xaa') && (pllc[1]='\xaa') && (pllc[2]=='\x03')) { + if ((pllc[6]=='\x08')&&(pllc[7]=='\x06')) { + strcat(msg, " - ARP"); + } + if ((pllc[6]=='\x08') && (pllc[7] == '\x00')) { + char *pip = &pllc[8]; + strcat(msg, " - IP"); + if (pip[9] == '\x01') { + char *picmp = &pip[20]; + strcat(msg, " - ICMP"); + if (picmp[0] == '\x00') { + strcat(msg, " - echo_rply"); + } + if (picmp[0] == '\x08') { + strcat(msg, " - echo_req"); + } + } + if (pip[9] == '\x11') { + //char *pudp = &pip[20]; + strcat(msg, " - UDP"); + } + } + } + } + + return msg; +} +*/ + + +/* + Packet format (sdio interface): + [len:2][type:2][mwl_rx_desc:44][mwl_dma_data:32][payload wo 802.11 header] +*/ +void mwl_handle_rx_packet(struct mwl_priv *priv, struct sk_buff *skb) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_rx_desc *pdesc; + struct mwl_dma_data *dma; + struct sk_buff *prx_skb = skb; + struct ieee80211_rx_status status; + struct mwl_vif *mwl_vif = NULL; + struct ieee80211_hdr *wh; + struct mwl_rx_event_data *rx_evnt; + + pdesc = (struct mwl_rx_desc *)prx_skb->data; + + /* => todo: + // Save the rate info back to card + //card->rate_info = pdesc->rate; + //=> rateinfo-- + */ + if (pdesc->payldType == RX_PAYLOAD_TYPE_EVENT_INFO) { + skb_pull(prx_skb, sizeof(struct mwl_rx_desc)); + rx_evnt = (struct mwl_rx_event_data *)prx_skb->data; + mwl_handle_rx_event(hw, rx_evnt); + dev_kfree_skb_any(prx_skb); + return; + } + + if ((pdesc->channel != hw->conf.chandef.chan->hw_value) && + !(priv->roc.tmr_running && priv->roc.in_progress && + (pdesc->channel == priv->roc.chan))) { + dev_kfree_skb_any(prx_skb); +// wiphy_debug(priv->hw->wiphy, +// "<= %s(), not accepted channel (%d, %d)\n", __func__, +// pdesc->channel, hw->conf.chandef.chan->hw_value); + return; + } + + mwl_rx_prepare_status(pdesc, &status); + priv->noise = pdesc->noise_floor; + + skb_pull(prx_skb, sizeof(struct mwl_rx_desc)); + dma = (struct mwl_dma_data *)prx_skb->data; + wh = &dma->wh; + + if (ieee80211_has_protected(wh->frame_control)) { + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + if (ieee80211_has_tods(wh->frame_control)) + { + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1); + if (!mwl_vif && ieee80211_has_a4(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv,wh->addr2); + } + else if (ieee80211_has_fromds(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2); + else + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr3); + + if (mwl_vif && mwl_vif->is_hw_crypto_enabled) { + /* When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwlwifi Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + memset((void *)&dma->data, 0, 4); + skb_put(prx_skb, 4); + /* IV is stripped in this case + * Indicate that, so mac80211 doesn't attempt to decrypt the + * packet and fail prior to handling the MMIC error indication + */ + status.flag |= RX_FLAG_IV_STRIPPED; + } + + if (!ieee80211_is_auth(wh->frame_control)) + /* For WPA2 frames, AES header/MIC are + ** present to enable mac80211 to check + ** for replay attacks + */ + status.flag |= RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + /* + Remove the DMA header (dma->fwlen) + */ + mwl_rx_remove_dma_header(prx_skb, pdesc->qos_ctrl); + + /* Update the pointer of wifi header, + which may be different after mwl_rx_remove_dma_header() + */ + wh = (struct ieee80211_hdr *)prx_skb->data; + if (ieee80211_is_mgmt(wh->frame_control)) { + struct ieee80211_mgmt *mgmt; + __le16 capab; + + mgmt = (struct ieee80211_mgmt *)prx_skb->data; + + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_resp.action_code == + WLAN_ACTION_ADDBA_RESP)) { + capab = mgmt->u.action.u.addba_resp.capab; + if (le16_to_cpu(capab) & 1) + mwl_rx_enable_sta_amsdu(priv, mgmt->sa); + } + } + +#if 0 //def CONFIG_MAC80211_MESH + if (ieee80211_is_data_qos(wh->frame_control) && + ieee80211_has_a4(wh->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(wh); + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + if (mwl_rx_process_mesh_amsdu(priv, prx_skb, + &status)) + return; + } +#endif + + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); + + /* Packet to indicate => Will indicate AMPDU/AMSDU packets */ + mwl_rx_upload_pkt(hw, prx_skb); + + return; +} + + +static void mwl_sdio_rx_recv(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_sdio_card *card = priv->intf; + struct mwl_rxinfo *rx_info; + struct sk_buff *prx_skb = NULL; + int work_done = 0; + + mwl_restart_ds_timer(priv, false); + + while (work_done < priv->recv_limit) { + + // shutdown flag set in other thread + // Ensure compiler doesn't play games with the read... + if (READ_ONCE(priv->shutdown)) + break; + + prx_skb = skb_dequeue(&card->rx_data_q); + if (prx_skb == NULL) { + break; + } + + rx_info = MWL_SKB_RXCB(prx_skb); + + + if (rx_info->buf_type == MWL_TYPE_AGGR_DATA) + mwl_deaggr_sdio_pkt(priv, prx_skb); + else + mwl_handle_rx_packet(priv, prx_skb); + work_done++; + } + + return; +} + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +static int mwl_write_data_complete(struct mwl_priv *priv, + struct sk_buff *skb) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)priv->hw; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_tx_info *info; + struct sk_buff_head *amsdu_pkts; + struct mwl_dma_data *dma_data; + struct ieee80211_hdr *wh; + u8 *data = skb->data; + u32 rate; + struct mwl_tx_desc *tx_wcb; + + if (skb == NULL) + return 0; + + tx_wcb = (struct mwl_tx_desc *) &data[INTF_HEADER_LEN]; + dma_data = (struct mwl_dma_data *) &data[tx_wcb->pkt_ptr]; + + wh = &dma_data->wh; + info = IEEE80211_SKB_CB(skb); + + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + + if (ieee80211_is_data(wh->frame_control) || + ieee80211_is_data_qos(wh->frame_control)) { + rate = TX_COMP_RATE_FOR_DATA; + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + mwl_tx_ack_amsdu_pkts(hw, rate, amsdu_pkts); + dev_kfree_skb_any(skb); + skb = NULL; + } else + mwl_tx_prepare_info(hw, rate, info); + } else + mwl_tx_prepare_info(hw, 0, info); + + if (skb != NULL) { + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= IEEE80211_TX_STAT_ACK; + + if (ieee80211_is_data(wh->frame_control) || + ieee80211_is_data_qos(wh->frame_control)) { +// wiphy_err(hw->wiphy, "fr_data_skb=%p\n", skb); + } + + ieee80211_tx_status(hw, skb); + } + + return 0; +} + +static void mwl_sdio_flush_amsdu(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_amsdu_frag *amsdu_frag; + + struct mwl_sta *sta_info; + int i; + + list_for_each_entry(sta_info, &priv->sta_list, list) { + for (i = 0; i < SYSADPT_TX_WMM_QUEUES; i++) { + amsdu_frag = &sta_info->amsdu_ctrl.frag[i]; + if (amsdu_frag->num) { + spin_unlock_bh(&sta_info->amsdu_lock); + mwl_tx_skb(priv, i, + amsdu_frag->skb); + spin_lock_bh(&sta_info->amsdu_lock); + amsdu_frag->num = 0; + amsdu_frag->cur_pos = NULL; + } + } + } +} + +static void mwl_sdio_flush_amsdu_no_lock(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct mwl_amsdu_frag *amsdu_frag; + + struct mwl_sta *sta_info; + int i; + + list_for_each_entry(sta_info, &priv->sta_list, list) { + if (sta_info == NULL) { + return; + } + for (i = 0; i < SYSADPT_TX_WMM_QUEUES; i++) { + amsdu_frag = &sta_info->amsdu_ctrl.frag[i]; + if (amsdu_frag->num) { + mwl_tx_skb(priv, i, + amsdu_frag->skb); + amsdu_frag->num = 0; + amsdu_frag->cur_pos = NULL; + } + } + } +} + +static void mwl_sdio_tx_workq(struct work_struct *work) +{ + struct mwl_sdio_card *card = container_of(work, + struct mwl_sdio_card, tx_work); + struct mwl_priv *priv = card->priv; + struct ieee80211_hw *hw = (struct ieee80211_hw *)priv->hw; + + /* We wakeup here instead of mwl_mac80211_tx since this call runs in atomic context + and tx needs sdio write call which is blocking call */ + mutex_lock(&priv->fwcmd_mutex); + if (priv->if_ops.is_deepsleep(priv)) { + priv->if_ops.wakeup_card(priv); + } + mutex_unlock(&priv->fwcmd_mutex); + + + mwl_tx_skbs((unsigned long)hw); + mwl_sdio_flush_amsdu_no_lock((unsigned long)hw); +} + + +/* + * This function aggregates transmission buffers in driver and downloads + * the aggregated packet to card. + * + * The individual packets are aggregated by copying into an aggregation + * buffer and then downloaded to the card. Previous unsent packets in the + * aggregation buffer are pre-copied first before new packets are added. + * Aggregation is done till there is space left in the aggregation buffer, + * or till new packets are available. + * + * The function will only download the packet to the card when aggregation + * stops, otherwise it will just aggregate the packet in aggregation buffer + * and return. + */ +static int mwl_host_to_card_mp_aggr(struct mwl_priv *priv, + u8 *payload, u32 pkt_len, u32 port, + u32 desc_num) +{ + struct mwl_sdio_card *card = priv->intf; + int ret = 0; + struct sk_buff *next_skb; + s32 f_send_aggr_buf = 0; + s32 f_send_cur_buf = 0; + s32 f_precopy_cur_buf = 0; + s32 f_postcopy_cur_buf = 0; + u32 mport; + u32 port_count, next_pkt_len; + int i; + +// wiphy_err(priv->hw->wiphy, "%s() called\n", __FUNCTION__); + next_skb = skb_peek(&priv->txq[desc_num]); + if (next_skb != NULL) { + next_pkt_len = next_skb->len + + sizeof(struct mwl_tx_desc); + } + else + next_pkt_len = 0; + + if (next_pkt_len) { + /* More pkt in TX queue */ + if (MP_TX_AGGR_IN_PROGRESS(card)) { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + + if (((card->mp_wr_bitmap & + (1 << card->curr_wr_port)) == 0) || + !MP_TX_AGGR_BUF_HAS_ROOM(card, + pkt_len + next_pkt_len)) { + f_send_aggr_buf = 1; + } + } else { + /* No room in Aggr buf, send it */ + f_send_aggr_buf = 1; + + if ((card->mp_wr_bitmap & + (1 << card->curr_wr_port)) == 0) + f_send_cur_buf = 1; + else + f_postcopy_cur_buf = 1; + } + } else { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) && + (card->mp_wr_bitmap & (1 << card->curr_wr_port))) { + f_precopy_cur_buf = 1; + } else { + f_send_cur_buf = 1; + } + } + } else { + /* Last pkt in TX queue */ + if (MP_TX_AGGR_IN_PROGRESS(card)) { + /* some packs in Aggr buf already */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + } else { + /* No room in Aggr buf, send it */ + f_send_cur_buf = 1; + } + } else { + f_send_cur_buf = 1; + } + } + + if (f_precopy_cur_buf) { + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) || + mp_tx_aggr_port_limit_reached(card)) + /* No more pkts allowed in Aggr buf, send it */ + f_send_aggr_buf = 1; + } + + if (f_send_aggr_buf) { + for (i = 0, port_count = 0; i < card->max_ports; i++) + if (card->mpa_tx.ports & BIT(i)) + port_count++; + + /* Writing data from "start_port + 0" to "start_port + + * port_count -1", so decrease the count by 1 + */ + port_count--; + mport = (card->ioport | SDIO_MPA_ADDR_BASE | (port_count << 8)) + + card->mpa_tx.start_port; + ret = mwl_write_data_to_card(priv, card->mpa_tx.buf, + card->mpa_tx.buf_len, mport); + + MP_TX_AGGR_BUF_RESET(card); + } + + if (f_send_cur_buf != 0) { + ret = mwl_write_data_to_card(priv, payload, pkt_len, + card->ioport + port); + } + + if (f_postcopy_cur_buf != 0) + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + return ret; +} + +/* + * This function gets the write port for data. + * + * The current write port is returned if available and the value is + * increased (provided it does not reach the maximum limit, in which + * case it is reset to 1) + */ +static int mwl_get_wr_port_data(struct mwl_priv *priv, u32 *port) +{ + struct mwl_sdio_card *card = priv->intf; + const struct mwl_sdio_card_reg *reg = card->reg; + u32 wr_bitmap = card->mp_wr_bitmap; + + if (!(wr_bitmap & card->mp_data_port_mask)) { + card->data_sent = true; + return -EBUSY; + } + + if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) { + card->mp_wr_bitmap &= (u32) (~(1 << card->curr_wr_port)); + *port = card->curr_wr_port; + if (++card->curr_wr_port == card->mp_end_port) + card->curr_wr_port = reg->start_wr_port; + } else { + card->data_sent = true; + return -EBUSY; + } + + return 0; +} + + +static bool mwl_sdio_is_tx_available(struct mwl_priv *priv, int desc_num) +{ + struct mwl_sdio_card *card = priv->intf; + u32 wr_bitmap = card->mp_wr_bitmap; + + if ((wr_bitmap & card->mp_data_port_mask) == 0) + return false; + + if ((card->mp_wr_bitmap & (1 << card->curr_wr_port)) == 0) + return false; + + return true; +} + +/* + * Adds TxPD to AMSDU header. + * + * Each AMSDU packet will contain one TxPD at the beginning, + * followed by multiple AMSDU subframes. + */ +static int +mwl_process_txdesc(struct mwl_priv *priv, struct sk_buff *skb, struct mwl_dma_data **dma_data) +{ + struct mwl_tx_desc *tx_desc; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_tx_info *tx_info; + u8 *ptr; + int align_pad; + int size_needed; + struct mwl_sdio_card *card = priv->intf; + + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&IEEE80211_SKB_CB(skb)->status; + + align_pad = ((void *)skb->data - (sizeof(struct mwl_tx_desc) + INTF_HEADER_LEN)- + NULL) & (MWL_DMA_ALIGN_SZ - 1); + + if (align_pad) + { + card->tx_pkt_unaligned_cnt++; + } + + size_needed = sizeof(struct mwl_tx_desc) + INTF_HEADER_LEN + align_pad; + + /* existing pointers into skb buffer may not be valid after this call */ + if (skb_cow_head(skb, size_needed)) { + WARN_ON(skb_headroom(skb) < (size_needed)); + return -ENOMEM; + } + + ptr = (u8 *)skb->data; + + *dma_data = (struct mwl_dma_data *) skb->data; + + skb_push(skb, sizeof(struct mwl_tx_desc)+align_pad); + tx_desc = (struct mwl_tx_desc *) skb->data; + memset(tx_desc, 0, sizeof(struct mwl_tx_desc)); + + skb_push(skb, INTF_HEADER_LEN); + tx_desc->tx_priority = tx_ctrl->tx_priority; + tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl); + tx_desc->pkt_len = cpu_to_le16(skb->len); + + if (tx_info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_DONT_ENCRYPT); + } + + if (tx_info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_NO_CCK_RATE); + } + + tx_desc->packet_info = 0; + tx_desc->data_rate = 0; + tx_desc->type = tx_ctrl->type; + tx_desc->xmit_control = tx_ctrl->xmit_control; + tx_desc->sap_pkt_info = 0; + tx_desc->pkt_ptr = cpu_to_le32(ptr - (u8 *)skb->data); + tx_desc->status = 0; + + return 0; +} + +/* + +*/ +static int mwl_sdio_host_to_card(struct mwl_priv *priv, + int desc_num, struct sk_buff *tx_skb) +{ + struct mwl_sdio_card *card = priv->intf; + int ret; + u32 buf_block_len; + u32 blk_size; + u32 port; + u8 *payload; + u32 pkt_len; + struct mwl_dma_data *dma_data; + struct ieee80211_hdr *wh; + + if (priv->recovery_in_progress) { + dev_kfree_skb_any(tx_skb); + ret = -ENETDOWN; + goto done; + } + + /* get port number. */ + ret = mwl_get_wr_port_data(priv, &port); + if (ret) { +// wiphy_err(priv->hw->wiphy, "%s: no wr_port available\n", __func__); + dev_kfree_skb_any(tx_skb); + goto done; + } + + +// wiphy_err(priv->hw->wiphy, "curr wr_port = %d\n", port); + + /* hard code rate_info here, will get this information from FW later. */ + card->rate_info = 0x0F4F8762; /* VHT, SGI-80M, MCS7, 3SS.*/ + + /* Push INTF_HEADER_LEN & mwl_tx_desc + * + * Existing pointers into skb buffer are not valid after this call + */ + ret = mwl_process_txdesc(priv, tx_skb, &dma_data); + if (ret) { + wiphy_err(priv->hw->wiphy, "%s: Failed to send packet! ret = %d\n", __func__, ret); + dev_kfree_skb_any(tx_skb); + goto done; + } + + wh = &dma_data->wh; + + payload = (u8 *)tx_skb->data; + pkt_len = tx_skb->len; + + /* Transfer data to card */ + blk_size = MWL_SDIO_BLOCK_SIZE; + buf_block_len = (pkt_len + blk_size - 1) / blk_size; + *(__le16 *)&payload[0] = cpu_to_le16((u16)pkt_len); + if (ieee80211_is_data(wh->frame_control)) + *(__le16 *)&payload[2] = cpu_to_le16(MWL_TYPE_DATA); + else + *(__le16 *)&payload[2] = cpu_to_le16(MWL_TYPE_MGMT); + + pkt_len = buf_block_len * blk_size; + ret = mwl_host_to_card_mp_aggr(priv, payload, pkt_len, + port, desc_num); + + if (ret != 0) { + card->curr_wr_port = port; + card->mp_wr_bitmap |= (u32)(1<curr_wr_port); + } + mwl_write_data_complete(priv, tx_skb); + +done: + return ret; +} + +/* + * SDIO interrupt handler. + * + * This function reads the interrupt status from firmware and handles + * the interrupt in current thread (ksdioirqd) right away. +* + * The following interrupts are checked and handled by this function - + * - Data sent + * - Command sent + * - Packets received + * + * Since the firmware does not generate download ready interrupt if the + * port updated is command port only, command sent interrupt checking + * should be done manually, and for every SDIO interrupt. + * + * In case of Rx packets received, the packets are uploaded from card to + * host and processed accordingly. + */ +static void +mwl_sdio_interrupt(struct sdio_func *func) +{ + struct mwl_priv *priv; + struct ieee80211_hw *hw; + struct mwl_sdio_card *card; + u8 sdio_ireg; + u32 rx_blocks; + u16 rx_len; + unsigned long flags; + u32 bitmap; + u8 cr; + + hw = sdio_get_drvdata(func); + + if (!hw || !hw->priv) + return; + + priv = hw->priv; + card = priv->intf; + + if (mwl_read_data_sync(priv, card->mp_regs, card->reg->max_mp_regs, REG_PORT)) { + wiphy_err(priv->hw->wiphy, "read mp_regs failed\n"); + return; + } + + /* + * DN_LD_HOST_INT_STATUS and/or UP_LD_HOST_INT_STATUS + * For SDIO new mode CMD port interrupts + * DN_LD_CMD_PORT_HOST_INT_STATUS and/or + * UP_LD_CMD_PORT_HOST_INT_STATUS + * Clear the interrupt status register + */ + spin_lock_irqsave(&card->int_lock, flags); + card->int_status |= card->mp_regs[card->reg->host_int_status_reg]; + sdio_ireg = card->int_status; + spin_unlock_irqrestore(&card->int_lock, flags); + + if (!sdio_ireg) + return; + + /* Following interrupt is only for SDIO new mode */ + if (sdio_ireg & DN_LD_CMD_PORT_HOST_INT_STATUS) { + card->cmd_wait_q.status = 0; + wake_up(&card->cmd_wait_q.wait); + } + + /* Command Response / Event is back */ + if (sdio_ireg & UP_LD_CMD_PORT_HOST_INT_STATUS) { + struct cmd_header *cmd_hdr_resp = (struct cmd_header *) + &priv->pcmd_event_buf[INTF_CMDHEADER_LEN(INTF_HEADER_LEN)]; + __le16 *pRspBuf = (__le16 *)priv->pcmd_event_buf; + + spin_lock_irqsave(&card->int_lock, flags); + card->int_status &= ~UP_LD_CMD_PORT_HOST_INT_STATUS; + spin_unlock_irqrestore(&card->int_lock, flags); + + /* read the len of control packet */ + rx_len = card->mp_regs[card->reg->cmd_rd_len_1] << 8; + rx_len |= (u16)card->mp_regs[card->reg->cmd_rd_len_0]; + rx_blocks = DIV_ROUND_UP(rx_len, MWL_SDIO_BLOCK_SIZE); + if ((rx_blocks * MWL_SDIO_BLOCK_SIZE) > CMD_BUF_SIZE) + return; + + rx_len = (u16) (rx_blocks * MWL_SDIO_BLOCK_SIZE); + + if (mwl_sdio_card_to_host(priv, NULL, (u8 *)priv->pcmd_event_buf, + rx_len, card->ioport | CMD_PORT_SLCT)) { + wiphy_err(hw->wiphy, + "%s: failed to card_to_host", __func__); + goto term_cmd; + } + + /* + * If command has been sent & cmd_code = 0x8xxx => It's cmd_resp + * Otherwise, it's event (new added) + */ + if ((!card->cmd_resp_recvd) && + (le16_to_cpu(cmd_hdr_resp->command) == (card->cmd_id | HOSTCMD_RESP_BIT)) && + (pRspBuf[1] == cpu_to_le16(MWL_TYPE_CMD))) { + spin_lock_irqsave(&card->int_lock, flags); + card->cmd_id = 0; + memcpy(priv->pcmd_buf,priv->pcmd_event_buf,rx_len); + card->cmd_wait_q.status = 0; + card->cmd_resp_recvd = true; + spin_unlock_irqrestore(&card->int_lock, flags); + + wake_up(&card->cmd_wait_q.wait); + + } + else if (pRspBuf[1] == cpu_to_le16(MWL_TYPE_EVENT)) { + mwl_sdio_event(priv); + } + else { + wiphy_err(hw->wiphy, + "%s: Unexpected cmd/resp! Type 0x%x, cmd 0x%x\n", + __func__, le16_to_cpu(pRspBuf[1]), le16_to_cpu(cmd_hdr_resp->command)); + } + } + + /* Tx-Done interrupt */ + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { + bitmap = (u32) card->mp_regs[card->reg->wr_bitmap_l]; + bitmap |= ((u32) card->mp_regs[card->reg->wr_bitmap_u]) << 8; + bitmap |= ((u32) card->mp_regs[card->reg->wr_bitmap_1l]) << 16; + bitmap |= ((u32) card->mp_regs[card->reg->wr_bitmap_1u]) << 24; + + spin_lock_irqsave(&card->int_lock, flags); + card->int_status &= ~DN_LD_HOST_INT_STATUS; + spin_unlock_irqrestore(&card->int_lock, flags); + + card->mp_wr_bitmap = bitmap; + + if (card->data_sent && + (card->mp_wr_bitmap & card->mp_data_port_mask)) { +#if 0 + wiphy_err(hw->wiphy, + "error: <--- Tx DONE Interrupt bmp=0x%x --->\n", card->mp_wr_bitmap); +#endif + card->data_sent = false; + } + + queue_work(card->tx_workq, &card->tx_work); + } + + /* Rx process */ + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + bitmap = (u32) card->mp_regs[card->reg->rd_bitmap_l]; + bitmap |= ((u32) card->mp_regs[card->reg->rd_bitmap_u]) << 8; + bitmap |= ((u32) card->mp_regs[card->reg->rd_bitmap_1l]) << 16; + bitmap |= ((u32) card->mp_regs[card->reg->rd_bitmap_1u]) << 24; + card->mp_rd_bitmap = bitmap; + + spin_lock_irqsave(&card->int_lock, flags); + card->int_status &= ~UP_LD_HOST_INT_STATUS; + spin_unlock_irqrestore(&card->int_lock, flags); + + while (true) { + u8 port; + u32 len_reg_l, len_reg_u; + + if (mwl_get_rd_port(priv, &port)) + break; + + len_reg_l = card->reg->rd_len_p0_l + (port << 1); + len_reg_u = card->reg->rd_len_p0_u + (port << 1); + rx_len = ((u16) card->mp_regs[len_reg_u]) << 8; + rx_len |= (u16) card->mp_regs[len_reg_l]; + + rx_blocks = + (rx_len + MWL_SDIO_BLOCK_SIZE - + 1) / MWL_SDIO_BLOCK_SIZE; + + if (card->mpa_rx.enabled && + ((rx_blocks * MWL_SDIO_BLOCK_SIZE) > + card->mpa_rx.buf_size)) { + wiphy_err(hw->wiphy, + "invalid rx_len=%d\n", + rx_len); + return; + } + + rx_len = (u16) (rx_blocks * MWL_SDIO_BLOCK_SIZE); + if (mwl_sdio_card_to_host_mp_aggr(priv, rx_len, + port)) { + wiphy_err(hw->wiphy, + "card_to_host_mpa failed: int status=%#x\n", + sdio_ireg); + goto term_cmd; + } + } + + /* Indicate the received packets (card->rx_data_q)to MAC80211 */ + if (!priv->shutdown) { + tasklet_schedule(&priv->rx_task); + } + } + return; + +term_cmd: + /* terminate cmd */ + if (mwl_read_reg(card, CONFIGURATION_REG, &cr)) + wiphy_err(hw->wiphy, "read CFG reg failed\n"); + else + wiphy_err(hw->wiphy, + "info: CFG reg val = %d\n", cr); + + if (mwl_write_reg(card, CONFIGURATION_REG, (cr | 0x04))) + wiphy_err(hw->wiphy, + "write CFG reg failed\n"); + else + wiphy_err(hw->wiphy, "info: write success\n"); + + if (mwl_read_reg(card, CONFIGURATION_REG, &cr)) + wiphy_err(hw->wiphy, + "read CFG reg failed\n"); + else + wiphy_err(hw->wiphy, + "info: CFG reg val =%x\n", cr); +} + +/* Check command response back or not */ +static int mwl_sdio_cmd_resp_wait_completed(struct mwl_priv *priv, + unsigned short cmd) +{ + struct mwl_sdio_card *card = priv->intf; + int status; + + /* Wait for completion */ + status = wait_event_timeout(card->cmd_wait_q.wait, + (card->cmd_resp_recvd == true), + (12 * HZ)); + + if (!card->is_running) + return -ENETDOWN; + + if (status == 0) { + status = -ETIMEDOUT; + wiphy_err(priv->hw->wiphy, "timeout, cmd_wait_q terminated: %d\n", + status); + card->cmd_wait_q.status = status; + return status; + } + + status = card->cmd_wait_q.status; + card->cmd_wait_q.status = 0; + + /* status is command response value */ + return status; +} + +/* + * This function enables the host interrupt. + * + * The host interrupt enable mask is written to the card + * host interrupt mask register. + */ +static int mwl_sdio_register_dev(struct mwl_priv *priv) +{ + int rc = 0; + + return rc; +} + +/* + * This function unregisters the SDIO device. + * + * The SDIO IRQ is released, the function is disabled and driver + * data is set to null. + */ +static void +mwl_sdio_unregister_dev(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + + mwl_sdio_cleanup(priv); + + cancel_work_sync(&card->tx_work); + destroy_workqueue(card->tx_workq); + + cancel_work_sync(&card->cmd_work); + cancel_work_sync(&card->event_work); + + destroy_workqueue(card->cmd_workq); + + /* Free pcmd_buf/pcmd_event_buf */ + kfree(priv->pcmd_buf); priv->pcmd_buf = NULL; + kfree(priv->pcmd_event_buf); priv->pcmd_event_buf = NULL; +} + +/*Note: When calling this function, it is expected that the fwcmd_mutex is already held */ +static void mwl_sdio_enter_deepsleep(struct mwl_priv * priv) +{ + struct mwl_sdio_card *card = priv->intf; + card->is_deepsleep = 1; +} + +static int mwl_sdio_is_deepsleep(struct mwl_priv * priv) +{ + + struct mwl_sdio_card *card = priv->intf; + return card->is_deepsleep; +} + +static void mwl_sdio_up_dev(struct mwl_priv *priv) +{ + wiphy_info(priv->hw->wiphy, "%s: Bringing up adapter...\n", MWL_DRV_NAME); + + mwl_sdio_init(priv); +} + +static void mwl_sdio_down_dev(struct mwl_priv *priv) +{ + wiphy_info(priv->hw->wiphy, "%s: Taking down adapter...\n", MWL_DRV_NAME); + + mwl_sdio_cleanup(priv); +} + +static int mwl_sdio_mmc_hw_reset(struct sdio_func *func) +{ + int rc = mmc_hw_reset(func->card); + if (rc == 0) + mmc_card_clr_suspended(func->card); + + return rc; +} + +static int mwl_sdio_up_pwr(struct mwl_priv *priv) +{ + struct mwl_sdio_card * card = (struct mwl_sdio_card *)priv->intf; + int rc; + + if (!priv->mac_init_complete) + return 0; + + rc = mwl_sdio_set_gpio(card, 1); + if (rc) + return rc; + + sdio_claim_host(card->func); + rc = mwl_sdio_mmc_hw_reset(card->func); + sdio_release_host(card->func); + + return rc; +} + +static void mwl_sdio_down_pwr(struct mwl_priv *priv) +{ + struct mwl_sdio_card * card = (struct mwl_sdio_card *)priv->intf; + + mwl_sdio_set_gpio(card, 0); +} + +static int mwl_sdio_restart_handler(struct mwl_priv *priv) +{ + int ret, i; + + wiphy_debug(priv->hw->wiphy, "%s: Restarting adapter...\n", MWL_DRV_NAME); + + mwl_shutdown_sw(priv, false); + + for (i = 0; i < 2; ++i) { + ret = mwl_sdio_reset(priv); + if (ret) + continue; + + if (priv->stop_shutdown) { + ieee80211_restart_hw(priv->hw); + ret = 0; + break; + } + + ret = mwl_reinit_sw(priv, false); + if (!ret) + break; + + wiphy_err(priv->hw->wiphy, + "%s: Re-initialization failed with error %d\n", + MWL_DRV_NAME, ret); + } + + return ret; +} + +#ifdef CONFIG_DEBUG_FS +static int mwl_sdio_dbg_info (struct mwl_priv *priv, char *p, int size, int len) +{ + struct mwl_sdio_card *card = priv->intf; + +#ifdef CONFIG_PM + len += scnprintf(p + len, size - len, "WOW Capable: %s\n", priv->wow.capable?"yes":"no"); +#endif + + len += scnprintf(p + len, size - len, "Link down power off: %s\n", priv->stop_shutdown ? "enable":"disable"); + + if (gpio_is_valid(card->reset_pwd_gpio)) { + len += scnprintf(p + len, size - len, "PMU_EN gpio: %d\n", card->reset_pwd_gpio); + } + + return len; +} +#endif + +MODULE_DEVICE_TABLE(sdio, mwl_sdio_id_tbl); +static struct mwl_if_ops sdio_ops = { + .inttf_head_len = INTF_HEADER_LEN, + .init_if = mwl_sdio_init, + .init_if_post = mwl_sdio_init_post, + .cleanup_if = mwl_sdio_cleanup, + .check_card_status = mwl_sdio_check_card_status, + .prog_fw = mwl_sdio_program_firmware, + .register_dev = mwl_sdio_register_dev, +// .unregister_dev = mwl_sdio_unregister_dev, + .send_cmd = mwl_sdio_send_command, + .cmd_resp_wait_completed = mwl_sdio_cmd_resp_wait_completed, + .host_to_card = mwl_sdio_host_to_card, + .is_tx_available = mwl_sdio_is_tx_available, + .flush_amsdu = mwl_sdio_flush_amsdu, + .enter_deepsleep = mwl_sdio_enter_deepsleep, + .wakeup_card = mwl_sdio_wakeup_card, + .is_deepsleep = mwl_sdio_is_deepsleep, + .up_dev = mwl_sdio_up_dev, + .down_dev = mwl_sdio_down_dev, + .up_pwr = mwl_sdio_up_pwr, + .down_pwr = mwl_sdio_down_pwr, +#ifdef CONFIG_DEBUG_FS + .dbg_info = mwl_sdio_dbg_info, +#endif +}; + +static int mwl_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + static bool printed_version; + struct mwl_sdio_card *card; + struct device_node *np, *of_node = NULL; + int gpio, rc = 0; + enum of_gpio_flags flags; + + if (id->driver_data >= MWLUNKNOWN) + return -ENODEV; + + if (!printed_version) { + dev_info(&func->dev, "<<%s version %s>>", + LRD_DESC, LRD_DRV_VERSION); + printed_version = true; + } + + /* device tree node parsing and platform specific configuration */ + if (dev_of_node(&func->dev)) { + if (of_match_node(mwl_sdio_of_match_table, dev_of_node(&func->dev))) + of_node = dev_of_node(&func->dev); + } + + if (!of_node && dev_of_node(&func->card->dev)) { + for_each_child_of_node(dev_of_node(&func->card->dev), np) { + if (of_match_node(mwl_sdio_of_match_table, np)) { + of_node = np; + break; + } + } + } + + if (!of_node && dev_of_node(func->card->host->parent)) { + for_each_child_of_node(dev_of_node(func->card->host->parent), np) { + if (of_match_node(mwl_sdio_of_match_table, np)) { + of_node = np; + break; + } + } + } + + card = kzalloc(sizeof(struct mwl_sdio_card), GFP_KERNEL); + if (!card) { + dev_err(&func->dev, ": allocate mwl_sdio_card structure failed"); + return -ENOMEM; + } + + card->func = func; + card->dev_id = id; + + if ((id->driver_data == MWL8897) || (id->driver_data == MWL8997)){ + if (id->driver_data == MWL8897) { + card->reg = &mwl_reg_sd8897; + card->chip_type = MWL8897; + } else { + card->reg = &mwl_reg_sd8997; + card->chip_type = MWL8997; + } + + card->max_ports = 32; + card->mp_agg_pkt_limit = 16; + card->tx_buf_size = MWL_TX_DATA_BUF_SIZE_4K; + card->mp_tx_agg_buf_size = MWL_MP_AGGR_BUF_SIZE_MAX; + card->mp_rx_agg_buf_size = MWL_MP_AGGR_BUF_SIZE_MAX; + card->mp_end_port = 0x0020; + } + + card->tx_workq = alloc_workqueue("lrdwifi-tx_workq", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1); + card->cmd_workq = alloc_workqueue("lrdwifi-cmd_workq", + WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1); + + if (!card->tx_workq || !card->cmd_workq) { + rc = -ENOMEM; + goto err_return; + } + + INIT_WORK(&card->tx_work, mwl_sdio_tx_workq); + INIT_WORK(&card->cmd_work, mwl_sdio_enter_ps_sleep); + INIT_WORK(&card->event_work, mwl_sdio_wakeup_complete); + + sdio_ops.ptx_work = &card->tx_work; + sdio_ops.ptx_workq = card->tx_workq; + + memcpy(&sdio_ops.mwl_chip_tbl, &mwl_chip_tbl[card->chip_type], + sizeof(struct mwl_chip_info)); + + card->reset_pwd_gpio = reset_pwd_gpio; + + if (of_node) { + gpio = of_get_named_gpio_flags(of_node, "pmu-en-gpios", 0, &flags); + + if (!gpio_is_valid(gpio)) + gpio = of_get_named_gpio_flags(of_node, "reset-gpios", 0, &flags); + + if (gpio_is_valid(gpio)) { + rc = devm_gpio_request_one(&func->dev, gpio, + flags | GPIOF_OUT_INIT_HIGH, "wifi_pmu_en"); + + if (!rc) + card->reset_pwd_gpio = gpio; + } + } + + if (gpio_is_valid(card->reset_pwd_gpio)) + dev_info(&func->dev, "PMU_EN GPIO %d configured\n", card->reset_pwd_gpio); + else + dev_info(&func->dev, "PMU_EN GPIO not configured\n"); + + rc = mwl_add_card(card, &sdio_ops, of_node); + +err_return: + + if (rc != 0) { + if (rc != -EPROBE_DEFER) + dev_err(&func->dev, "Failed to add_card %d\n", rc); + + if (card->cmd_workq) + destroy_workqueue(card->cmd_workq); + if (card->tx_workq) + destroy_workqueue(card->tx_workq); + kfree(card); + } + + return rc; +} + +static void mwl_sdio_remove(struct sdio_func *func) +{ + struct mwl_priv *priv; + struct ieee80211_hw *hw; + + dev_info(&func->dev, "Card removal initiated!\n"); + hw = sdio_get_drvdata(func); + if (!hw || !hw->priv) { + dev_err(&func->dev, "Data structures invalid, exiting..."); + return; + } + + priv = hw->priv; + + mwl_wl_deinit(priv); + + mwl_sdio_unregister_dev(priv); + + mwl_sdio_reset(priv); + + mwl_ieee80211_free_hw(priv); + kfree(priv->intf); + + dev_info(&func->dev, "Card removal complete!\n"); +} + +#ifdef CONFIG_PM +static int mwl_sdio_pm_worker(struct device *dev, int action) +{ + struct sdio_func *func; + struct ieee80211_hw *hw; + struct mwl_priv *priv; + struct mwl_sdio_card *card; + + func = dev_to_sdio_func(dev); + if (!func) + return 0; + + hw = sdio_get_drvdata(func); + if (!hw || !hw->priv) + return 0; + + priv = (struct mwl_priv*)hw->priv; + card = (struct mwl_sdio_card *)priv->intf; + if (!card) + return 0; + + // Since we do not own reset gpio, we need to restore it direction + // on resume from hibernate manually + if (action == MWL_PM_RESTORE_EARLY) { + if (gpio_is_valid(card->reset_pwd_gpio)) + gpio_direction_output(card->reset_pwd_gpio, 0); + } + + // When we are in stop shutdown mode ignore PM callbacks + // mac80211 start/stop will control power state + if (priv->stop_shutdown && !(priv->wow.state & WOWLAN_STATE_ENABLED)) + return 0; + + switch (action) { + case MWL_PM_PREPARE: + if (mmc_card_is_removable(func->card->host)) { + card->caps_fixups |= MMC_CAP_NONREMOVABLE; + func->card->host->caps |= MMC_CAP_NONREMOVABLE; + } + break; + + case MWL_PM_COMPLETE: + if (card->caps_fixups) + func->card->host->caps &= ~(card->caps_fixups); + break; + + case MWL_PM_RESUME_EARLY: + if (!(sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER)) { + mwl_sdio_set_gpio(card, 1); + card->expect_recovery = true; + } else if (priv->wow.state & WOWLAN_STATE_ENABLED) + lrd_disable_wowlan(priv); + break; + + case MWL_PM_RESTORE_EARLY: + if (sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER) + mwl_shutdown_sw(priv, true); + + mwl_sdio_set_gpio(card, 1); + card->expect_recovery = true; + break; + + case MWL_PM_RESUME: + if (card->expect_recovery) { + if (sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER) { + sdio_claim_host(func); + mwl_sdio_mmc_hw_reset(card->func); + sdio_release_host(func); + } + + card->is_suspended = false; + mwl_reinit_sw(priv, true); + + card->expect_recovery = false; + } + else { + card->is_suspended = false; + mwl_restart_ds_timer(priv, false); + } + + break; + + case MWL_PM_SUSPEND: + if (!(sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER)) + mwl_shutdown_sw(priv, true); + else { + sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + mwl_delete_ds_timer(priv); + } + card->is_suspended = true; + break; + + case MWL_PM_SUSPEND_LATE: + if (!(sdio_get_host_pm_caps(func) & MMC_PM_KEEP_POWER)) + mwl_sdio_set_gpio(card, 0); + else if (priv->wow.state & WOWLAN_STATE_ENABLED) + lrd_enable_wowlan(priv); + break; + + case MWL_PM_POWEROFF: + mwl_sdio_set_gpio(card, 0); + break; + } + + return 0; +} + +static int mwl_sdio_prepare(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_PREPARE); +} + +static void mwl_sdio_complete(struct device *dev) +{ + mwl_sdio_pm_worker(dev, MWL_PM_COMPLETE); +} + +static int mwl_sdio_resume(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_RESUME); +} + +static int mwl_sdio_suspend(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_SUSPEND); +} + +static int mwl_sdio_suspend_late(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_SUSPEND_LATE); +} + +static int mwl_sdio_resume_early(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_RESUME_EARLY); +} + +static int mwl_sdio_restore_early(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_RESTORE_EARLY); +} + +static int mwl_sdio_poweroff(struct device *dev) +{ + return mwl_sdio_pm_worker(dev, MWL_PM_POWEROFF); +} + +static const struct dev_pm_ops mwl_sdio_pm_ops = { + .prepare = mwl_sdio_prepare, + .complete = mwl_sdio_complete, + .resume = mwl_sdio_resume, + .suspend = mwl_sdio_suspend, + .resume_early = mwl_sdio_resume_early, + .thaw_early = mwl_sdio_resume_early, + .restore_early = mwl_sdio_restore_early, + .suspend_late = mwl_sdio_suspend_late, + .freeze_late = mwl_sdio_suspend_late, + .poweroff_late = mwl_sdio_poweroff, +}; +#endif + +static struct sdio_driver mwl_sdio_driver = { + .name = MWL_DRV_NAME, + .id_table = mwl_sdio_id_tbl, + .probe = mwl_sdio_probe, + .remove = mwl_sdio_remove, + .drv = { + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &mwl_sdio_pm_ops, +#endif + } +}; + +#ifdef CONFIG_GPIOLIB +module_param(reset_pwd_gpio, uint, 0644); +MODULE_PARM_DESC(reset_pwd_gpio, "WIFI CHIP_PWD reset pin GPIO (deprecated)"); +#endif + +static int mwl_sdio_init_gpio_legacy(void) +{ + int ret; + + if (!gpio_is_valid(reset_pwd_gpio)) + return 0; + + ret = gpio_request_one(reset_pwd_gpio, GPIOF_OUT_INIT_HIGH, "WIFI_RESET"); + + /* Only return failure code if GPIO is configured but + * request fails + */ + if (ret) + pr_err("lrdmwl: Unable to obtain WIFI power gpio. %d\n", ret); + else + gpio_export(reset_pwd_gpio, false); + + return ret; +} + +static int mwl_sdio_set_gpio(struct mwl_sdio_card *card, int value) +{ + if (!gpio_is_valid(card->reset_pwd_gpio)) + return -ENOSYS; + + gpio_set_value(card->reset_pwd_gpio, value); + + if (value) + lrdmwl_delay(1500); + + return 0; +} + +static int mwl_sdio_reset(struct mwl_priv *priv) +{ + struct mwl_sdio_card *card = priv->intf; + struct sdio_func *func = card->func; + int rc; + + sdio_claim_host(func); + + if (!mwl_sdio_set_gpio(card, 0)) { + msleep(SDIO_DEFAULT_POWERDOWN_DELAY_MS); + mwl_sdio_set_gpio(card, 1); + } + + rc = mwl_sdio_mmc_hw_reset(card->func); + + sdio_release_host(func); + + return rc; +} + +static int mwl_sdio_release_gpio_legacy(void) +{ + if (gpio_is_valid(reset_pwd_gpio)) { + /* Be sure we release GPIO and leave the chip in reset + * when we unload + */ + gpio_set_value(reset_pwd_gpio, 0); + gpio_free(reset_pwd_gpio); + } + + return 0; +} + +static int __init mwl_sdio_driver_init(void) +{ + int ret; + + ret = mwl_sdio_init_gpio_legacy(); + if (ret) + return ret; + + ret = sdio_register_driver(&mwl_sdio_driver); + if (ret) + mwl_sdio_release_gpio_legacy(); + + return ret; +} + +static void __exit mwl_sdio_driver_exit(void) +{ + sdio_unregister_driver(&mwl_sdio_driver); + + mwl_sdio_release_gpio_legacy(); +} + +module_init(mwl_sdio_driver_init); +module_exit(mwl_sdio_driver_exit); + +MODULE_DESCRIPTION(LRD_SDIO_DESC); +MODULE_VERSION(LRD_SDIO_VERSION); +MODULE_AUTHOR(LRD_AUTHOR); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/laird/lrdmwl/sdio.h b/drivers/net/wireless/laird/lrdmwl/sdio.h new file mode 100644 index 0000000000000..6fa05ac4fc6fe --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/sdio.h @@ -0,0 +1,565 @@ +/* + * Marvell Wireless LAN device driver: SDIO specific definitions + * + * Copyright (C) 2011-2014, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWLWIFI_SDIO_H +#define _MWLWIFI_SDIO_H +#include +#include +#include +#include +#include + + +#define MWL_MAX_FUNC2_REG_NUM 13 +#define MWL_TX_DATA_BUF_SIZE_4K 4096 + +/* + type of the sdio data path +*/ +enum sdio_pkt_type { + MWL_TYPE_DATA = 0, + MWL_TYPE_CMD, + MWL_TYPE_TX_DONE, /* not used, it's been embedded in SDIO_MAC_EVT */ + MWL_TYPE_EVENT, + MWL_TYPE_DUMMY_PKT, + MWL_TYPE_HIGH_PRI_DATA_PKT, + MWL_TYPE_LOOPBACK_DATA_PKT, + MWL_TYPE_AGGR_DATA = 0x0a, + MWL_TYPE_MGMT = 0x0b, + MWL_TYPE_BEACON = 0x0c, +}; + +#define MWL_DMA_ALIGN_SZ 4 +#define MWL_RX_HEADROOM 64 +#define MAX_TXPD_SZ 32 +#define INTF_HDR_ALIGN 4 + +#define MWL_MIN_DATA_HEADER_LEN (MWL_DMA_ALIGN_SZ + INTF_HDR_ALIGN + \ + MAX_TXPD_SZ) + +/* Address alignment */ +#define MWL_ALIGN_ADDR(p, a) (((long)(p) + (a) - 1) & ~((a) - 1)) + + +#define REG_PORT 0 +#define CTRL_PORT 0 +#define MEM_PORT 0x10000 + +#define CMD53_NEW_MODE (0x1U << 0) +#define CMD_PORT_RD_LEN_EN (0x1U << 2) +#define CMD_PORT_AUTO_EN (0x1U << 0) + +/* Misc. Config Register : Auto Re-enable interrupts */ +#define AUTO_RE_ENABLE_INT (0x1U << 4) + +#define CMD_PORT_UPLD_INT_MASK (0x1U<<6) +#define CMD_PORT_DNLD_INT_MASK (0x1U<<7) + + +/* Host Control Registers : Upload host interrupt mask */ +#define UP_LD_HOST_INT_MASK (0x1U) +/* Host Control Registers : Download host interrupt mask */ +#define DN_LD_HOST_INT_MASK (0x2U) + + +/* Host Control Registers : Upload host interrupt status */ +#define UP_LD_HOST_INT_STATUS (0x1U) +/* Host Control Registers : Download host interrupt status */ +#define DN_LD_HOST_INT_STATUS (0x2U) +/* Host Control Registers : Command Port Upload host interrupt status */ +#define UP_LD_CMD_PORT_HOST_INT_STATUS (0x40U) +/* Host Control Registers : Command Port Download host interrupt status */ +#define DN_LD_CMD_PORT_HOST_INT_STATUS (0x80U) + +/* Card Control Registers : Card I/O ready */ +#define CARD_IO_READY (0x1U << 3) +/* Card Control Registers : Download card ready */ +#define DN_LD_CARD_RDY (0x1U << 0) + +#define CMD_PORT_SLCT 0x8000 + +/* Host Control Registers : Configuration */ +#define CONFIGURATION_REG 0x00 + + +#define MWL_MP_AGGR_BUF_SIZE_MAX (65280) +#define MWL_MP_AGGR_BUF_SIZE_32K (32768) +#define SDIO_MAX_AGGR_BUF_SIZE (256 * 256) +#define MWL_SDIO_BLOCK_SIZE 256 + +#define BLOCK_MODE 1 +#define BYTE_MODE 0 + +#define MWL_UPLD_SIZE (2312) +#define MAX_POLL_TRIES 100 +#define MAX_WRITE_IOMEM_RETRY 2 +#define MAX_FIRMWARE_POLL_TRIES 100 +#define FIRMWARE_READY_SDIO 0xfedc +#define MWL_SDIO_BYTE_MODE_MASK 0x80000000 +#define MWL_SDIO_IO_PORT_MASK 0xfffff +#define SDIO_MPA_ADDR_BASE 0x1000 +#define BLOCK_NUMBER_OFFSET 15 +#define SDIO_HEADER_OFFSET 28 +#define MAX_DNLD_CMD_FAILURES 5 + + +/* SDIO Tx aggregation in progress ? */ +#define MP_TX_AGGR_IN_PROGRESS(a) (a->mpa_tx.enabled && a->mpa_tx.pkt_cnt > 0) + + +/* SDIO Tx aggregation buffer room for next packet ? */ +#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) (a->mpa_tx.enabled && \ + ((a->mpa_tx.buf_len + len) <= a->mpa_tx.buf_size)) + +/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */ +#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) \ +if (a->mpa_tx.enabled) { \ + memmove(&a->mpa_tx.buf[a->mpa_tx.buf_len], \ + payload, pkt_len); \ + a->mpa_tx.buf_len += pkt_len; \ + if (!a->mpa_tx.pkt_cnt) \ + a->mpa_tx.start_port = port; \ + if (a->mpa_tx.start_port <= port) \ + a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt)); \ + else \ + a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt+1+ \ + (a->max_ports - \ + a->mp_end_port))); \ + a->mpa_tx.pkt_cnt++; \ +} + +/* SDIO Tx aggregation limit ? */ +#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) \ + (a->mpa_tx.pkt_cnt == a->mpa_tx.pkt_aggr_limit) + +/* Reset SDIO Tx aggregation buffer parameters */ +#define MP_TX_AGGR_BUF_RESET(a) do { \ + a->mpa_tx.pkt_cnt = 0; \ + a->mpa_tx.buf_len = 0; \ + a->mpa_tx.ports = 0; \ + a->mpa_tx.start_port = 0; \ +} while (0) + + +/* SDIO Rx aggregation limit ? */ +#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) \ + (a->mpa_rx.pkt_cnt == a->mpa_rx.pkt_aggr_limit) + +/* SDIO Rx aggregation in progress ? */ +#define MP_RX_AGGR_IN_PROGRESS(a) (a->mpa_rx.pkt_cnt > 0) + +/* SDIO Rx aggregation buffer room for next packet ? */ +#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) \ + ((a->mpa_rx.buf_len+rx_len) <= a->mpa_rx.buf_size) + +/* Reset SDIO Rx aggregation buffer parameters */ +#define MP_RX_AGGR_BUF_RESET(a) do { \ + a->mpa_rx.pkt_cnt = 0; \ + a->mpa_rx.buf_len = 0; \ + a->mpa_rx.ports = 0; \ + a->mpa_rx.start_port = 0; \ +} while (0) + +struct mwl_rxinfo { + struct sk_buff *parent; + u8 bss_num; + u8 bss_type; + u8 use_count; + u8 buf_type; +}; + +struct mwl_txinfo { + u32 status_code; + u8 flags; + u8 bss_num; + u8 bss_type; + u8 aggr_num; + u32 pkt_len; + u8 ack_frame_id; + u64 cookie; +}; + +struct mwl_cb { + union { + struct mwl_rxinfo rx_info; + struct mwl_txinfo tx_info; + }; +}; + +struct mwl_sdio_card_reg { + u8 start_rd_port; + u8 start_wr_port; + u8 base_0_reg; + u8 base_1_reg; + u8 poll_reg; + u8 host_int_enable; + u8 host_int_rsr_reg; + u8 host_int_status_reg; + u8 host_int_mask_reg; + u8 status_reg_0; + u8 status_reg_1; + u8 sdio_int_mask; + u32 data_port_mask; + u8 io_port_0_reg; + u8 io_port_1_reg; + u8 io_port_2_reg; + u16 max_mp_regs; + u8 rd_bitmap_l; + u8 rd_bitmap_u; + u8 rd_bitmap_1l; + u8 rd_bitmap_1u; + u8 wr_bitmap_l; + u8 wr_bitmap_u; + u8 wr_bitmap_1l; + u8 wr_bitmap_1u; + u8 rd_len_p0_l; + u8 rd_len_p0_u; + u8 card_misc_cfg_reg; + u8 card_cfg_2_1_reg; + u8 cmd_rd_len_0; + u8 cmd_rd_len_1; + u8 cmd_rd_len_2; + u8 cmd_rd_len_3; + u8 cmd_cfg_0; + u8 cmd_cfg_1; + u8 cmd_cfg_2; + u8 cmd_cfg_3; + u8 fw_dump_host_ready; + u8 fw_dump_ctrl; + u8 fw_dump_start; + u8 fw_dump_end; + u8 func1_dump_reg_start; + u8 func1_dump_reg_end; + u8 func1_scratch_reg; + u8 func1_spec_reg_num; + u8 func1_spec_reg_table[MWL_MAX_FUNC2_REG_NUM]; +}; + +static const struct mwl_sdio_card_reg mwl_reg_sd8897 = { + .start_rd_port = 0, + .start_wr_port = 0, + .base_0_reg = 0x60, + .base_1_reg = 0x61, + .poll_reg = 0x50, + .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK | + CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK, + .host_int_rsr_reg = 0x1, + .host_int_status_reg = 0x03, + .host_int_mask_reg = 0x02, + .status_reg_0 = 0xc0, + .status_reg_1 = 0xc1, + .sdio_int_mask = 0xff, + .data_port_mask = 0xffffffff, + .io_port_0_reg = 0xD8, + .io_port_1_reg = 0xD9, + .io_port_2_reg = 0xDA, + .max_mp_regs = 184, + .rd_bitmap_l = 0x04, + .rd_bitmap_u = 0x05, + .rd_bitmap_1l = 0x06, + .rd_bitmap_1u = 0x07, + .wr_bitmap_l = 0x08, + .wr_bitmap_u = 0x09, + .wr_bitmap_1l = 0x0a, + .wr_bitmap_1u = 0x0b, + .rd_len_p0_l = 0x0c, + .rd_len_p0_u = 0x0d, + .card_misc_cfg_reg = 0xcc, + .card_cfg_2_1_reg = 0xcd, + .cmd_rd_len_0 = 0xb4, + .cmd_rd_len_1 = 0xb5, + .cmd_rd_len_2 = 0xb6, + .cmd_rd_len_3 = 0xb7, + .cmd_cfg_0 = 0xb8, + .cmd_cfg_1 = 0xb9, + .cmd_cfg_2 = 0xba, + .cmd_cfg_3 = 0xbb, + .fw_dump_host_ready = 0xee, + .fw_dump_ctrl = 0xe2, + .fw_dump_start = 0xe3, + .fw_dump_end = 0xea, + .func1_dump_reg_start = 0x0, + .func1_dump_reg_end = 0xb, + .func1_scratch_reg = 0xc0, + .func1_spec_reg_num = 8, + .func1_spec_reg_table = {0x4C, 0x50, 0x54, 0x55, 0x58, + 0x59, 0x5c, 0x5d}, +}; + +static const struct mwl_sdio_card_reg mwl_reg_sd8997 = { + .start_rd_port = 0, + .start_wr_port = 0, + .base_0_reg = 0xf8, + .base_1_reg = 0xf9, + .poll_reg = 0x5c, + .host_int_enable = UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK | + CMD_PORT_UPLD_INT_MASK | CMD_PORT_DNLD_INT_MASK, + .host_int_rsr_reg = 0x4, + .host_int_status_reg = 0x0c, + .host_int_mask_reg = 0x08, + .status_reg_0 = 0xe8, + .status_reg_1 = 0xe9, + .sdio_int_mask = 0xff, + .data_port_mask = 0xffffffff, + .io_port_0_reg = 0xE4, + .io_port_1_reg = 0xE5, + .io_port_2_reg = 0xE6, + .max_mp_regs = 256, + .rd_bitmap_l = 0x10, + .rd_bitmap_u = 0x11, + .rd_bitmap_1l = 0x12, + .rd_bitmap_1u = 0x13, + .wr_bitmap_l = 0x14, + .wr_bitmap_u = 0x15, + .wr_bitmap_1l = 0x16, + .wr_bitmap_1u = 0x17, + .rd_len_p0_l = 0x18, + .rd_len_p0_u = 0x19, + .card_misc_cfg_reg = 0xd8, + .card_cfg_2_1_reg = 0xd9, + .cmd_rd_len_0 = 0xc0, + .cmd_rd_len_1 = 0xc1, + .cmd_rd_len_2 = 0xc2, + .cmd_rd_len_3 = 0xc3, + .cmd_cfg_0 = 0xc4, + .cmd_cfg_1 = 0xc5, + .cmd_cfg_2 = 0xc6, + .cmd_cfg_3 = 0xc7, + .fw_dump_host_ready = 0xee, + .fw_dump_ctrl = 0xe2, + .fw_dump_start = 0xe3, + .fw_dump_end = 0xea, + .func1_dump_reg_start = 0x0, + .func1_dump_reg_end = 0xb, + .func1_scratch_reg = 0xc0, + .func1_spec_reg_num = 8, + .func1_spec_reg_table = {0x4C, 0x50, 0x54, 0x55, 0x58, + 0x59, 0x5c, 0x5d}, +}; + + +/* data structure for SDIO MPA TX */ +struct mwifiex_sdio_mpa_tx { + /* multiport tx aggregation buffer pointer */ + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +struct mwifiex_sdio_mpa_rx { + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u32 ports; + u16 start_port; + + struct sk_buff **skb_arr; + u32 *len_arr; + + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +/* 16 bit SDIO event code */ +#define SDEVENT_RADAR_DETECT 0x0001 +#define SDEVENT_CHNL_SWITCH 0x0002 +#define SDEVENT_BA_WATCHDOG 0x0003 +#define SDEVENT_WAKEUP 0x0005 +#define SDEVENT_PS_SLEEP 0x0006 +#define SDEVENT_IBSS_LAST_BCN 0x0007 + +struct mwl_host_event_mac_t { + u16 event_id; + u8 payload[0]; +}; +struct mwl_ibss_lastBcn_payload { + u16 event; + u8 bssNum; + u8 bssType; + u8 bssTsf[8]; +}; +struct mwl_hostevent { + u16 length; + u16 type; + union { + struct mwl_host_event_mac_t mac_event; + struct mwl_ibss_lastBcn_payload BcnPayload; + }; +}; + +struct mwl_wait_queue { + wait_queue_head_t wait; + int status; +}; + +struct mwl_sdio_card { + struct mwl_priv *priv; + struct sdio_func *func; + struct device *dev; + const char *firmware; + const struct mwl_sdio_card_reg *reg; + struct sk_buff_head rx_data_q; + spinlock_t int_lock; + spinlock_t rx_proc_lock; + u8 int_status; + struct workqueue_struct *tx_workq; + struct work_struct tx_work; + struct workqueue_struct *cmd_workq; + struct work_struct cmd_work; + struct work_struct event_work; + /* + * Variables for data path + */ + u8 max_ports; /* max port on card. = 32 for KF */ + u8 mp_agg_pkt_limit; /* max aggregated pkts, = 16 for KF*/ + u16 tx_buf_size; /* tx buffer size = 4k for KF */ + u32 mp_tx_agg_buf_size; /* = 65280 for KF? */ + u32 mp_rx_agg_buf_size; /* = 65280 for KF? */ + + u32 mp_rd_bitmap; /* =[rd_bitmap_1u ~ rd_bitmap_l] from reg*/ + u32 mp_wr_bitmap; /* =[wr_bitmap_1u ~ reg->wr_bitmap_l] */ + + u16 mp_end_port; /* = 0x0020 for KF */ + u32 mp_data_port_mask; + + u8 curr_rd_port; + u8 curr_wr_port; + + u8 *mp_regs; + struct mwifiex_sdio_mpa_tx mpa_tx; + struct mwifiex_sdio_mpa_rx mpa_rx; + + int tx_pkt_unaligned_cnt; + + u16 sdio_rx_block_size; + u8 data_sent; + u8 data_received; + + /* + */ + bool is_running; + bool is_suspended; + bool expect_recovery; + bool host_disable_sdio_rx_aggr; + bool sdio_rx_aggr_enable; + bool rx_work_enabled; + atomic_t rx_pending; + + int chip_type; + u32 ioport; + bool cmd_resp_recvd; + + struct mwl_wait_queue cmd_wait_q; + u16 cmd_id; + + u32 rate_info; + /* needed for card reset */ + const struct sdio_device_id *dev_id; + wait_queue_head_t wait_deepsleep; + bool is_deepsleep; + + /* Host capabilities fixups */ + u32 caps_fixups; + int reset_pwd_gpio; + + int dnld_cmd_failure; +}; + +static inline bool +mp_tx_aggr_port_limit_reached(struct mwl_sdio_card *card) +{ + u16 tmp; + + if (card->curr_wr_port < card->mpa_tx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_tx.start_port) + + card->curr_wr_port) >= tmp) + return true; + } + + if ((card->curr_wr_port - card->mpa_tx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + +static inline bool +mp_rx_aggr_port_limit_reached(struct mwl_sdio_card *card) +{ + u8 tmp; + + if (card->curr_rd_port < card->mpa_rx.start_port) { + tmp = card->mp_end_port >> 1; + + if (((card->max_ports - card->mpa_rx.start_port) + + card->curr_rd_port) >= tmp) + return true; + } + + + if ((card->curr_rd_port - card->mpa_rx.start_port) >= + (card->mp_end_port >> 1)) + return true; + + return false; +} + + +/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */ +static inline void mp_rx_aggr_setup(struct mwl_sdio_card *card, + u16 rx_len, u8 port) +{ + card->mpa_rx.buf_len += rx_len; + + if (!card->mpa_rx.pkt_cnt) + card->mpa_rx.start_port = port; + + card->mpa_rx.ports |= (1 << port); + card->mpa_rx.skb_arr[card->mpa_rx.pkt_cnt] = NULL; + card->mpa_rx.len_arr[card->mpa_rx.pkt_cnt] = rx_len; + card->mpa_rx.pkt_cnt++; +} + +static inline struct mwl_rxinfo *MWL_SKB_RXCB(struct sk_buff *skb) +{ + struct mwl_cb *cb = (struct mwl_cb *)skb->cb; + + BUILD_BUG_ON(sizeof(struct mwl_cb) > sizeof(skb->cb)); + return &cb->rx_info; +} + +static inline struct mwl_txinfo *MWL_SKB_TXCB(struct sk_buff *skb) +{ + struct mwl_cb *cb = (struct mwl_cb *)skb->cb; + + return &cb->tx_info; +} + +void mwl_handle_rx_packet(struct mwl_priv *priv, struct sk_buff *skb); + +#endif /* _MWLWIFI_SDIO_H */ diff --git a/drivers/net/wireless/laird/lrdmwl/sysadpt.h b/drivers/net/wireless/laird/lrdmwl/sysadpt.h new file mode 100644 index 0000000000000..ac91ebdbc6e88 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/sysadpt.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines system adaptation related information. */ + +#ifndef _SYSADPT_H_ +#define _SYSADPT_H_ + + +#define SYSADPT_MAX_NUM_TX_DESC 256 + +#define SYSADPT_MAX_NUM_RX_DESC 256 + +#define SYSADPT_MAX_AGGR_SIZE 8192 + +#define SYSADPT_MAX_NUM_CHANNELS 64 + +#define SYSADPT_MAX_DATA_RATES_G 14 + +//#define SYSADPT_TX_POWER_LEVEL_TOTAL 16 + +#define SYSADPT_TX_GRP_PWR_LEVEL_TOTAL 28 + +#define SYSADPT_TX_PWR_LEVEL_TOTAL_SC4 32 + +#define SYSADPT_TX_WMM_QUEUES 4 + +#define SYSADPT_TX_AMPDU_QUEUES 4 + +#define SYSADPT_NUM_OF_SU_AP 3 +#define SYSADPT_NUM_OF_ST_AP 1 +#define SYSADPT_NUM_OF_STA 2 +#define SYSADPT_NUM_OF_AP SYSADPT_NUM_OF_SU_AP + +#define SYSADPT_TOTAL_TX_QUEUES (SYSADPT_TX_WMM_QUEUES + \ + SYSADPT_NUM_OF_AP) + +#define SYSADPT_TOTAL_HW_QUEUES (SYSADPT_TX_WMM_QUEUES + \ + SYSADPT_TX_AMPDU_QUEUES) + +#define SYSADPT_NUM_OF_DESC_DATA (4 + SYSADPT_NUM_OF_AP) +#define SYSADPT_PFU_NUM_OF_DESC_DATA (1) + +/* #define SYSADPT_MAX_NUM_TX_DESC 256 */ + +#define SYSADPT_TX_QUEUE_LIMIT (3 * SYSADPT_MAX_NUM_TX_DESC) + +#define SYSADPT_TX_WAKE_Q_THRESHOLD (2 * SYSADPT_MAX_NUM_TX_DESC) + +#define SYSADPT_DELAY_FREE_Q_LIMIT SYSADPT_MAX_NUM_TX_DESC + +/* #define SYSADPT_MAX_NUM_RX_DESC 256 */ + +#define SYSADPT_RECEIVE_LIMIT 64 + +/* #define SYSADPT_MAX_AGGR_SIZE 8192 */ + +#define SYSADPT_MIN_BYTES_HEADROOM 64 + +#define SYSADPT_TX_MIN_BYTES_HEADROOM (64 + 64) + +#define SYSADPT_AMPDU_PACKET_THRESHOLD 64 + +#define SYSADPT_AMSDU_FW_MAX_SIZE 3300 + +#define SYSADPT_AMSDU_4K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE + +#define SYSADPT_AMSDU_8K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE + +#define SYSADPT_AMSDU_ALLOW_SIZE 1600 + +#define SYSADPT_AMSDU_FLUSH_TIME 500 + +#define SYSADPT_AMSDU_PACKET_THRESHOLD 10 + +#define SYSADPT_MAX_TID 8 + +#define SYSADPT_QUIET_PERIOD_DEFAULT 100 + +#define SYSADPT_QUIET_PERIOD_MIN 25 + +#define SYSADPT_QUIET_START_OFFSET 10 + +#define SYSADPT_THERMAL_THROTTLE_MAX 100 + +#define SYSADPT_TIMER_WAKEUP_TIME 300 /* ms */ + +#endif /* _SYSADPT_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/sysfs.c b/drivers/net/wireless/laird/lrdmwl/sysfs.c new file mode 100644 index 0000000000000..f13b20ff57980 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/sysfs.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Laird Connectivity + * under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements sysfs related functions. */ + +#include + +#include "sysadpt.h" +#include "dev.h" + +static const char chipname[MWLUNKNOWN][8] = { + "88W8864", + "88W8897", + "88W8964", + "88W8997" +}; + +static const char chipbus[5][5] = { + "?", + "?", + "SDIO", + "PCIE", + "USB" +}; +static ssize_t info_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct ieee80211_hw *hw = dev_get_drvdata(d); + struct mwl_priv *priv; + char *p = (char *)buf; + int len = 0; + int size = PAGE_SIZE; + + if (!p || !hw || !hw->priv) { + return 0; + } + + priv = hw->priv; + + len += scnprintf(p + len, size - len, "\n"); + len += scnprintf(p + len, size - len, "Driver name : %s\n", MWL_DRV_NAME); + len += scnprintf(p + len, size - len, "Chip type : %s-%s\n", chipname[priv->chip_type], chipbus[priv->host_if]); + len += scnprintf(p + len, size - len, "HW version : %d\n", priv->hw_data.hw_version); + len += scnprintf(p + len, size - len, "FW version : %d.%d.%d.%d\n", + ((priv->hw_data.fw_release_num >> 24) & 0xff), + ((priv->hw_data.fw_release_num >> 16) & 0xff), + ((priv->hw_data.fw_release_num >> 8) & 0xff), + ((priv->hw_data.fw_release_num >> 0) & 0xff)); + len += scnprintf(p + len, size - len, "DRV version : %s\n", LRD_BLD_VERSION); + len += scnprintf(p + len, size - len, "OTP version : %d\n", priv->radio_caps.version); + len += scnprintf(p + len, size - len, "OTP cap : 0x%x\n", priv->radio_caps.capability); + len += scnprintf(p + len, size - len, "OTP num mac : %d\n", priv->radio_caps.num_mac); + len += scnprintf(p + len, size - len, "Radio Type : %s%s%s\n", + (priv->radio_caps.capability & LRD_CAP_SU60) ? "SU":"ST", + (priv->radio_caps.capability & LRD_CAP_440) ? "-440":"", + (priv->radio_caps.capability & LRD_CAP_SOM8MP) ? "-SOM8MP":""); + len += scnprintf(p + len, size - len, "BT Offset : %s\n", + (priv->radio_caps.capability & LRD_CAP_BT_SEQ) ? "1": + (priv->radio_caps.capability & LRD_CAP_BT_DUP) ? "0":"Default"); + len += scnprintf(p + len, size - len, "MAC address : %pM\n", priv->hw_data.mac_addr); + len += scnprintf(p + len, size - len, "Region code : 0x%02x (0x%02x)\n", priv->reg.cc.region, priv->reg.otp.region); + len += scnprintf(p + len, size - len, "Country code: '%c%c' ('%c%c')\n", + priv->reg.cc.alpha2[0],priv->reg.cc.alpha2[1], + priv->reg.otp.alpha2[0],priv->reg.otp.alpha2[1]); + len += scnprintf(p + len, size - len, "TX antenna : %d\n", priv->ant_tx_num); + len += scnprintf(p + len, size - len, "RX antenna : %d\n", priv->ant_rx_num); + len += scnprintf(p + len, size - len, "\n"); + + return len; +} + +static ssize_t mfg_mode_show(struct device *d, struct device_attribute *attr, char *buf) +{ + struct ieee80211_hw *hw = dev_get_drvdata(d); + struct mwl_priv *priv; + + if (!buf || !hw || !hw->priv) + return 0; + + priv = hw->priv; + + return scnprintf(buf, PAGE_SIZE, "%d\n", priv->mfg_mode); +} + +static DEVICE_ATTR(info, 0444, info_show, NULL); +static DEVICE_ATTR(mfg_mode, 0444, mfg_mode_show, NULL); + +static struct attribute *lrd_sys_status_entries[] = { + &dev_attr_info.attr, + &dev_attr_mfg_mode.attr, + NULL +}; + +static const struct attribute_group lrd_attribute_group = { + .name = "lrd", + .attrs = lrd_sys_status_entries, +}; + +void lrd_sysfs_init(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + int ret; + + if (!hw || !hw->priv ) { + return; + } + + priv = hw->priv; + + if (priv->dev) { + ret = sysfs_create_group(&priv->dev->kobj, &lrd_attribute_group); + if (ret) + wiphy_err(priv->hw->wiphy, "%s: Unable to create attribute group!\n", __func__); + } +} + +void lrd_sysfs_remove(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv; + + if (!hw || !hw->priv) { + return; + } + + priv = hw->priv; + + if (priv) { + sysfs_remove_group(&priv->dev->kobj, &lrd_attribute_group); + } +} diff --git a/drivers/net/wireless/laird/lrdmwl/sysfs.h b/drivers/net/wireless/laird/lrdmwl/sysfs.h new file mode 100644 index 0000000000000..41df68206344b --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/sysfs.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Laird Connectivity + * under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines debug fs related functions. */ + +#ifndef _LRD_SYSFS_H_ +#define _LRD_SYSFS_H_ + +void lrd_sysfs_init(struct ieee80211_hw *hw); +void lrd_sysfs_remove(struct ieee80211_hw *hw); + +#endif /* _LRD_SYSFS_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/thermal.c b/drivers/net/wireless/laird/lrdmwl/thermal.c new file mode 100644 index 0000000000000..908e89c9360d1 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/thermal.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements thermal framework related functions. */ + +#include +#include +#include +#include +#include + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "thermal.h" + +static int +mwl_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = SYSADPT_THERMAL_THROTTLE_MAX; + + return 0; +} + +static int +mwl_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct mwl_priv *priv = cdev->devdata; + + *state = priv->throttle_state; + + return 0; +} + +static int +mwl_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev, + unsigned long throttle_state) +{ + struct mwl_priv *priv = cdev->devdata; + + if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) { + wiphy_warn(priv->hw->wiphy, + "throttle state %ld is exceeding the limit %d\n", + throttle_state, SYSADPT_THERMAL_THROTTLE_MAX); + return -EINVAL; + } + priv->throttle_state = throttle_state; + mwl_thermal_set_throttling(priv); + + return 0; +} + +static struct thermal_cooling_device_ops mwl_thermal_ops = { + .get_max_state = mwl_thermal_get_max_throttle_state, + .get_cur_state = mwl_thermal_get_cur_throttle_state, + .set_cur_state = mwl_thermal_set_cur_throttle_state, +}; + +static ssize_t mwl_thermal_show_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mwl_priv *priv = dev_get_drvdata(dev); + int ret, temperature; + + ret = mwl_fwcmd_get_temp(priv->hw, &priv->temperature); + if (ret) { + wiphy_warn(priv->hw->wiphy, "failed: can't get temperature\n"); + goto out; + } + + temperature = priv->temperature; + + /* display in millidegree celcius */ + ret = snprintf(buf, PAGE_SIZE, "%d\n", temperature * 1000); +out: + return ret; +} + +static SENSOR_DEVICE_ATTR(temp1_input, 0444, mwl_thermal_show_temp, + NULL, 0); + +static struct attribute *mwl_hwmon_attrs[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mwl_hwmon); + +void mwl_thermal_set_throttling(struct mwl_priv *priv) +{ + u32 period, duration, enabled; + int ret; + + period = priv->quiet_period; + duration = (period * priv->throttle_state) / 100; + enabled = duration ? 1 : 0; + + ret = mwl_fwcmd_quiet_mode(priv->hw, enabled, period, + duration, SYSADPT_QUIET_START_OFFSET); + if (ret) { + wiphy_warn(priv->hw->wiphy, + "failed: period %u duarion %u enabled %u ret %d\n", + period, duration, enabled, ret); + } +} + +int mwl_thermal_register(struct mwl_priv *priv) +{ + struct thermal_cooling_device *cdev; + struct device *hwmon_dev; + int ret; + + if (priv->chip_type != MWL8897) + return 0; + + cdev = thermal_cooling_device_register("mwlwifi_thermal", priv, + &mwl_thermal_ops); + if (IS_ERR(cdev)) { + wiphy_err(priv->hw->wiphy, + "failed to setup thermal device result: %ld\n", + PTR_ERR(cdev)); + return -EINVAL; + } + + ret = sysfs_create_link(&priv->dev->kobj, &cdev->device.kobj, + "cooling_device"); + if (ret) { + wiphy_err(priv->hw->wiphy, + "failed to create cooling device symlink\n"); + goto err_cooling_destroy; + } + + priv->cdev = cdev; + priv->quiet_period = SYSADPT_QUIET_PERIOD_DEFAULT; + + if (!IS_ENABLED(CONFIG_HWMON)) + return 0; + + hwmon_dev = + devm_hwmon_device_register_with_groups(priv->dev, + "mwlwifi_hwmon", priv, + mwl_hwmon_groups); + if (IS_ERR(hwmon_dev)) { + wiphy_err(priv->hw->wiphy, + "failed to register hwmon device: %ld\n", + PTR_ERR(hwmon_dev)); + ret = -EINVAL; + goto err_remove_link; + } + + return 0; + +err_remove_link: + sysfs_remove_link(&priv->dev->kobj, "cooling_device"); +err_cooling_destroy: + thermal_cooling_device_unregister(cdev); + + return ret; +} + +void mwl_thermal_unregister(struct mwl_priv *priv) +{ + if (priv->chip_type != MWL8897) + return; + + sysfs_remove_link(&priv->dev->kobj, "cooling_device"); + thermal_cooling_device_unregister(priv->cdev); +} diff --git a/drivers/net/wireless/laird/lrdmwl/thermal.h b/drivers/net/wireless/laird/lrdmwl/thermal.h new file mode 100644 index 0000000000000..359000c849fc2 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/thermal.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines Linux thermal framework related functions. */ + +#ifndef _THERMAL_H_ +#define _THERMAL_H_ + +#include + +#if IS_ENABLED(CONFIG_THERMAL) +int mwl_thermal_register(struct mwl_priv *priv); +void mwl_thermal_unregister(struct mwl_priv *priv); +void mwl_thermal_set_throttling(struct mwl_priv *priv); +#else +static inline int mwl_thermal_register(struct mwl_priv *priv) +{ + return 0; +} + +static inline void mwl_thermal_unregister(struct mwl_priv *priv) +{ +} + +static inline void mwl_thermal_set_throttling(struct mwl_priv *priv) +{ +} +#endif + +#endif /* _THERMAL_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/tx.c b/drivers/net/wireless/laird/lrdmwl/tx.c new file mode 100644 index 0000000000000..b2a17f83a739c --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/tx.c @@ -0,0 +1,1243 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file implements transmit related functions. */ + +#include +#include + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "tx.h" +#include "main.h" + +extern int lrd_debug; + +#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)++; \ +} + +enum { + IEEE_TYPE_MANAGEMENT = 0, + IEEE_TYPE_CONTROL, + IEEE_TYPE_DATA +}; + +struct ccmp_hdr { + __le16 iv16; + u8 rsvd; + u8 key_id; + __le32 iv32; +} __packed; + + +static inline int mwl_tx_add_dma_header(struct mwl_priv *priv, + struct sk_buff *skb, + int head_pad, + int tail_pad) +{ + struct ieee80211_hdr *wh, *tr_wh_ptr; + int hdrlen; + int reqd_hdrlen, dma_hdr_len; + __le16 *tr_fwlen_ptr; + + dma_hdr_len = (((priv->host_if == MWL_IF_PCIE) && + IS_PFU_ENABLED(priv->chip_type)) ? + sizeof(struct mwl_tx_pfu_dma_data) : + sizeof(struct mwl_dma_data)); + + /* 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); + /*wiphy_err(priv->hw->wiphy,"%s() hdrlen=%d\n",__FUNCTION__,hdrlen);*/ + + reqd_hdrlen = dma_hdr_len + head_pad; + /* wiphy_err(priv->hw->wiphy, "reqd_hdrlen=%d\n", reqd_hdrlen); */ + + /* Ensure there is enough headroom to accommodate reqd_hdrlen + * Warning! - Buffer may be reallocated during this call! + */ + if (skb_cow_head(skb, reqd_hdrlen)) { + wiphy_err(priv->hw->wiphy, "%s() Not enough headroom in skb! Need %d, available %d", \ + __FUNCTION__, reqd_hdrlen - hdrlen, skb_headroom(skb)); + WARN_ON(skb_headroom(skb) < (reqd_hdrlen - hdrlen)); + return -1; + } + + /* Reinit wh pointer in case buffer has been re-allocated */ + wh = (struct ieee80211_hdr *)skb->data; + + if (hdrlen != reqd_hdrlen) { + skb_push(skb, reqd_hdrlen - hdrlen); + } + + if (ieee80211_is_data_qos(wh->frame_control)) { + hdrlen -= IEEE80211_QOS_CTL_LEN; + + if (ieee80211_has_order(wh->frame_control)) + hdrlen -= IEEE80211_HT_CTL_LEN; + } + + if (((priv->host_if == MWL_IF_PCIE) && + IS_PFU_ENABLED(priv->chip_type))) { + struct mwl_tx_pfu_dma_data *tr = + (struct mwl_tx_pfu_dma_data *)skb->data; + + tr_wh_ptr = &tr->wh; + tr_fwlen_ptr = &tr->fwlen; + } else { + struct mwl_dma_data *tr = (struct mwl_dma_data *)skb->data; + + tr_wh_ptr = &tr->wh; + tr_fwlen_ptr = &tr->fwlen; + } + + if (wh != tr_wh_ptr) + memmove(tr_wh_ptr, wh, hdrlen); + + if (hdrlen != sizeof(struct ieee80211_hdr)) + memset(((void *)tr_wh_ptr) + hdrlen, 0, + sizeof(struct ieee80211_hdr) - 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_ptr = cpu_to_le16(skb->len - dma_hdr_len + tail_pad); + return 0; +} + +static inline int mwl_tx_encapsulate_frame(struct mwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_key_conf *k_conf, + bool *ccmp) +{ + int head_pad = 0; + int data_pad = 0; + + /* Make sure the packet header is in the DMA header format (4-address + * without QoS), and add head & tail padding when HW crypto is enabled. + * + * We have the following trailer padding requirements: + * - WEP: 4 trailer bytes (ICV) + * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) + * - CCMP: 8 trailer bytes (MIC) + */ + + if (k_conf) { + head_pad = k_conf->iv_len; + + switch (k_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + data_pad = 4; + break; + case WLAN_CIPHER_SUITE_TKIP: + data_pad = 12; + break; + case WLAN_CIPHER_SUITE_CCMP: + data_pad = 8; + *ccmp = true; + break; + } + } + + /* existing pointers into skb buffer are not valid after this call */ + return mwl_tx_add_dma_header(priv, skb, head_pad, data_pad); + + +} + +static inline void mwl_tx_insert_ccmp_hdr(u8 *pccmp_hdr, + u8 key_id, u16 iv16, u32 iv32) +{ + struct ccmp_hdr *ccmp_h = (struct ccmp_hdr *)pccmp_hdr; + + ccmp_h->iv16 = cpu_to_le16(iv16); + ccmp_h->rsvd = 0; + ccmp_h->key_id = EXT_IV | (key_id << 6); + ccmp_h->iv32 = cpu_to_le32(iv32); +} + +static inline int mwl_tx_tid_queue_mapping(u8 tid) +{ + switch (tid) { + case 0: + case 3: + return IEEE80211_AC_BE; + case 1: + case 2: + return IEEE80211_AC_BK; + case 4: + case 5: + return IEEE80211_AC_VI; + case 6: + case 7: + return IEEE80211_AC_VO; + default: + break; + } + + return -1; +} + +static inline void mwl_tx_add_basic_rates(int band, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt; + int len; + u8 *pos; + + mgmt = (struct ieee80211_mgmt *)skb->data; + len = skb->len - ieee80211_hdrlen(mgmt->frame_control); + len -= 4; + pos = (u8 *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, + mgmt->u.assoc_req.variable, + len); + if (pos) { + pos++; + len = *pos++; + while (len) { + if (band == NL80211_BAND_2GHZ) { + if ((*pos == 2) || (*pos == 4) || + (*pos == 11) || (*pos == 22)) + *pos |= 0x80; + } else { + if ((*pos == 12) || (*pos == 24) || + (*pos == 48)) + *pos |= 0x80; + } + pos++; + len--; + } + } +} + +static inline void mwl_tx_set_cap_2040_coex(__le16 fc, struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt; + int len; + u8 *pos, *ie_start; + + mgmt = (struct ieee80211_mgmt *)skb->data; + len = skb->len - ieee80211_hdrlen(fc); + len -= 4; + + if (ieee80211_is_reassoc_req(fc)) { + len -= 6; /* Current AP addr */ + ie_start = mgmt->u.reassoc_req.variable; + } else + ie_start = mgmt->u.assoc_req.variable; + + pos = (u8 *)cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ie_start, len); + + if (pos) { + pos++; + len = *pos++; + if(len) + pos[0] |= BIT(0); /* Set 20/40 coex support */ + } + +} + +static inline void mwl_tx_count_packet(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_sta *sta_info; + struct mwl_tx_info *tx_stats; + + if (WARN_ON(tid >= SYSADPT_MAX_TID)) + return; + + sta_info = mwl_dev_get_sta(sta); + + tx_stats = &sta_info->tx_stats[tid]; + + if (tx_stats->start_time == 0) + tx_stats->start_time = jiffies; + + /* reset the packet count after each second elapses. If the number of + * packets ever exceeds the ampdu_min_traffic threshold, we will allow + * an ampdu stream to be started. + */ + if (jiffies - tx_stats->start_time > HZ) { + tx_stats->pkts = 0; + tx_stats->start_time = jiffies; + } else { + tx_stats->pkts++; + } +} + +static inline bool mwl_tx_available(struct mwl_priv *priv, int desc_num) +{ + return priv->if_ops.is_tx_available(priv, desc_num); +} + +inline void mwl_tx_skb(struct mwl_priv *priv, int desc_num, + struct sk_buff *tx_skb) +{ + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_sta *sta; + struct ieee80211_vif *vif; + struct mwl_vif *mwl_vif; + struct ieee80211_key_conf *k_conf; + bool ccmp = false; + struct ieee80211_hdr *wh; + char *dma_data_pload; + + if (WARN_ON(!tx_skb)) + return; + +// wiphy_err(priv->hw->wiphy, "%s() called\n", __FUNCTION__); + + tx_info = IEEE80211_SKB_CB(tx_skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + sta = (struct ieee80211_sta *)tx_ctrl->sta; + vif = (struct ieee80211_vif *)tx_ctrl->vif; + mwl_vif = mwl_dev_get_vif(vif); + k_conf = (struct ieee80211_key_conf *)tx_ctrl->k_conf; + + /* existing pointers into skb buffer are not valid after this call */ + if (mwl_tx_encapsulate_frame(priv, tx_skb, k_conf, &ccmp)) { + wiphy_err(priv->hw->wiphy, "%s() Failed to encapsulate packet, dropping!!\n", __FUNCTION__); + dev_kfree_skb_any(tx_skb); + return; + } + + if (((priv->host_if == MWL_IF_PCIE) && + IS_PFU_ENABLED(priv->chip_type))) { + struct mwl_tx_pfu_dma_data *dma_data = + (struct mwl_tx_pfu_dma_data *)tx_skb->data; + + wh = &dma_data->wh; + dma_data_pload = dma_data->data; + } else { + struct mwl_dma_data *dma_data = + (struct mwl_dma_data *)tx_skb->data; + + wh = &dma_data->wh; + dma_data_pload = dma_data->data; + } + + if (ieee80211_is_data(wh->frame_control) || + (ieee80211_is_mgmt(wh->frame_control) && + ieee80211_has_protected(wh->frame_control) && + !is_multicast_ether_addr(wh->addr1))) { + if (is_multicast_ether_addr(wh->addr1)) { + if (ccmp) { + mwl_tx_insert_ccmp_hdr(dma_data_pload, + mwl_vif->keyidx, + mwl_vif->iv16, + mwl_vif->iv32); + INCREASE_IV(mwl_vif->iv16, mwl_vif->iv32); + } + } else { + if (ccmp) { + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_P2P_CLIENT)) { + mwl_tx_insert_ccmp_hdr(dma_data_pload, + mwl_vif->keyidx, + mwl_vif->iv16, + mwl_vif->iv32); + INCREASE_IV(mwl_vif->iv16, + mwl_vif->iv32); + } else { + struct mwl_sta *sta_info; + + sta_info = mwl_dev_get_sta(sta); + + mwl_tx_insert_ccmp_hdr(dma_data_pload, + 0, + sta_info->iv16, + sta_info->iv32); + INCREASE_IV(sta_info->iv16, + sta_info->iv32); + } + } + } + } + + priv->if_ops.host_to_card(priv, desc_num, tx_skb); +} +EXPORT_SYMBOL_GPL(mwl_tx_skb); + +static inline struct sk_buff *mwl_tx_do_amsdu(struct mwl_priv *priv, + int desc_num, + struct sk_buff *tx_skb, + struct ieee80211_tx_info *tx_info) +{ + struct ieee80211_sta *sta; + struct mwl_sta *sta_info; + struct mwl_tx_ctrl *tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + struct ieee80211_tx_info *amsdu_info; + struct sk_buff_head *amsdu_pkts; + struct mwl_amsdu_frag *amsdu; + int amsdu_allow_size; + struct ieee80211_hdr *wh; + int wh_len; + u16 len; + u8 *data; + + sta = (struct ieee80211_sta *)tx_ctrl->sta; + sta_info = mwl_dev_get_sta(sta); + + if (!sta_info->is_amsdu_allowed || !tx_amsdu_enable) + return tx_skb; + + wh = (struct ieee80211_hdr *)tx_skb->data; + if (sta_info->is_mesh_node && is_multicast_ether_addr(wh->addr3)) + return tx_skb; + + if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_4K) + amsdu_allow_size = SYSADPT_AMSDU_4K_MAX_SIZE; + else if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_8K) + amsdu_allow_size = SYSADPT_AMSDU_8K_MAX_SIZE; + else + return tx_skb; + + spin_lock_bh(&sta_info->amsdu_lock); + amsdu = &sta_info->amsdu_ctrl.frag[desc_num]; + +// wiphy_err(priv->hw->wiphy, "[1]\n"); + if (tx_skb->len > SYSADPT_AMSDU_ALLOW_SIZE) { + if (amsdu->num) { + spin_unlock_bh(&sta_info->amsdu_lock); + mwl_tx_skb(priv, desc_num, amsdu->skb); + spin_lock_bh(&sta_info->amsdu_lock); + amsdu->num = 0; + amsdu->cur_pos = NULL; + } + spin_unlock_bh(&sta_info->amsdu_lock); + return tx_skb; + } + + /* potential amsdu size, should add amsdu header 14 bytes + + * maximum padding 3. + */ + wh_len = ieee80211_hdrlen(wh->frame_control); + len = tx_skb->len - wh_len + 17; + + /*pr_alert("(2)len=%d a->num=%d\n",tx_skb->len,amsdu->num);*/ + if (amsdu->num) { + if ((amsdu->skb->len + len) > amsdu_allow_size) { + spin_unlock_bh(&sta_info->amsdu_lock); + mwl_tx_skb(priv, desc_num, amsdu->skb); + spin_lock_bh(&sta_info->amsdu_lock); + amsdu->num = 0; + amsdu->cur_pos = NULL; + } + } + + amsdu->jiffies = jiffies; + len = tx_skb->len - wh_len; + + if (amsdu->num == 0) { + struct sk_buff *newskb; + int headroom; + + amsdu_pkts = (struct sk_buff_head *) + kmalloc(sizeof(*amsdu_pkts), GFP_ATOMIC); + if (!amsdu_pkts) { + spin_unlock_bh(&sta_info->amsdu_lock); + return tx_skb; + } + newskb = dev_alloc_skb(amsdu_allow_size + + SYSADPT_TX_MIN_BYTES_HEADROOM); + if (!newskb) { + spin_unlock_bh(&sta_info->amsdu_lock); + kfree(amsdu_pkts); + wiphy_err(priv->hw->wiphy, "Failed to allocate skb for amsdu pkt\n"); + return tx_skb; + } + + headroom = skb_headroom(newskb); + if (headroom < SYSADPT_TX_MIN_BYTES_HEADROOM) { + /* wiphy_err(priv->hw->wiphy, + * "INCR reserve hroom %d -> %d\n", + * headroom, SYSADPT_TX_MIN_BYTES_HEADROOM); + */ + skb_reserve(newskb, + (SYSADPT_TX_MIN_BYTES_HEADROOM - headroom)); + } + + data = newskb->data; + memcpy(data, tx_skb->data, wh_len); + if (sta_info->is_mesh_node) { + ether_addr_copy(data + wh_len, wh->addr3); + ether_addr_copy(data + wh_len + ETH_ALEN, wh->addr4); + } else { + ether_addr_copy(data + wh_len, + ieee80211_get_DA(wh)); + ether_addr_copy(data + wh_len + ETH_ALEN, + ieee80211_get_SA(wh)); + } + *(u8 *)(data + wh_len + ETH_HLEN - 1) = len & 0xff; + *(u8 *)(data + wh_len + ETH_HLEN - 2) = (len >> 8) & 0xff; + memcpy(data + wh_len + ETH_HLEN, tx_skb->data + wh_len, len); + + skb_put(newskb, tx_skb->len + ETH_HLEN); + tx_ctrl->qos_ctrl |= IEEE80211_QOS_CTL_A_MSDU_PRESENT; + amsdu_info = IEEE80211_SKB_CB(newskb); + memcpy(amsdu_info, tx_info, sizeof(*tx_info)); + skb_queue_head_init(amsdu_pkts); + ((struct mwl_tx_ctrl *)&amsdu_info->status)->amsdu_pkts = + (void *)amsdu_pkts; + amsdu->skb = newskb; + } else { + amsdu->cur_pos += amsdu->pad; + data = amsdu->cur_pos; + + if (sta_info->is_mesh_node) { + ether_addr_copy(data, wh->addr3); + ether_addr_copy(data + ETH_ALEN, wh->addr4); + } else { + ether_addr_copy(data, ieee80211_get_DA(wh)); + ether_addr_copy(data + ETH_ALEN, ieee80211_get_SA(wh)); + } + *(u8 *)(data + ETH_HLEN - 1) = len & 0xff; + *(u8 *)(data + ETH_HLEN - 2) = (len >> 8) & 0xff; + memcpy(data + ETH_HLEN, tx_skb->data + wh_len, len); + + skb_put(amsdu->skb, len + ETH_HLEN + amsdu->pad); + amsdu_info = IEEE80211_SKB_CB(amsdu->skb); + amsdu_pkts = (struct sk_buff_head *) + ((struct mwl_tx_ctrl *)&amsdu_info->status)->amsdu_pkts; + } + + amsdu->num++; + amsdu->pad = ((len + ETH_HLEN) % 4) ? (4 - (len + ETH_HLEN) % 4) : 0; + amsdu->cur_pos = amsdu->skb->data + amsdu->skb->len; + skb_queue_tail(amsdu_pkts, tx_skb); + + if (amsdu->num > SYSADPT_AMSDU_PACKET_THRESHOLD) { + amsdu->num = 0; + amsdu->cur_pos = NULL; + spin_unlock_bh(&sta_info->amsdu_lock); + return amsdu->skb; + } + /* amsdu_tcp */ + if ((priv->if_ops.flush_amsdu != NULL) + && (amsdu->num > 1)) { + priv->if_ops.flush_amsdu((unsigned long)priv->hw); + } + + spin_unlock_bh(&sta_info->amsdu_lock); + return NULL; +} + + +inline void mwl_tx_prepare_info(struct ieee80211_hw *hw, u32 rate, + struct ieee80211_tx_info *info) +{ + u32 format, bandwidth, short_gi, rate_id, nss; + + ieee80211_tx_info_clear_status(info); + + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + info->status.rates[0].flags = 0; + + if (rate) { + /* Prepare rate information */ + format = rate & MWL_TX_RATE_FORMAT_MASK; + bandwidth = + (rate & MWL_TX_RATE_BANDWIDTH_MASK) >> + MWL_TX_RATE_BANDWIDTH_SHIFT; + short_gi = (rate & MWL_TX_RATE_SHORTGI_MASK) >> + MWL_TX_RATE_SHORTGI_SHIFT; + rate_id = (rate & MWL_TX_RATE_RATEIDMCS_MASK) >> + MWL_TX_RATE_RATEIDMCS_SHIFT; + nss = (rate & MWL_TX_RATE_NSS_MASK) >> + MWL_TX_RATE_NSS_SHIFT; + + info->status.rates[0].idx = rate_id; + if (format == TX_RATE_FORMAT_LEGACY) { + if (hw->conf.chandef.chan->hw_value > + BAND_24_CHANNEL_NUM) { + info->status.rates[0].idx -= 5; + } + } else if (format == TX_RATE_FORMAT_11AC) { + ieee80211_rate_set_vht( + &info->status.rates[0], rate_id, nss); + } + + if (format == TX_RATE_FORMAT_11N) + info->status.rates[0].flags |= + IEEE80211_TX_RC_MCS; + if (format == TX_RATE_FORMAT_11AC) + info->status.rates[0].flags |= + IEEE80211_TX_RC_VHT_MCS; + if (bandwidth == TX_RATE_BANDWIDTH_40) + info->status.rates[0].flags |= + IEEE80211_TX_RC_40_MHZ_WIDTH; + if (bandwidth == TX_RATE_BANDWIDTH_80) + info->status.rates[0].flags |= + IEEE80211_TX_RC_80_MHZ_WIDTH; + if (bandwidth == TX_RATE_BANDWIDTH_160) + info->status.rates[0].flags |= + IEEE80211_TX_RC_160_MHZ_WIDTH; + if (short_gi == TX_RATE_INFO_SHORT_GI) + info->status.rates[0].flags |= + IEEE80211_TX_RC_SHORT_GI; + info->status.rates[0].count = 1; + info->status.rates[1].idx = -1; + } +} +EXPORT_SYMBOL_GPL(mwl_tx_prepare_info); + +inline void mwl_tx_ack_amsdu_pkts(struct ieee80211_hw *hw, u32 rate, + struct sk_buff_head *amsdu_pkts) +{ + struct mwl_priv *priv = hw->priv; + struct sk_buff *amsdu_pkt; + struct ieee80211_tx_info *info; + + while (skb_queue_len(amsdu_pkts) > 0) { + amsdu_pkt = skb_dequeue(amsdu_pkts); + info = IEEE80211_SKB_CB(amsdu_pkt); + mwl_tx_prepare_info(hw, rate, info); + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= IEEE80211_TX_STAT_ACK; + + // USB completion handlers are sometimes called in irq context + if (priv->host_if == MWL_IF_USB) + ieee80211_tx_status_irqsafe(hw, amsdu_pkt); + else + ieee80211_tx_status(hw, amsdu_pkt); + } + + kfree(amsdu_pkts); +} +EXPORT_SYMBOL_GPL(mwl_tx_ack_amsdu_pkts); + +static +unsigned int wmm_find_txq_for_tc(struct mwl_priv *priv, + unsigned int hw_tc) +{ + if (hw_tc >= SYSADPT_TX_WMM_QUEUES){ + hw_tc = SYSADPT_TX_WMM_QUEUES-1; + } + + return priv->tc_2_txq_map[hw_tc]; +} + +static +unsigned int wmm_find_tc_for_txq(struct mwl_priv *priv, + unsigned int txq) +{ + if (txq >= SYSADPT_TX_WMM_QUEUES){ + txq = SYSADPT_TX_WMM_QUEUES-1; + } + + return priv->txq_2_tc_map[txq]; +} + +void wmm_init_tc_to_txq_mapping(struct mwl_priv *priv) +{ + int tc, i, j; + struct ieee80211_tx_queue_params *params; + + struct { + int avg_bkoff; + int tc; + } bkoff[SYSADPT_TX_WMM_QUEUES], tmp; + + for (tc=0; tcwmm_params[tc]; + bkoff[tc].tc = tc; + bkoff[tc].avg_bkoff = + (params->cw_min >> 1) + params->aifs; + } + + for(i=0; i bkoff[j].avg_bkoff) || + ((bkoff[j-1].avg_bkoff == bkoff[j].avg_bkoff) && + (bkoff[j-1].tc < bkoff[j].tc))) { + + memcpy(&tmp, &bkoff[j-1], sizeof(tmp)); + memcpy(&bkoff[j-1], &bkoff[j], sizeof(tmp)); + memcpy(&bkoff[j], &tmp, sizeof(tmp)); + } + } + } + + for(i=0; itc_2_txq_map[tc] = txq; + priv->txq_2_tc_map[txq] = tc; + } + + if (lrd_debug) { + for(i=0; ihw->wiphy, "prio_idx=%d bkoff=%d tc=%d\n", i, bkoff[i].avg_bkoff, bkoff[i].tc); + } + + wiphy_debug(priv->hw->wiphy, "tc_2_txq_map = [ %d:%d %d:%d %d:%d %d:%d ]\n", + priv->tc_2_txq_map[0], priv->txq_2_tc_map[0], + priv->tc_2_txq_map[1], priv->txq_2_tc_map[1], + priv->tc_2_txq_map[2], priv->txq_2_tc_map[2], + priv->tc_2_txq_map[3], priv->txq_2_tc_map[3]); + } + +} + +void mwl_tx_xmit(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl_priv *priv = hw->priv; + int txq_idx; + int mac80211_tc, hw_tc; + struct ieee80211_sta *sta; + struct ieee80211_tx_info *tx_info; + struct mwl_vif *mwl_vif; + struct ieee80211_hdr *wh; + u8 xmitcontrol; + u16 qos; + int txpriority; + u8 tid = 0; + struct mwl_ampdu_stream *stream = NULL; + bool start_ba_session = false; + bool mgmtframe = false; + struct ieee80211_mgmt *mgmt; + bool eapol_frame = false; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_key_conf *k_conf = NULL; + + mac80211_tc = skb_get_queue_mapping(skb); + sta = control->sta; + + wh = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(wh->frame_control)) + qos = *((u16 *)ieee80211_get_qos_ctl(wh)); + else + qos = 0; + + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) { + mac80211_tc = IEEE80211_AC_VO; + eapol_frame = true; + } + + if (ieee80211_is_mgmt(wh->frame_control)) { + mgmtframe = true; + mgmt = (struct ieee80211_mgmt *)skb->data; + } + +#ifdef CONFIG_DEBUG_FS + if (ieee80211_is_mgmt(wh->frame_control)) + priv->tx_mgmt_cnt++; + else + priv->tx_data_cnt++; +#endif + + tx_info = IEEE80211_SKB_CB(skb); + mwl_vif = mwl_dev_get_vif(tx_info->control.vif); + + if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + if (tx_info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + mwl_vif->seqno += 0x10; + + wh->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + wh->seq_ctrl |= cpu_to_le16(mwl_vif->seqno); + } + + /* 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) || eapol_frame) + xmitcontrol |= EAGLE_TXD_XMITCTRL_USE_MC_RATE; + } + + k_conf = tx_info->control.hw_key; + + /* Queue ADDBA request in the respective data queue. While setting up + * the ampdu stream, mac80211 queues further packets for that + * particular ra/tid pair. However, packets piled up in the hardware + * for that ra/tid pair will still go out. ADDBA request and the + * related data packets going out from different queues asynchronously + * will cause a shift in the receiver window which might result in + * ampdu packets getting dropped at the receiver after the stream has + * been setup. + */ + if (mgmtframe) { + u16 capab; + + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_req.action_code == + WLAN_ACTION_ADDBA_REQ)) { + capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); + tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; + mac80211_tc = mwl_tx_tid_queue_mapping(tid); + } + + if (unlikely(ieee80211_is_assoc_req(wh->frame_control))) + mwl_tx_add_basic_rates(hw->conf.chandef.chan->band, + skb); + + /* XXX: WAR - Currently it's not clear where/how to inform to + ** to upper layer to set 20/40 coex capability. Set it in + ** driver for now + */ + if (unlikely((ieee80211_is_assoc_req(wh->frame_control) || + ieee80211_is_reassoc_req(wh->frame_control)) && + (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ))) { + wiphy_err(hw->wiphy, "Setting 20/40 coex cap\n"); + mwl_tx_set_cap_2040_coex(wh->frame_control, skb); + } + } + + hw_tc = SYSADPT_TX_WMM_QUEUES - mac80211_tc - 1; + txpriority = hw_tc; + + txq_idx = wmm_find_txq_for_tc(priv, hw_tc); + + if (sta && sta->deflink.ht_cap.ht_supported && !eapol_frame && + ieee80211_is_data_qos(wh->frame_control)) { + tid = qos & 0xf; + mwl_tx_count_packet(sta, tid); + + spin_lock_bh(&priv->stream_lock); + stream = mwl_fwcmd_lookup_stream(hw, sta->addr, tid); + + if (stream) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + if (WARN_ON(!(qos & + MWL_QOS_ACK_POLICY_BLOCKACK))) { + spin_unlock_bh(&priv->stream_lock); + dev_kfree_skb_any(skb); + return; + } + + txpriority = + (SYSADPT_TX_WMM_QUEUES + stream->idx) % + SYSADPT_TOTAL_HW_QUEUES; + } else if (stream->state == AMPDU_STREAM_NEW) { + /* We get here if the driver sends us packets + * after we've initiated a stream, but before + * our ampdu_action routine has been called + * with IEEE80211_AMPDU_TX_START to get the SSN + * for the ADDBA request. So this packet can + * go out with no risk of sequence number + * mismatch. No special handling is required. + */ + } else { + /* Drop packets that would go out after the + * ADDBA request was sent but before the ADDBA + * response is received. If we don't do this, + * the recipient would probably receive it + * after the ADDBA request with SSN 0. This + * will cause the recipient's BA receive window + * to shift, which would cause the subsequent + * packets in the BA stream to be discarded. + * mac80211 queues our packets for us in this + * case, so this is really just a safety check. + */ + wiphy_warn(hw->wiphy, + "can't send packet during ADDBA\n"); + spin_unlock_bh(&priv->stream_lock); + dev_kfree_skb_any(skb); + return; + } + } else { + if (mwl_fwcmd_ampdu_allowed(sta, tid)) { + stream = mwl_fwcmd_add_stream(hw, sta, tid); + + if (stream) + start_ba_session = true; + } + } + + spin_unlock_bh(&priv->stream_lock); + } else { + qos &= ~MWL_QOS_ACK_POLICY_MASK; + qos |= MWL_QOS_ACK_POLICY_NORMAL; + } + + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + tx_ctrl->vif = (void *)tx_info->control.vif; + tx_ctrl->sta = (void *)sta; + tx_ctrl->k_conf = (void *)k_conf; + tx_ctrl->amsdu_pkts = NULL; + tx_ctrl->tx_priority = txpriority; + tx_ctrl->type = (mgmtframe ? IEEE_TYPE_MANAGEMENT : IEEE_TYPE_DATA); + tx_ctrl->qos_ctrl = qos; + tx_ctrl->xmit_control = xmitcontrol; + + if (skb_queue_len(&priv->txq[txq_idx]) > priv->txq_limit){ + if (lrd_debug) + wiphy_debug(priv->hw->wiphy, "[SQ%d.%d.%d]\n", mac80211_tc, txq_idx, skb_queue_len(&priv->txq[txq_idx])); + ieee80211_stop_queue(hw, mac80211_tc); + } + + skb_queue_tail(&priv->txq[txq_idx], skb); + + mwl_restart_ds_timer(priv, false); + + if (priv->if_ops.ptx_work != NULL) { + /* SDIO interface is using this path */ + queue_work(priv->if_ops.ptx_workq, priv->if_ops.ptx_work); + } else { + /* PCIE interface is using this path */ + tasklet_schedule(priv->if_ops.ptx_task); + } + + /* Initiate the ampdu session here */ + if (start_ba_session) { + spin_lock_bh(&priv->stream_lock); + if (mwl_fwcmd_start_stream(hw, stream)) + mwl_fwcmd_remove_stream(hw, stream); + spin_unlock_bh(&priv->stream_lock); + } +} + +void mwl_tx_del_pkts_via_vif(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl_priv *priv = hw->priv; + int num; + struct sk_buff *skb, *tmp; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + bool enable_queues = false; + unsigned long flags; + + for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + spin_lock_irqsave(&priv->txq[num].lock, flags); + skb_queue_walk_safe(&priv->txq[num], skb, tmp) { + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + if (tx_ctrl->vif == vif) { + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + skb_queue_purge(amsdu_pkts); + kfree(amsdu_pkts); + } + __skb_unlink(skb, &priv->txq[num]); + dev_kfree_skb_any(skb); + enable_queues = true; + } + } + spin_unlock_irqrestore(&priv->txq[num].lock, flags); + } + + // Restart mac80211 queues if any TX queues were cleared + if (enable_queues == true) + ieee80211_wake_queues(hw); +} + +void mwl_tx_del_pkts_via_sta(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct mwl_priv *priv = hw->priv; + int num; + struct sk_buff *skb, *tmp; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + bool enable_queues = false; + unsigned long flags; + + for (num = 1; num < SYSADPT_NUM_OF_DESC_DATA; num++) { + spin_lock_irqsave(&priv->txq[num].lock, flags); + skb_queue_walk_safe(&priv->txq[num], skb, tmp) { + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + if (tx_ctrl->sta == sta) { + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + skb_queue_purge(amsdu_pkts); + kfree(amsdu_pkts); + } + __skb_unlink(skb, &priv->txq[num]); + dev_kfree_skb_any(skb); + enable_queues = true; + } + } + spin_unlock_irqrestore(&priv->txq[num].lock, flags); + } + + // Restart mac80211 queues if any TX queues were cleared + if (enable_queues == true) + ieee80211_wake_queues(hw); + +} + +void mwl_tx_del_ampdu_pkts(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 tid) +{ + struct mwl_priv *priv = hw->priv; + struct mwl_sta *sta_info = mwl_dev_get_sta(sta); + int ac, desc_num; + struct mwl_amsdu_frag *amsdu_frag; + struct sk_buff *skb, *tmp; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + unsigned long flags; + + ac = mwl_tx_tid_queue_mapping(tid); + desc_num = SYSADPT_TX_WMM_QUEUES - ac - 1; + spin_lock_irqsave(&priv->txq[desc_num].lock, flags); + skb_queue_walk_safe(&priv->txq[desc_num], skb, tmp) { + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + if (tx_ctrl->sta == sta) { + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + skb_queue_purge(amsdu_pkts); + kfree(amsdu_pkts); + } + __skb_unlink(skb, &priv->txq[desc_num]); + dev_kfree_skb_any(skb); + } + } + spin_unlock_irqrestore(&priv->txq[desc_num].lock, flags); + + spin_lock_bh(&sta_info->amsdu_lock); + amsdu_frag = &sta_info->amsdu_ctrl.frag[desc_num]; + if (amsdu_frag->num) { + amsdu_frag->num = 0; + amsdu_frag->cur_pos = NULL; + if (amsdu_frag->skb) { + tx_info = IEEE80211_SKB_CB(amsdu_frag->skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + skb_queue_purge(amsdu_pkts); + kfree(amsdu_pkts); + } + dev_kfree_skb_any(amsdu_frag->skb); + } + } + spin_unlock_bh(&sta_info->amsdu_lock); +} + +int mwl_tx_get_highest_priority_qnum(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int num = -1; + + for (num = SYSADPT_TX_WMM_QUEUES - 1; num >= 0; num--) { + if ((skb_queue_len(&priv->txq[num]) > 0) && + mwl_tx_available(priv, num)) + return num; + } + return num; +} + + +void mwl_tx_skbs(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + int num; + struct sk_buff *tx_skb; + struct mwl_sta *sta_info; + + /*pr_alert( "%s() called\n", __FUNCTION__);*/ + +#if 0 + pr_alert("wrptr=0x%x, rdptr=0x%x not_full=%d\n", + priv->txbd_wrptr, priv->txbd_rdptr, + PCIE_TXBD_NOT_FULL(priv->txbd_wrptr, priv->txbd_rdptr)); +#endif + + spin_lock_bh(&priv->tx_desc_lock); + while (1) { + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + + num = mwl_tx_get_highest_priority_qnum(hw); + if (num < 0) + break; + + tx_skb = skb_dequeue(&priv->txq[num]); + + if (!tx_skb) + continue; + + tx_info = IEEE80211_SKB_CB(tx_skb); + tx_ctrl = (struct mwl_tx_ctrl *)&tx_info->status; + + if ((tx_skb->protocol != cpu_to_be16(ETH_P_PAE)) && + (tx_ctrl->tx_priority >= SYSADPT_TX_WMM_QUEUES)) { + /* Need to leave spin_lock since bus driver may + sleep while transferring data + */ + spin_unlock_bh(&priv->tx_desc_lock); + //wiphy_err(priv->hw->wiphy, "[call mwl_tx_do_amsdu]\n"); + tx_skb = mwl_tx_do_amsdu(priv, num, + tx_skb, tx_info); + spin_lock_bh(&priv->tx_desc_lock); + } + + /* get next buffer length */ + + if (tx_skb) { + /* Need to leave spin_lock since bus driver + * may sleep while transferring data + */ + spin_unlock_bh(&priv->tx_desc_lock); + if (mwl_tx_available(priv, num)) + { + // wiphy_err(priv->hw->wiphy, "[call mwl_tx_skb1]\n"); + mwl_tx_skb(priv, num, tx_skb); + } + else { + /* skb consumed. Marking as NULL. This code is not + * intended to be executed. + * If it gets executed, following will + * happen - tx_skb can contain an amsdu, which will get + * added to queue head updating the queue length and the + * while () loop will never terminate... + */ + skb_queue_head(&priv->txq[num], tx_skb); + } + spin_lock_bh(&priv->tx_desc_lock); + } + + if (skb_queue_len(&priv->txq[num]) == 0) { + spin_lock(&priv->sta_lock); + + list_for_each_entry(sta_info, &priv->sta_list, list) { + + spin_lock_bh(&sta_info->amsdu_lock); + + if (sta_info->amsdu_ctrl.frag[num].num) { + if (mwl_tx_available(priv, num)) { + sta_info->amsdu_ctrl.frag[num].num = 0; + sta_info->amsdu_ctrl.frag[num].cur_pos = NULL; + spin_unlock_bh(&sta_info->amsdu_lock); + spin_unlock(&priv->sta_lock); + spin_unlock_bh(&priv->tx_desc_lock); + mwl_tx_skb(priv, num, + sta_info->amsdu_ctrl.frag[num].skb); + spin_lock_bh(&priv->tx_desc_lock); + spin_lock(&priv->sta_lock); + spin_lock_bh(&sta_info->amsdu_lock); + } + } + spin_unlock_bh(&sta_info->amsdu_lock); + } + spin_unlock(&priv->sta_lock); + } + + if (skb_queue_len(&priv->txq[num]) < + SYSADPT_TX_WAKE_Q_THRESHOLD) { + int hw_tc = wmm_find_tc_for_txq(priv, num); + int queue; + + queue = SYSADPT_TX_WMM_QUEUES - hw_tc - 1; + + if (ieee80211_queue_stopped(hw, queue)) + { + if (lrd_debug) + wiphy_debug(priv->hw->wiphy, "[WQ%d.%d.%d]\n", queue, num, skb_queue_len(&priv->txq[num])); + ieee80211_wake_queue(hw, queue); + } + } + } + spin_unlock_bh(&priv->tx_desc_lock); + wake_up(&priv->tx_flush_wq); +} +EXPORT_SYMBOL_GPL(mwl_tx_skbs); + +void mwl_tx_del_sta_amsdu_pkts(struct ieee80211_sta *sta) +{ + struct mwl_sta *sta_info = mwl_dev_get_sta(sta); + int num; + struct mwl_amsdu_frag *amsdu_frag; + struct ieee80211_tx_info *tx_info; + struct mwl_tx_ctrl *tx_ctrl; + struct sk_buff_head *amsdu_pkts; + + spin_lock_bh(&sta_info->amsdu_lock); + for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) { + amsdu_frag = &sta_info->amsdu_ctrl.frag[num]; + if (amsdu_frag->num) { + amsdu_frag->num = 0; + amsdu_frag->cur_pos = NULL; + if (amsdu_frag->skb) { + tx_info = IEEE80211_SKB_CB(amsdu_frag->skb); + tx_ctrl = (struct mwl_tx_ctrl *) + &tx_info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + skb_queue_purge(amsdu_pkts); + kfree(amsdu_pkts); + } + dev_kfree_skb_any(amsdu_frag->skb); + } + } + } + spin_unlock_bh(&sta_info->amsdu_lock); +} + +bool mwl_tx_pending(struct ieee80211_hw *hw) +{ + struct mwl_priv *priv = hw->priv; + int num = -1; + bool ret = false; + + spin_lock_bh(&priv->tx_desc_lock); + for (num = SYSADPT_TX_WMM_QUEUES - 1; num >= 0; num--) { + if (skb_queue_len(&priv->txq[num]) > 0) { + ret = true; + break; + } + } + spin_unlock_bh(&priv->tx_desc_lock); + + return ret; +} + diff --git a/drivers/net/wireless/laird/lrdmwl/tx.h b/drivers/net/wireless/laird/lrdmwl/tx.h new file mode 100644 index 0000000000000..4105065c47230 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/tx.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2006-2017, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +/* Description: This file defines transmit related functions. */ + +#ifndef _TX_H_ +#define _TX_H_ + +/* Tx Rate to be indicated to mac80211 - For KF2 PCIe & SDIO, +** driver has no way of knowing the rate at which the pkt was Tx'ed. +** Use hardcoded max value for this +*/ + +/* VHT/2SS/BW80/MCS7/SGI */ +#define TX_COMP_RATE_FOR_DATA ((7 << MWL_TX_RATE_RATEIDMCS_SHIFT) |\ + (TX_RATE_INFO_SHORT_GI << MWL_TX_RATE_SHORTGI_SHIFT) |\ + (TX_RATE_BANDWIDTH_80 << MWL_TX_RATE_BANDWIDTH_SHIFT) |\ + (2 << MWL_TX_RATE_NSS_SHIFT) |\ + TX_RATE_FORMAT_11AC); + + +int mwl_tx_init(struct ieee80211_hw *hw); +void mwl_tx_deinit(struct ieee80211_hw *hw); +void mwl_tx_xmit(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +void mwl_tx_del_pkts_via_vif(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +void mwl_tx_del_pkts_via_sta(struct ieee80211_hw *hw, + struct ieee80211_sta *sta); +void mwl_tx_del_ampdu_pkts(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 tid); + +void mwl_tx_del_sta_amsdu_pkts(struct ieee80211_sta *sta); +void mwl_tx_skbs(unsigned long data); +void mwl_tx_skb(struct mwl_priv *priv, int desc_num, + struct sk_buff *tx_skb); +void mwl_tx_done(unsigned long data); +void mwl_pfu_tx_done(unsigned long data); +void mwl_tx_flush_amsdu(unsigned long data); +void mwl_tx_ack_amsdu_pkts(struct ieee80211_hw *hw, u32 rate, + struct sk_buff_head *amsdu_pkts); +void mwl_tx_prepare_info(struct ieee80211_hw *hw, u32 rate, + struct ieee80211_tx_info *info); + +void wmm_init_tc_to_txq_mapping(struct mwl_priv *priv); + +bool mwl_tx_pending(struct ieee80211_hw *hw); + +#endif /* _TX_H_ */ diff --git a/drivers/net/wireless/laird/lrdmwl/usb.c b/drivers/net/wireless/laird/lrdmwl/usb.c new file mode 100644 index 0000000000000..85c9d25d887b3 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/usb.c @@ -0,0 +1,1466 @@ +/* + * Marvell Wireless LAN device driver: USB specific handling + * + * Copyright (C) 2012-2014, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "sysadpt.h" +#include "usb.h" +#include "dev.h" +#include "main.h" +#include "fwcmd.h" +#include "tx.h" +#include "rx.h" +#include +#include +#include +#include + +#define MWL_USBDRV_VERSION "1.0-20171201" +#define LRD_USB_VERSION LRD_BLD_VERSION "-" MWL_USBDRV_VERSION +#define LRD_USB_DESC "Laird 60 Series Wireless USB Network Driver" + +static struct mwl_if_ops usb_ops1; +#define INTF_HEADER_LEN 4 + +/* 88W8997 datasheet requires PMIC_EN/PMU_EN to remain de-asserted for a minimum of 100ms */ +#define USB_DEFAULT_POWERDOWN_DELAY_MS 100 + + +static const struct usb_device_id mwl_usb_table[] = { + /* 8997 */ + {USB_DEVICE(USB8XXX_VID, USB8997_PID_1)}, + {USB_DEVICE_AND_INTERFACE_INFO(USB8XXX_VID, USB8997_PID_2, + USB_CLASS_VENDOR_SPEC, + USB_SUBCLASS_VENDOR_SPEC, 0xff)}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, mwl_usb_table); + +static const struct of_device_id mwl_usb_of_match_table[] = { + { .compatible = "usb1286,204e" }, + { .compatible = "usbif1286,204e.config1.2" }, + { } +}; + + +static unsigned int reset_pwd_gpio = ARCH_NR_GPIOS; + + +static struct mwl_chip_info mwl_chip_tbl[] = { + [MWL8997] = { + .part_name = "88W8997", + .fw_image = MWL_FW_ROOT"/88W8997_usb.bin", + .mfg_image = MWL_FW_ROOT"/88W8997_usb_mfg.bin", + .antenna_tx = ANTENNA_TX_2, + .antenna_rx = ANTENNA_RX_2, + }, +}; + + +#define WARNING 3 +#define MSG 5 +#define INFO 5 +#define CMD 0x10 +#define DATA 0x8 +#define EVENT 0x20 + +#define MWIFIEX_TYPE_LEN 4 +#define MWIFIEX_USB_TYPE_CMD 0xF00DFACE +#define MWIFIEX_USB_TYPE_DATA 0xBEADC0DE +#define MWIFIEX_USB_TYPE_EVENT 0xBEEFFACE + +static int mwl_usb_submit_rx_urb(struct urb_context *ctx); +static void mwl_usb_free(struct usb_card_rec *card); +static int mwl_usb_restart_handler(struct mwl_priv *priv); +/* This function probes an mwl device and registers it. It allocates + * the card structure, initiates the device registration and initialization + * procedure by adding a logical interface. + */ +static int mwl_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *iface_desc = intf->cur_altsetting; + struct usb_endpoint_descriptor *epd; + int gpio, rc, i; + struct usb_card_rec *card; + u16 id_vendor, id_product, bcd_device; + struct device_node *of_node = NULL; + enum of_gpio_flags flags; + + card = devm_kzalloc(&intf->dev, sizeof(*card), GFP_KERNEL); + if (!card) { + pr_err("allocate usb_card_rec structure failed\n"); + return -ENOMEM; + } + + init_completion(&card->fw_done); + + id_vendor = le16_to_cpu(udev->descriptor.idVendor); + id_product = le16_to_cpu(udev->descriptor.idProduct); + bcd_device = le16_to_cpu(udev->descriptor.bcdDevice); + pr_debug("info: VID/PID = %X/%X, Boot2 version = %X\n", + id_vendor, id_product, bcd_device); + + card->chip_type = MWL8997; + + /* PID_1 is used for firmware downloading only */ + switch (id_product) { + case USB8997_PID_1: + card->usb_boot_state = USB8XXX_FW_DNLD; + break; + case USB8997_PID_2: + card->usb_boot_state = USB8XXX_FW_READY; + break; + default: + pr_warn("unknown id_product %#x\n", id_product); + card->usb_boot_state = USB8XXX_FW_DNLD; + break; + } + + card->udev = udev; + card->intf = intf; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + epd = &iface_desc->endpoint[i].desc; + if (usb_endpoint_dir_in(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_CMD_EVENT && + (usb_endpoint_xfer_bulk(epd) || + usb_endpoint_xfer_int(epd))) { + + card->rx_cmd_ep_type = usb_endpoint_type(epd); + card->rx_cmd_interval = epd->bInterval; + card->rx_cmd_ep = usb_endpoint_num(epd); + + pr_debug("info: Rx CMD/EVT:: max pkt size: %d, addr: %d, ep_type: %d\n", + le16_to_cpu(epd->wMaxPacketSize), epd->bEndpointAddress, card->rx_cmd_ep_type); + } + + if (usb_endpoint_dir_in(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_DATA && + usb_endpoint_xfer_bulk(epd)) { + + card->rx_data_ep = usb_endpoint_num(epd); + + pr_debug("info: bulk IN: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), epd->bEndpointAddress); + } + + if (usb_endpoint_dir_out(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_DATA && + usb_endpoint_xfer_bulk(epd)) { + + card->port.tx_data_ep = usb_endpoint_num(epd); + atomic_set(&card->port.tx_data_urb_pending, 0); + + pr_debug("info: bulk OUT 0: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), epd->bEndpointAddress); + } + + if (usb_endpoint_dir_out(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_DATA_CH2 && + usb_endpoint_xfer_bulk(epd)) { + + pr_debug("info: bulk OUT chan2 (not supported):\t max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), epd->bEndpointAddress); + } + + if (usb_endpoint_dir_out(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_CMD_EVENT && + (usb_endpoint_xfer_bulk(epd) || + usb_endpoint_xfer_int(epd))) { + + card->tx_cmd_ep_type = usb_endpoint_type(epd); + card->tx_cmd_interval = epd->bInterval; + card->tx_cmd_ep = usb_endpoint_num(epd); + card->bulk_out_maxpktsize = le16_to_cpu(epd->wMaxPacketSize); + + pr_debug("info: bulk OUT: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), epd->bEndpointAddress); + } + } + + usb_set_intfdata(intf, card); + memcpy(&usb_ops1.mwl_chip_tbl, &mwl_chip_tbl[card->chip_type], + sizeof(struct mwl_chip_info)); + + // Initialize reset gpio to value provided by mod parameter if it exists + card->reset_pwd_gpio = reset_pwd_gpio; + flags = 0; + + /* device tree node parsing and platform specific configuration */ + if (dev_of_node(&intf->dev)) { + if (of_match_node(mwl_usb_of_match_table, dev_of_node(&intf->dev))) { + of_node = dev_of_node(&intf->dev); + + // Override gpio reset with value provided in devicetree if it exists + gpio = of_get_named_gpio_flags(of_node, "pmu-en-gpios", 0, &flags); + if (!gpio_is_valid(gpio)) + gpio = of_get_named_gpio_flags(of_node, "reset-gpios", 0, &flags); + + if (gpio_is_valid(gpio)) + card->reset_pwd_gpio = gpio; + } + } + + if (gpio_is_valid(card->reset_pwd_gpio)) { + rc = devm_gpio_request_one(&intf->dev, card->reset_pwd_gpio, + flags | GPIOF_OUT_INIT_HIGH, "wifi_pmu_en"); + if (!rc) { + pr_info("PMU_EN GPIO %d configured\n", card->reset_pwd_gpio); + usb_ops1.hardware_restart = mwl_usb_restart_handler; + } + else + pr_err("Failed to configure PMU_EN gpio %d!!\n", card->reset_pwd_gpio); + } + else + pr_info("PMU_EN GPIO not configured\n"); + + rc = mwl_add_card(card, &usb_ops1, of_node); + + if (rc) { + if (rc != -EINPROGRESS) + pr_err("%s: Failed to add card: %d\n", __func__, rc); + goto err_add_card; + } + + usb_get_dev(udev); + + return 0; + +err_add_card: + mwl_usb_free(card); + return rc; +} + +static void mwl_usb_free(struct usb_card_rec *card) +{ + struct usb_tx_data_port *port; + struct mwl_priv * priv = card->priv; + int i; + + if (card->rx_cmd.urb) { + usb_kill_urb(card->rx_cmd.urb); + usb_free_urb(card->rx_cmd.urb); + card->rx_cmd.urb = NULL; + } + + if (card->rx_cmd.skb) { + dev_kfree_skb_any(card->rx_cmd.skb); + card->rx_cmd.skb = NULL; + } + + if(likely(!priv->mfg_mode) && card->rx_data_list[0].urb) { + usb_kill_anchored_urbs(&card->rx_urb_anchor); + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) { + if (card->rx_data_list[i].urb) { + usb_free_urb(card->rx_data_list[i].urb); + card->rx_data_list[i].urb = NULL; + } + if (card->rx_data_list[i].skb) { + dev_kfree_skb_any(card->rx_data_list[i].skb); + card->rx_data_list[i].skb = NULL; + } + } + + port = &card->port; + for (i = 0; i < MWIFIEX_TX_DATA_URB; i++) { + if (port->tx_data_list[i].urb) { + // Note - only urbs that had been submitted have skbs + // skb is free'd in tx complete handler when urb is killed + usb_kill_urb(port->tx_data_list[i].urb); + usb_free_urb(port->tx_data_list[i].urb); + port->tx_data_list[i].urb = NULL; + } + } + } + + if (card->tx_cmd.urb) { + usb_kill_urb(card->tx_cmd.urb); + usb_free_urb(card->tx_cmd.urb); + card->tx_cmd.urb = NULL; + } + + return; +} + + +static int mwl_usb_resume(struct usb_interface *intf) +{ + return 0; +} + +static int mwl_usb_suspend(struct usb_interface *intf,pm_message_t message) +{ + return 0; +} + +static void mwl_usb_disconnect(struct usb_interface *intf) +{ + struct usb_card_rec *card = usb_get_intfdata(intf); + struct mwl_priv *adapter; + + /*TODO wait_for_completion(&card->fw_done);*/ + adapter = card->priv; + + mwl_wl_deinit(adapter); + + /*TODO : deauthenticate and shutdown firmware*/ + + card->dying = true; + + mwl_usb_free(card); + + /* TODO mwl_remove_card(adapter);*/ + + if (adapter->if_ops.ptx_task != NULL) + tasklet_kill(adapter->if_ops.ptx_task); + + tasklet_kill(&adapter->rx_task); + + usb_put_dev(interface_to_usbdev(intf)); +} + +static struct usb_driver mwl_usb_driver = { + .name = MWL_DRV_NAME, + .probe = mwl_usb_probe, + .disconnect = mwl_usb_disconnect, + .id_table = mwl_usb_table, + .suspend = mwl_usb_suspend, + .resume = mwl_usb_resume, + .soft_unbind = 1, +}; + + + +static bool mwl_usb_check_card_status(struct mwl_priv *priv) +{ + return true; +} + +/* + Packet format (sdio interface): + [TYPE:4][mwl_rx_desc:44][mwl_dma_data:32][payload wo 802.11 header] +*/ +void mwl_handle_rx_packet(struct mwl_priv *priv, struct sk_buff *skb) +{ + struct ieee80211_hw *hw = priv->hw; + struct mwl_rx_desc *pdesc; + struct mwl_dma_data *dma; + struct sk_buff *prx_skb = skb; + struct ieee80211_rx_status status; + struct mwl_vif *mwl_vif = NULL; + struct ieee80211_hdr *wh; + struct mwl_rx_event_data *rx_evnt; + + pdesc = (struct mwl_rx_desc *)prx_skb->data; + + /* => todo: + // Save the rate info back to card + //card->rate_info = pdesc->rate; + //=> rateinfo-- + */ + if (pdesc->payldType == RX_PAYLOAD_TYPE_EVENT_INFO) { + skb_pull(prx_skb, sizeof(struct mwl_rx_desc)); + rx_evnt = (struct mwl_rx_event_data *)prx_skb->data; + mwl_handle_rx_event(hw, rx_evnt); + dev_kfree_skb_any(prx_skb); + return; + } + + if ((pdesc->channel != hw->conf.chandef.chan->hw_value) && + !(priv->roc.tmr_running && priv->roc.in_progress && + (pdesc->channel == priv->roc.chan))) { + dev_kfree_skb_any(prx_skb); +// wiphy_debug(priv->hw->wiphy, +// "<= %s(), not accepted channel (%d, %d)\n", __func__, +// pdesc->channel, hw->conf.chandef.chan->hw_value); + return; + } + + mwl_rx_prepare_status(pdesc, &status); + priv->noise = pdesc->noise_floor; + + skb_pull(prx_skb, sizeof(struct mwl_rx_desc)); + dma = (struct mwl_dma_data *)prx_skb->data; + wh = &dma->wh; + + if (ieee80211_has_protected(wh->frame_control)) { + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + if (ieee80211_has_tods(wh->frame_control)) + { + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr1); + if (!mwl_vif && ieee80211_has_a4(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv,wh->addr2); + } + else if (ieee80211_has_fromds(wh->frame_control)) + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr2); + else + mwl_vif = mwl_rx_find_vif_bss(priv, wh->addr3); + + if (mwl_vif && mwl_vif->is_hw_crypto_enabled) { + /* When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwlwifi Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + memset((void *)&dma->data, 0, 4); + skb_put(prx_skb, 4); + /* IV is stripped in this case + * Indicate that, so mac80211 doesn't attempt to decrypt the + * packet and fail prior to handling the MMIC error indication + */ + status.flag |= RX_FLAG_IV_STRIPPED; + } + + if (!ieee80211_is_auth(wh->frame_control)) + /* For WPA2 frames, AES header/MIC are + ** present to enable mac80211 to check + ** for replay attacks + */ + status.flag |= RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + /* + Remove the DMA header (dma->fwlen) + */ + mwl_rx_remove_dma_header(prx_skb, pdesc->qos_ctrl); + + /* Update the pointer of wifi header, + which may be different after mwl_rx_remove_dma_header() + */ + wh = (struct ieee80211_hdr *)prx_skb->data; + +#if 0 //def CONFIG_MAC80211_MESH + if (ieee80211_is_data_qos(wh->frame_control) && + ieee80211_has_a4(wh->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(wh); + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + if (mwl_rx_process_mesh_amsdu(priv, prx_skb, + &status)) + return; + } +#endif + memcpy(IEEE80211_SKB_RXCB(prx_skb), &status, sizeof(status)); + + /* Packet to indicate => Will indicate AMPDU/AMSDU packets */ + mwl_rx_upload_pkt(hw, prx_skb); + + return; +} + + +static void mwl_usb_flush_rx_urb_queue(struct mwl_priv *priv) +{ + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + struct urb_context * rx_urb, * tmp; + struct list_head local; + unsigned long flags; + + if (list_empty(&card->rx_urb_pending_list)) + return; + + INIT_LIST_HEAD(&local); + + spin_lock_irqsave(&card->rx_urb_lock, flags); + list_splice_init(&card->rx_urb_pending_list, &local); + spin_unlock_irqrestore(&card->rx_urb_lock, flags); + + list_for_each_entry_safe(rx_urb, tmp, &local, list) { + list_del_init(&rx_urb->list); + mwl_usb_submit_rx_urb(rx_urb); + } +} + + +static void mwl_usb_rx_recv(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl_priv *priv = hw->priv; + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + struct sk_buff *prx_skb = NULL; + + while (!skb_queue_empty(&card->rx_data_q)) { + prx_skb = skb_dequeue(&card->rx_data_q); + if (prx_skb == NULL) { + break; + } + atomic_dec(&card->rx_pending); + mwl_handle_rx_packet(priv, prx_skb); + + // If we hit the low theshold, empty the rx urb pending queue + if (atomic_read(&card->rx_pending) == LOW_RX_PENDING) + mwl_usb_flush_rx_urb_queue(priv); + } + + return; +} + +static int mwl_usb_init_post(struct mwl_priv *priv) +{ + // Stop Shutdown mode not supported + priv->stop_shutdown = false; + + if (priv->mfg_mode) { + // Assume ST 60 with one interface + priv->radio_caps.capability = 0; + priv->radio_caps.num_mac = 1; + } + + return 0; +} + +static int mwl_usb_init(struct mwl_priv *priv) +{ + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + int num; + + card->priv = priv; + priv->host_if = MWL_IF_USB; + + priv->dev = &card->udev->dev; + priv->chip_type = card->chip_type; + priv->pcmd_buf = kzalloc(CMD_BUF_SIZE, GFP_KERNEL); + + if (!priv->pcmd_buf) { + wiphy_err(priv->hw->wiphy, + "%s: cannot alloc memory for command buffer\n", + MWL_DRV_NAME); + return -ENOMEM; + } + + dev_set_drvdata(&card->udev->dev, priv->hw); + + wiphy_debug(priv->hw->wiphy, "priv->pcmd_buf = %p\n", priv->pcmd_buf); + + memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); + init_waitqueue_head(&card->cmd_wait_q.wait); + card->cmd_wait_q.status = 0; + card->cmd_resp_recvd = false; + card->cmd_id = 0; + + if (priv->mfg_mode) { + return 0; + } + + skb_queue_head_init(&card->rx_data_q); + + /* Init the tasklet first in case there are tx/rx interrupts */ + tasklet_init(&priv->rx_task, (void *)mwl_usb_rx_recv, (unsigned long)priv->hw); + tasklet_disable(&priv->rx_task); + + for (num = 0; num < SYSADPT_NUM_OF_DESC_DATA; num++) + skb_queue_head_init(&priv->txq[num]); + + return 0; +} + +static int mwl_usb_register_dev(struct mwl_priv *priv) +{ + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + + card->priv = priv; + + priv->if_ops.ptx_task = &card->tx_task; + tasklet_init(priv->if_ops.ptx_task, (void *)mwl_tx_skbs, (unsigned long)priv->hw); + tasklet_disable(priv->if_ops.ptx_task); + + return 0; +} + +/* This function handles received packet. Necessary action is taken based on + * cmd/event/data. + */ +static int mwl_usb_recv(struct mwl_priv *adapter, + struct sk_buff *skb, u8 ep) +{ + u32 recv_type; + struct usb_card_rec *card = (struct usb_card_rec *)adapter->intf; + struct sk_buff *newskb; + struct cmd_header cmd_hdr_tmp; + __le32 tmp; + + /*TODO if (adapter->hs_activated) + mwifiex_process_hs_config(adapter);*/ + + if (skb->len < INTF_HEADER_LEN) { + wiphy_err(adapter->hw->wiphy, "%s: invalid skb->len\n", __func__); + return -1; + } + + switch (ep) { + case MWIFIEX_USB_EP_CMD_EVENT: + skb_copy_from_linear_data(skb, &tmp, INTF_HEADER_LEN); + recv_type = le32_to_cpu(tmp); + + switch (recv_type) { + case MWIFIEX_USB_TYPE_CMD: + skb_copy_from_linear_data_offset(skb, INTF_HEADER_LEN, &cmd_hdr_tmp, sizeof(cmd_hdr_tmp)); + + if ((!card->cmd_resp_recvd) && + (le16_to_cpu(cmd_hdr_tmp.command) == (card->cmd_id | HOSTCMD_RESP_BIT))) { + skb_copy_from_linear_data(skb, adapter->pcmd_buf, skb->len); + card->cmd_wait_q.status = 0; + card->cmd_resp_recvd = true; + card->cmd_id = 0; + wake_up(&card->cmd_wait_q.wait); + } + break; + + case MWIFIEX_USB_TYPE_EVENT: + if (skb->len < sizeof(u32)) { + wiphy_err(adapter->hw->wiphy, "EVENT: skb->len too small\n"); + return -1; + } + skb_copy_from_linear_data(skb, &tmp, sizeof(u32)); + wiphy_debug(adapter->hw->wiphy, "event_cause %#x\n", le32_to_cpu(tmp)); + break; + default: + wiphy_err(adapter->hw->wiphy, "unknown recv_type %#x\n", recv_type); + return -1; + } + break; + case MWIFIEX_USB_EP_DATA: + if (skb->len > MWIFIEX_RX_DATA_BUF_SIZE) { + wiphy_err(adapter->hw->wiphy, "DATA: skb->len too large\n"); + return -1; + } + newskb = dev_alloc_skb(skb->len); + if (!newskb) { + wiphy_err(adapter->hw->wiphy, "DATA: skb allocation failure!\n"); + return -1; + } + skb_put_data(newskb, skb->data, skb->len); + skb_pull(newskb, INTF_HEADER_LEN); + skb_queue_tail(&card->rx_data_q, newskb); + atomic_inc(&card->rx_pending); + tasklet_schedule(&adapter->rx_task); + break; + default: + wiphy_err(adapter->hw->wiphy, "%s: unknown endport %#x\n", __func__, ep); + return -1; + } + + return -EINPROGRESS; +} + + +static void mwl_usb_cleanup(struct mwl_priv *adapter) +{ + +} + + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +static int mwl_write_data_complete(struct mwl_priv *priv, + struct sk_buff *skb) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)priv->hw; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_tx_info *info; + struct sk_buff_head *amsdu_pkts; + struct mwl_dma_data *dma_data; + struct ieee80211_hdr *wh; + u8 *data = skb->data; + u32 rate; + + if (skb == NULL) + return 0; + dma_data = (struct mwl_dma_data *) + &data[INTF_HEADER_LEN + sizeof(struct mwl_tx_desc)]; + wh = &dma_data->wh; + info = IEEE80211_SKB_CB(skb); + + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + + if (ieee80211_is_data(wh->frame_control) || + ieee80211_is_data_qos(wh->frame_control)) { + rate = TX_COMP_RATE_FOR_DATA; + tx_ctrl = (struct mwl_tx_ctrl *)&info->status; + amsdu_pkts = (struct sk_buff_head *) + tx_ctrl->amsdu_pkts; + if (amsdu_pkts) { + mwl_tx_ack_amsdu_pkts(hw, rate, amsdu_pkts); + dev_kfree_skb_any(skb); + skb = NULL; + } else + mwl_tx_prepare_info(hw, rate, info); + } else + mwl_tx_prepare_info(hw, 0, info); + + if (skb != NULL) { + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= IEEE80211_TX_STAT_ACK; + + if (ieee80211_is_data(wh->frame_control) || + ieee80211_is_data_qos(wh->frame_control)) { +// wiphy_err(hw->wiphy, "fr_data_skb=%p\n", skb); + } + // USB completion handlers are sometimes called in irq context + ieee80211_tx_status_irqsafe(hw, skb); + } + return 0; +} + + +static void mwl_usb_tx_complete(struct urb *urb) +{ + struct urb_context *context = (struct urb_context *)(urb->context); + struct mwl_priv *priv = context->priv; + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + struct usb_tx_data_port *port; + + if (context->ep != card->tx_cmd_ep) { + if (urb->status == -ENOENT) { + // urb was canceled, free the tx skb + ieee80211_free_txskb(priv->hw, context->skb); + } + else + mwl_write_data_complete(priv, context->skb); + + port = &card->port; + atomic_dec(&port->tx_data_urb_pending); + + if (!priv->recovery_in_progress) + tasklet_schedule(priv->if_ops.ptx_task); + } + + return; +} + +static int mwl_usb_tx_init(struct mwl_priv *adapter) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->intf; + struct usb_tx_data_port *port; + int i; + + card->tx_cmd.priv = adapter; + card->tx_cmd.ep = card->tx_cmd_ep; + + card->tx_cmd.urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->tx_cmd.urb) + return -ENOMEM; + + if(unlikely(adapter->mfg_mode)) + return 0; + + port = &card->port; + + if (!port->tx_data_ep) + return -1; + + port->tx_data_ix = 0; + + for (i = 0; i < MWIFIEX_TX_DATA_URB; i++) { + port->tx_data_list[i].priv = adapter; + port->tx_data_list[i].ep = port->tx_data_ep; + port->tx_data_list[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->tx_data_list[i].urb) + return -ENOMEM; + } + + return 0; +} + +static void mwl_usb_rx_complete(struct urb *urb) +{ + struct urb_context *context = (struct urb_context *)urb->context; + struct mwl_priv *adapter = context->priv; + struct sk_buff *skb = context->skb; + struct usb_card_rec *card; + int recv_length = urb->actual_length; + int status; + unsigned long flags; + + if (!adapter || !adapter->intf) { + pr_err("mwl adapter or card structure is not valid\n"); + return; + } + + card = (struct usb_card_rec *)adapter->intf; + + if (adapter->recovery_in_progress) + return; + + if ((urb->status) || !recv_length) { + // Do not print when urb is killed or unlinked or recovery in progress + if (!adapter->recovery_in_progress) { + if (urb->status && (urb->status != -ENOENT) && (urb->status != -ECONNRESET)) + pr_err("%s: %s URB failed! status %d, length %d\n", __func__, + card->rx_cmd_ep == context->ep ? "command" : "data", + urb->status, recv_length); + } + + goto setup_for_next; + } + + if (recv_length) { + if (skb->len > recv_length) + skb_trim(skb, recv_length); + else + skb_put(skb, recv_length - skb->len); + + status = mwl_usb_recv(adapter, skb, context->ep); + + if (status != -EINPROGRESS) { + if (status == -1) + pr_err("received data processing failed!\n"); + } + } + +setup_for_next: + + switch(urb->status) { + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + goto done; + default: + break; + } + + if (card->dying) + goto done; + + if (atomic_read(&card->rx_pending) > HIGH_RX_PENDING) { + spin_lock_irqsave(&card->rx_urb_lock, flags); + list_add_tail(&context->list, &card->rx_urb_pending_list); + spin_unlock_irqrestore(&card->rx_urb_lock, flags); + } + else + mwl_usb_submit_rx_urb(context); + +done: + return; +} + +static int mwl_usb_submit_rx_urb(struct urb_context *ctx) +{ + struct mwl_priv *adapter = ctx->priv; + struct usb_card_rec *card = (struct usb_card_rec *)adapter->intf; + int size; + + if (card->rx_data_ep == ctx->ep) + size = MWIFIEX_RX_DATA_BUF_SIZE; + else + size = MWIFIEX_RX_CMD_BUF_SIZE; + + if (card->rx_cmd_ep == ctx->ep && + card->rx_cmd_ep_type == USB_ENDPOINT_XFER_INT) + { + usb_fill_int_urb(ctx->urb, card->udev, + usb_rcvintpipe(card->udev, ctx->ep), + ctx->skb->data, size, mwl_usb_rx_complete, + (void *)ctx, card->rx_cmd_interval); + } + else + { + usb_fill_bulk_urb(ctx->urb, card->udev, + usb_rcvbulkpipe(card->udev, ctx->ep), + ctx->skb->data, size, mwl_usb_rx_complete, + (void *)ctx); + } + + if (card->rx_data_ep == ctx->ep) + usb_anchor_urb(ctx->urb, &card->rx_urb_anchor); + + if (usb_submit_urb(ctx->urb, GFP_ATOMIC)) { + wiphy_err(adapter->hw->wiphy, "usb_submit_urb failed\n"); + if (card->rx_data_ep == ctx->ep) + usb_unanchor_urb(ctx->urb); + return -1; + } + + return 0; +} + +static int mwl_usb_rx_init(struct mwl_priv *adapter) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->intf; + int i; + + card->rx_cmd.priv = adapter; + card->rx_cmd.ep = card->rx_cmd_ep; + + card->rx_cmd.urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->rx_cmd.urb) + return -ENOMEM; + + card->rx_cmd.skb = dev_alloc_skb(MWIFIEX_RX_CMD_BUF_SIZE); + if (!card->rx_cmd.skb) + return -ENOMEM; + + if (mwl_usb_submit_rx_urb(&card->rx_cmd)) + return -1; + + if(unlikely(adapter->mfg_mode)) + return 0; + + INIT_LIST_HEAD(&card->rx_urb_pending_list); + spin_lock_init(&card->rx_urb_lock); + init_usb_anchor(&card->rx_urb_anchor); + + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) { + card->rx_data_list[i].priv = adapter; + card->rx_data_list[i].ep = card->rx_data_ep; + card->rx_data_list[i].urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!card->rx_data_list[i].urb) + return -1; + + card->rx_data_list[i].skb = dev_alloc_skb(MWIFIEX_RX_DATA_BUF_SIZE); + if (!card->rx_data_list[i].skb) { + wiphy_err(adapter->hw->wiphy, "%s: dev_alloc_skb failed\n", __func__); + return -ENOMEM; + } + + if (mwl_usb_submit_rx_urb(&card->rx_data_list[i])) + return -1; + } + + return 0; +} + +static int mwl_read_data_sync(struct mwl_priv *priv, u8 *pbuf, + u32 *len, u8 ep, u32 timeout) +{ + struct usb_card_rec *card = priv->intf; + int actual_length, ret; + + /* Receive the data response */ + ret = usb_bulk_msg(card->udev, usb_rcvbulkpipe(card->udev, ep), pbuf, + *len, &actual_length, timeout); + if (ret) { + wiphy_err(priv->hw->wiphy, "usb_bulk_msg for rx failed: %d\n", ret); + return ret; + } + + *len = actual_length; + + return ret; +} + +static int mwl_write_data_sync(struct mwl_priv *priv, u8 *pbuf, + u32 *len, u8 ep, u32 timeout) +{ + struct usb_card_rec *card = priv->intf; + int actual_length, ret; + + if (!(*len % card->bulk_out_maxpktsize)) + (*len)++; + + /* Send the data block */ + ret = usb_bulk_msg(card->udev, usb_sndbulkpipe(card->udev, ep), pbuf, + *len, &actual_length, timeout); + if (ret) { + wiphy_err(priv->hw->wiphy,"usb_bulk_msg for tx failed: %d\n", ret); + return ret; + } + + *len = actual_length; + + return ret; +} + +static int mwl_prog_fw_w_helper(struct mwl_priv * priv) +{ + int ret = 0; + const u8 *firmware = priv->fw_ucode->data; + u8 *recv_buff = NULL; + u32 retries = USB8XXX_FW_MAX_RETRY + 1; + u32 dlen; + u32 fw_seqnum = 0, tlen = 0, dnld_cmd = 0; + struct fw_data *fwdata = NULL; + struct fw_sync_header sync_fw; + u8 check_winner = 1; + + if (!firmware) { + wiphy_err(priv->hw->wiphy, "No firmware image found! Terminating download\n"); + ret = -1; + goto fw_exit; + } + + /* Allocate memory for transmit */ + fwdata = kzalloc(FW_DNLD_TX_BUF_SIZE, GFP_KERNEL); + if (!fwdata) { + ret = -ENOMEM; + goto fw_exit; + } + + /* Allocate memory for receive */ + recv_buff = kzalloc(FW_DNLD_RX_BUF_SIZE, GFP_KERNEL); + if (!recv_buff) { + ret = -ENOMEM; + goto cleanup; + } + + do { + /* Send pseudo data to check winner status first */ + if (check_winner) { + memset(&fwdata->fw_hdr, 0, sizeof(struct fw_header)); + dlen = 0; + } else { + /* copy the header of the fw_data to get the length */ + memcpy(&fwdata->fw_hdr, &firmware[tlen], sizeof(struct fw_header)); + + dlen = le32_to_cpu(fwdata->fw_hdr.data_len); + dnld_cmd = le32_to_cpu(fwdata->fw_hdr.dnld_cmd); + tlen += sizeof(struct fw_header); + + /* Command 7 doesn't have data length field */ + if (dnld_cmd == FW_CMD_7) + dlen = 0; + + if (dlen > (FW_DNLD_TX_BUF_SIZE - (sizeof(struct fw_data) - 1))) + { + pr_err("Firmware data length chunk (%d bytes) too large!\n", dlen); + ret = -1; + goto cleanup; + } + + memcpy(fwdata->data, &firmware[tlen], dlen); + + fwdata->seq_num = cpu_to_le32(fw_seqnum); + tlen += dlen; + } + + /* If the send/receive fails or CRC occurs then retry */ + while (--retries) { + u8 *buf = (u8 *)fwdata; + u32 len = FW_DATA_XMIT_SIZE; + + /* send the firmware block */ + ret = mwl_write_data_sync(priv, buf, &len, + MWIFIEX_USB_EP_CMD_EVENT, + MWIFIEX_USB_TIMEOUT); + if (ret) { + wiphy_err(priv->hw->wiphy, "write_data_sync: failed: %d\n", ret); + continue; + } + + buf = recv_buff; + len = FW_DNLD_RX_BUF_SIZE; + + /* Receive the firmware block response */ + ret = mwl_read_data_sync(priv, buf, &len, + MWIFIEX_USB_EP_CMD_EVENT, + MWIFIEX_USB_TIMEOUT); + if (ret) { + wiphy_err(priv->hw->wiphy, "read_data_sync: failed: %d\n", ret); + continue; + } + + memcpy(&sync_fw, recv_buff, + sizeof(struct fw_sync_header)); + + /* check 1st firmware block resp for highest bit set */ + if (check_winner) { + if (le32_to_cpu(sync_fw.cmd) & 0x80000000) { + wiphy_info(priv->hw->wiphy, "USB is not the winner %#x\n", sync_fw.cmd); + + /* returning success */ + ret = 0; + goto cleanup; + } + + wiphy_info(priv->hw->wiphy, "start to download FW...\n"); + + check_winner = 0; + break; + } + + /* check the firmware block response for CRC errors */ + if (sync_fw.cmd) { + wiphy_err(priv->hw->wiphy, "FW received block with CRC %#x\n", sync_fw.cmd); + ret = -1; + continue; + } + + retries = USB8XXX_FW_MAX_RETRY + 1; + break; + } + fw_seqnum++; + } while ((dnld_cmd != FW_HAS_LAST_BLOCK) && retries); + +cleanup: + wiphy_info(priv->hw->wiphy, "info: FW download over, size %d bytes, ret %d\n", tlen, ret); + + if (recv_buff) + kfree(recv_buff); + + if (fwdata) + kfree(fwdata); + +fw_exit: + return ret; +} + + +static int mwl_usb_dnld_fw(struct mwl_priv *priv) +{ + int ret; + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + + if (card->usb_boot_state == USB8XXX_FW_DNLD) { + ret = mwl_prog_fw_w_helper(priv); + if (ret) { + return -1; + } + + /* Boot state changes after successful firmware download */ + wiphy_info(priv->hw->wiphy, "Firmware download complete, port will reset with new interface...\n"); + return -EINPROGRESS; + } + else + wiphy_info(priv->hw->wiphy, "Skipping FW download, continuing with initialization...\n"); + + ret = mwl_usb_rx_init(priv); + if (!ret) { + ret = mwl_usb_tx_init(priv); + } + + return ret; +} + +static int mwl_usb_send_cmd(struct mwl_priv * priv) +{ + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + struct urb_context *context = NULL; + int len; + int ret; + struct urb *tx_urb; + + __le32 *pbuf = (__le32 *)priv->pcmd_buf; + struct cmd_header *cmd_hdr = (struct cmd_header *)&priv->pcmd_buf[ + INTF_CMDHEADER_LEN(INTF_HEADER_LEN)]; + +/* TODO if (card->is_suspended) { + wiphy_err(priv->hw->wiphy, "%s: not allowed while suspended\n", __func__); + return -1; + } +*/ +/* TODO if (adapter->surprise_removed) { + wiphy_err(priv->hw->wiphy, "%s: device removed\n", __func__); + return -1; + } +*/ + + // Single context available for tx command urb + // Commands are synchronous and protected by fwcmd mutex + context = &card->tx_cmd; + tx_urb = context->urb; + len = le16_to_cpu(cmd_hdr->len) + + INTF_CMDHEADER_LEN(INTF_HEADER_LEN)*sizeof(unsigned short); + + *pbuf=cpu_to_le32(MWIFIEX_USB_TYPE_CMD); + + if (card->tx_cmd_ep_type == USB_ENDPOINT_XFER_INT) + { + usb_fill_int_urb(tx_urb, card->udev, + usb_sndintpipe(card->udev,card->tx_cmd_ep), + priv->pcmd_buf, + len, mwl_usb_tx_complete, + (void *)context, card->tx_cmd_interval); + } + else + usb_fill_bulk_urb(tx_urb, card->udev, + usb_sndbulkpipe(card->udev, card->tx_cmd_ep), + priv->pcmd_buf, len, + mwl_usb_tx_complete, (void *)context); + + tx_urb->transfer_flags |= URB_ZERO_PACKET; + card->cmd_resp_recvd = false; + card->cmd_id = (u16)(le16_to_cpu(cmd_hdr->command) & ~HOSTCMD_RESP_BIT); + + ret = usb_submit_urb(tx_urb, GFP_ATOMIC); + if (ret) { + wiphy_err(priv->hw->wiphy, "%s: usb_submit_urb failed\n", __func__); + } + + return ret; +} + +static int mwl_usb_cmd_resp_wait_completed(struct mwl_priv *priv, + unsigned short cmd) +{ + struct usb_card_rec *card = (struct usb_card_rec *)priv->intf; + int status; + + /* Wait for completion */ + status = wait_event_timeout(card->cmd_wait_q.wait, + (card->cmd_resp_recvd == true), + (12 * HZ)); + if (status == 0) { + wiphy_err(priv->hw->wiphy, "timeout, cmd_wait_q terminated: %d\n", status); + + card->cmd_wait_q.status = -ETIMEDOUT; + } + + return card->cmd_wait_q.status; +} + + +/* + * Adds TxPD to AMSDU header. + * + * Each AMSDU packet will contain one TxPD at the beginning, + * followed by multiple AMSDU subframes. + */ +static int +mwl_process_txdesc(struct mwl_priv *priv, + struct sk_buff *skb) +{ + struct mwl_tx_desc *tx_desc; + struct mwl_tx_ctrl *tx_ctrl; + struct ieee80211_tx_info *tx_info; + u8 *ptr; + u32* head; + int size_needed; + + tx_info = IEEE80211_SKB_CB(skb); + tx_ctrl = (struct mwl_tx_ctrl *)&IEEE80211_SKB_CB(skb)->status; + + size_needed = sizeof(struct mwl_tx_desc) + INTF_HEADER_LEN; + + /* existing pointers into skb buffer may not be valid after this call */ + if (skb_cow_head(skb, size_needed)) { + WARN_ON(skb_headroom(skb) < (size_needed)); + return -ENOMEM; + } + + ptr = (u8 *)skb->data; + + skb_push(skb, sizeof(struct mwl_tx_desc)); + tx_desc = (struct mwl_tx_desc *) skb->data; + memset(tx_desc, 0, sizeof(struct mwl_tx_desc)); + + skb_push(skb, INTF_HEADER_LEN); + head =(u32 *)skb->data; + *head = cpu_to_le32(MWIFIEX_USB_TYPE_DATA); + tx_desc->tx_priority = tx_ctrl->tx_priority; + tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl); + tx_desc->pkt_len = cpu_to_le16(skb->len); + + if (tx_info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_DONT_ENCRYPT); + } + + if (tx_info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) { + tx_desc->flags |= cpu_to_le32(MWL_TX_WCB_FLAGS_NO_CCK_RATE); + } + + tx_desc->packet_info = 0; + tx_desc->data_rate = 0; + tx_desc->type = tx_ctrl->type; + tx_desc->xmit_control = tx_ctrl->xmit_control; + tx_desc->sap_pkt_info = 0; + tx_desc->pkt_ptr = cpu_to_le32((u8 *)skb->data - ptr); + tx_desc->status = 0; + + return 0; +} + +static int mwl_usb_host_to_card(struct mwl_priv *priv, int desc_num, + struct sk_buff *tx_skb) +{ + struct urb_context *context = NULL; + struct usb_tx_data_port *port = NULL; + u8 ep = MWIFIEX_USB_EP_DATA; + struct urb *tx_urb; + int ret; + + struct usb_card_rec *card = (struct usb_card_rec *) (priv->intf); + +/* TODO if (card->is_suspended) { + wiphy_err(priv->hw->wiphy, "%s: not allowed while suspended\n", __func__); + return -1; + } +*/ +/* TODO if (adapter->surprise_removed) { + wiphy_err(priv->hw->wiphy, "%s: device removed\n", __func__); + return -1; + } +*/ + + if (priv->recovery_in_progress) { + ret = -1; + goto done; + } + + /* Existing pointers into skb buffer are not valid after this call */ + ret = mwl_process_txdesc(priv,tx_skb); + if (ret) { + wiphy_err(priv->hw->wiphy, "%s: Failed to send packet! ret = %d\n", __func__, ret); + dev_kfree_skb_any(tx_skb); + goto done; + } + + port = &card->port; + if (atomic_read(&port->tx_data_urb_pending) >= MWIFIEX_TX_DATA_URB) { + ret = -EBUSY; + goto done; + } + if (port->tx_data_ix >= MWIFIEX_TX_DATA_URB) + port->tx_data_ix = 0; + context = &port->tx_data_list[port->tx_data_ix++]; + + context->priv = priv; + context->ep = ep; + context->skb = tx_skb; + tx_urb = context->urb; + + usb_fill_bulk_urb(tx_urb, card->udev, usb_sndbulkpipe(card->udev, ep), + tx_skb->data, tx_skb->len, mwl_usb_tx_complete, + (void *)context); + + tx_urb->transfer_flags |= URB_ZERO_PACKET; + + atomic_inc(&port->tx_data_urb_pending); + + if (usb_submit_urb(tx_urb, GFP_ATOMIC)) { + wiphy_err(priv->hw->wiphy, "%s: usb_submit_urb failed\n", __func__); + atomic_dec(&port->tx_data_urb_pending); + if (port->tx_data_ix) + port->tx_data_ix--; + else + port->tx_data_ix = MWIFIEX_TX_DATA_URB; + + ret =-1; + goto done; + } + + ret = -EINPROGRESS; + +done: + return ret; +} + +static bool mwl_usb_is_tx_available(struct mwl_priv *priv, int desc_num) +{ + struct usb_tx_data_port *port = NULL; + struct usb_card_rec *card = (struct usb_card_rec *) (priv->intf); + + if (priv->recovery_in_progress) + return false; + + port = &card->port; + if (atomic_read(&port->tx_data_urb_pending) >= MWIFIEX_TX_DATA_URB) { + return false; + } + + return true; +} + +static void mwl_usb_enter_deepsleep(struct mwl_priv *priv) +{ + return; +} + +static int mwl_usb_wakeup_card(struct mwl_priv *priv) +{ + return 0; +} + +static int mwl_usb_is_deepsleep(struct mwl_priv * priv) +{ + return 0; +} + +static int mwl_usb_restart_handler(struct mwl_priv *priv) +{ + struct usb_card_rec *card = priv->intf; + + if (!gpio_is_valid(card->reset_pwd_gpio)) + return -ENOSYS; + + gpio_set_value(card->reset_pwd_gpio, 0); + msleep(USB_DEFAULT_POWERDOWN_DELAY_MS); + gpio_set_value(card->reset_pwd_gpio, 1); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int mwl_usb_dbg_info (struct mwl_priv *priv, char *p, int size, int len) +{ + struct usb_card_rec *card = priv->intf; + + if (gpio_is_valid(card->reset_pwd_gpio)) { + len += scnprintf(p + len, size - len, "PMU_EN gpio: %d\n", card->reset_pwd_gpio); + } + return len; +} +#endif + + +static struct mwl_if_ops usb_ops1 = { + .inttf_head_len = INTF_HEADER_LEN, + .register_dev = mwl_usb_register_dev, + .cleanup_if = mwl_usb_cleanup, + .prog_fw = mwl_usb_dnld_fw, + .init_if = mwl_usb_init, + .init_if_post = mwl_usb_init_post, + .check_card_status = mwl_usb_check_card_status, + .send_cmd = mwl_usb_send_cmd, + .cmd_resp_wait_completed = mwl_usb_cmd_resp_wait_completed, + .host_to_card = mwl_usb_host_to_card, + .is_tx_available = mwl_usb_is_tx_available, + //Tasklets are assigned per instance during device registration + .ptx_task = NULL, + .enter_deepsleep = mwl_usb_enter_deepsleep, + .wakeup_card = mwl_usb_wakeup_card, + .is_deepsleep = mwl_usb_is_deepsleep, +#ifdef CONFIG_DEBUG_FS + .dbg_info = mwl_usb_dbg_info, +#endif +}; + +module_usb_driver(mwl_usb_driver); + +#ifdef CONFIG_GPIOLIB +module_param(reset_pwd_gpio, uint, 0644); +MODULE_PARM_DESC(reset_pwd_gpio, "WIFI CHIP_PWD reset pin GPIO (deprecated)"); +#endif + + +MODULE_AUTHOR(LRD_AUTHOR); +MODULE_DESCRIPTION(LRD_USB_DESC); +MODULE_VERSION(LRD_USB_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/laird/lrdmwl/usb.h b/drivers/net/wireless/laird/lrdmwl/usb.h new file mode 100644 index 0000000000000..09f34cd269706 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/usb.h @@ -0,0 +1,190 @@ +/* + * This file contains definitions for mwifiex USB interface driver. + * + * Copyright (C) 2012-2014, Marvell International Ltd. + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _LRDMWL_USB_H +#define _LRDMWL_USB_H + +#include +#include +#include +#include + +#define USB8XXX_VID 0x1286 + +#define USB8766_PID_1 0x2041 +#define USB8766_PID_2 0x2042 +#define USB8797_PID_1 0x2043 +#define USB8797_PID_2 0x2044 +#define USB8801_PID_1 0x2049 +#define USB8801_PID_2 0x204a +#define USB8997_PID_1 0x2052 +#define USB8997_PID_2 0x204e + + +#define USB8XXX_FW_DNLD 1 +#define USB8XXX_FW_READY 2 +#define USB8XXX_FW_MAX_RETRY 3 + +#define MWIFIEX_TX_DATA_URB 6 +#define MWIFIEX_RX_DATA_URB 6 +#define MWIFIEX_USB_TIMEOUT 100 + +#define USB8997_DEFAULT_FW_NAME "mwlwifi/88W8997_usb.bin" + +#define FW_DNLD_TX_BUF_SIZE 4096 +#define FW_DNLD_RX_BUF_SIZE 2048 +#define FW_HAS_LAST_BLOCK 0x00000004 +#define FW_CMD_7 0x00000007 + +#define MWIFIEX_RX_DATA_BUF_SIZE (4 * 1024) +#define MWIFIEX_RX_CMD_BUF_SIZE (4 * 1024) + +#define HIGH_RX_PENDING 50 +#define LOW_RX_PENDING 20 + + +#define FW_DATA_XMIT_SIZE \ + (sizeof(struct fw_header) + dlen + sizeof(u32)) + +struct urb_context { + struct mwl_priv *priv; + struct sk_buff *skb; + struct urb *urb; + u8 ep; + struct list_head list; +}; + +struct usb_tx_data_port { + u8 tx_data_ep; + atomic_t tx_data_urb_pending; + int tx_data_ix; + struct urb_context tx_data_list[MWIFIEX_TX_DATA_URB]; +}; + +struct mwl_wait_queue { + wait_queue_head_t wait; + int status; +}; + + + +struct usb_card_rec { + struct mwl_priv *priv; + struct usb_device *udev; + struct usb_interface *intf; + struct completion fw_done; + u8 rx_cmd_ep; + struct urb_context rx_cmd; + + struct urb_context rx_data_list[MWIFIEX_RX_DATA_URB]; + struct list_head rx_urb_pending_list; + spinlock_t rx_urb_lock; + struct usb_anchor rx_urb_anchor; + + u8 usb_boot_state; + u8 rx_data_ep; + u8 tx_cmd_ep; + + int bulk_out_maxpktsize; + struct urb_context tx_cmd; + struct usb_tx_data_port port; + int rx_cmd_ep_type; + u8 rx_cmd_interval; + int tx_cmd_ep_type; + u8 tx_cmd_interval; + int chip_type; + + bool cmd_resp_recvd; + struct mwl_wait_queue cmd_wait_q; + u16 cmd_id; + + struct sk_buff_head rx_data_q; + atomic_t rx_pending; + struct tasklet_struct tx_task; + + int reset_pwd_gpio; + + bool dying; +}; + +struct fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_len; + __le32 crc; +}; + +struct fw_sync_header { + __le32 cmd; + __le32 seq_num; +} __packed; + +struct fw_data { + struct fw_header fw_hdr; + __le32 seq_num; + u8 data[1]; +} __packed; + +enum mwifiex_usb_ep { + MWIFIEX_USB_EP_CMD_EVENT = 1, + MWIFIEX_USB_EP_DATA = 2, + MWIFIEX_USB_EP_DATA_CH2 = 3, +}; + +enum RESPONSES { + OK = 0, + ERROR = 1 +}; + +struct mwl_rxinfo { + struct sk_buff *parent; + u8 bss_num; + u8 bss_type; + u8 use_count; + u8 buf_type; +}; + +struct mwl_txinfo { + u32 status_code; + u8 flags; + u8 bss_num; + u8 bss_type; + u8 aggr_num; + u32 pkt_len; + u8 ack_frame_id; + u64 cookie; +}; + +struct mwl_cb { + union { + struct mwl_rxinfo rx_info; + struct mwl_txinfo tx_info; + }; +}; + +static inline struct mwl_rxinfo *MWL_SKB_RXCB(struct sk_buff *skb) +{ + struct mwl_cb *cb = (struct mwl_cb *)skb->cb; + + BUILD_BUG_ON(sizeof(struct mwl_cb) > sizeof(skb->cb)); + return &cb->rx_info; +} + +#endif /*_LRDMWL_USB_H */ diff --git a/drivers/net/wireless/laird/lrdmwl/vendor_cmd.c b/drivers/net/wireless/laird/lrdmwl/vendor_cmd.c new file mode 100644 index 0000000000000..6ca9c12fc1d77 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/vendor_cmd.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Laird Connectivity + * under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include +#include +#include + +#include "sysadpt.h" +#include "dev.h" +#include "fwcmd.h" +#include "vendor_cmd.h" +#include "hostcmd.h" + +#define PWR_TABLE_ID_OFFSET 36 + +void mwl_hex_dump(const void *buf, size_t len); + +static int +lrd_vendor_cmd_mfg_start(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mwl_priv *priv = hw->priv; + struct sk_buff *msg = NULL; + int rc = -ENOSYS; + u32 rsp = 0; + + //If Deep Sleep enabled, pause it + mwl_pause_ds(priv); + + //Send + rc = lrd_fwcmd_mfg_start(hw, &rsp); + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32) * 2); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rc); + nla_put(msg, LRD_ATTR_DATA, sizeof(rsp), &rsp); + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + } + + return rc; +} + +static int +lrd_vendor_cmd_mfg_write(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct sk_buff *msg = NULL; + int rc = -ENOSYS; + + //Send + rc = lrd_fwcmd_mfg_write(hw, (void*)data, data_len); + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rc); + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + } + + return rc; +} + +static int +lrd_vendor_cmd_mfg_stop(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mwl_priv *priv = hw->priv; + struct sk_buff *msg = NULL; + int rc = -ENOSYS; + + //Send + rc = lrd_fwcmd_mfg_end(hw); + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rc); + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + } + + //If Deep Sleep was paused, resume now + mwl_resume_ds(priv); + + return rc; +} + +static int +lrd_vendor_cmd_lru_start(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct sk_buff *msg = NULL; + int rc = 0; + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rc); + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + } + + return rc; + +} + +static int +lrd_vendor_cmd_lru_end(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct sk_buff *msg = NULL; + int rc = 0; + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rc); + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + } + + return rc; +} + + +static int +lrd_vendor_cmd_lru_write(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct sk_buff *msg = NULL; + struct lrd_vndr_header *rsp = NULL; + int rc = -ENOSYS; + + //Send + rc = lrd_fwcmd_lru_write(hw, (void*)data, data_len, (void*)&rsp); + + if (rc < 0 ) { + goto fail; + } + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u32) + (rsp ? rsp->len:0)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rsp ? rsp->result : 0); + + if (rsp) { + nla_put(msg, LRD_ATTR_DATA, rsp->len - sizeof(struct lrd_vndr_header), ((u8*)rsp) + sizeof(struct lrd_vndr_header) ); + } + + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + goto fail; + } + +fail: + if (rsp) { + kfree(rsp); + } + + return rc; +} + +static int +lrd_vendor_cmd_lrd_write(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mwl_priv *priv = hw->priv; + struct sk_buff *msg = NULL; + struct lrd_vndr_header *rsp = NULL; + struct lrdcmd_header *hdr = (struct lrdcmd_header*)data; + int rc = -ENOSYS; + + //Send + if (NULL == hdr || data_len < sizeof(struct lrdcmd_header)) { + rc = -EINVAL; + goto fail; + } + + if (hdr->lrd_cmd == cpu_to_le16(LRD_CMD_PWR_TABLE)) { + if (priv->recovery_in_progress || priv->shutdown) { + rc = -ENETDOWN; + goto fail; + } + + //Restart timers + mod_timer(&priv->reg.timer_awm, jiffies + msecs_to_jiffies(CC_AWM_TIMER)); + cancel_work_sync(&priv->reg.awm); + cancel_work_sync(&priv->reg.event); + + mutex_lock(&priv->reg.mutex); + + //Send + rc = lrd_fwcmd_lrd_write(hw, (void*)data, data_len, (void*)&rsp); + + //Queue event work + if (!rc && !rsp->result) { + priv->reg.pn = le32_to_cpu(*(u32*)(((u8*)data) + sizeof(*hdr) + PWR_TABLE_ID_OFFSET)); + + queue_work(priv->lrd_workq, &priv->reg.event); + } + mutex_unlock(&priv->reg.mutex); + } + else if ((hdr->lrd_cmd == cpu_to_le16(LRD_CMD_CC_OTP_INFO)) || + (hdr->lrd_cmd == cpu_to_le16(LRD_CMD_CC_INFO)) ) { + rsp = kzalloc(sizeof(struct lrdcmd_cmd_cc_info) + sizeof(struct lrd_vndr_header), GFP_KERNEL); + + if (rsp) { + struct lrdcmd_cmd_cc_info *cc; + rsp->command = HOSTCMD_LRD_CMD; + rsp->result = 0; + rsp->len = sizeof(struct lrdcmd_cmd_cc_info) + sizeof(struct lrd_vndr_header); + + cc = (struct lrdcmd_cmd_cc_info*)(((u8*)rsp) + sizeof(struct lrd_vndr_header)); + cc->hdr.result = 0; + if (hdr->lrd_cmd == cpu_to_le16(LRD_CMD_CC_OTP_INFO)) { + cc->hdr.lrd_cmd = LRD_CMD_CC_OTP_INFO; + cc->region = priv->reg.otp.region; + memcpy(cc->alpha2, priv->reg.otp.alpha2, sizeof(cc->alpha2)); + } + else { + cc->hdr.lrd_cmd = LRD_CMD_CC_INFO; + cc->region = priv->reg.cc.region; + memcpy(cc->alpha2, priv->reg.cc.alpha2, sizeof(cc->alpha2)); + } + + rc = 0; + } + else { + wiphy_err(hw->wiphy, "lrd_cmd_cc_info failed allocation response %zu\n", sizeof(struct cc_info)); + rc = -ENOMEM; + } + } + else { + if (priv->recovery_in_progress) { + wiphy_info(hw->wiphy, + "Vendor command \"%d\" rejected: Wi-Fi off\n", + hdr->lrd_cmd); + rc = -ENETDOWN; + goto fail; + } + + //Send + rc = lrd_fwcmd_lrd_write(hw, (void*)data, data_len, (void*)&rsp); + } + + if (rc < 0 ) { + goto fail; + } + + //Respond + msg = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32_t) + (rsp ? rsp->len:0)); + + if (msg) { + nla_put_u32(msg, LRD_ATTR_CMD_RSP, rsp ? rsp->result : 0); + + if (rsp) { + nla_put(msg, LRD_ATTR_DATA, rsp->len - sizeof(struct lrd_vndr_header), ((u8*)rsp) + sizeof(struct lrd_vndr_header) ); + } + + rc = cfg80211_vendor_cmd_reply(msg); + } + else { + rc = -ENOMEM; + goto fail; + } + +fail: + if (rsp) { + kfree(rsp); + } + + return rc; +} + +static const struct wiphy_vendor_command lrd_vendor_commands[] = { + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_MFG_START, + }, + .flags = 0, + .doit = lrd_vendor_cmd_mfg_start, + .policy = VENDOR_CMD_RAW_DATA, + }, + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_MFG_WRITE, + }, + .flags = 0, + .doit = lrd_vendor_cmd_mfg_write, + .policy = VENDOR_CMD_RAW_DATA, + }, + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_MFG_STOP, + }, + .flags = 0, + .doit = lrd_vendor_cmd_mfg_stop, + .policy = VENDOR_CMD_RAW_DATA, + }, + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_LRU_START, + }, + .flags = 0, + .doit = lrd_vendor_cmd_lru_start, + .policy = VENDOR_CMD_RAW_DATA, + }, + + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_LRU_WRITE, + }, + .flags = 0, + .doit = lrd_vendor_cmd_lru_write, + .policy = VENDOR_CMD_RAW_DATA, + }, + + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_LRU_STOP, + }, + .flags = 0, + .doit = lrd_vendor_cmd_lru_end, + .policy = VENDOR_CMD_RAW_DATA, + }, + + { + .info = { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_CMD_LRD_WRITE, + }, + .flags = 0, + .doit = lrd_vendor_cmd_lrd_write, + .policy = VENDOR_CMD_RAW_DATA, + }, + +}; + + +void lrd_set_vendor_commands(struct wiphy *wiphy) +{ + wiphy->vendor_commands = lrd_vendor_commands; + wiphy->n_vendor_commands = ARRAY_SIZE(lrd_vendor_commands); +} + + +static const struct nl80211_vendor_cmd_info lrd_vendor_events[] = +{ + { + .vendor_id = LRD_OUI, + .subcmd = LRD_VENDOR_EVENT_RESTART, + }, +}; + +void lrd_set_vendor_events(struct wiphy *wiphy) +{ + wiphy->vendor_events = lrd_vendor_events; + wiphy->n_vendor_events = ARRAY_SIZE(lrd_vendor_events); +} diff --git a/drivers/net/wireless/laird/lrdmwl/vendor_cmd.h b/drivers/net/wireless/laird/lrdmwl/vendor_cmd.h new file mode 100644 index 0000000000000..7ba9d3f8c7aa7 --- /dev/null +++ b/drivers/net/wireless/laird/lrdmwl/vendor_cmd.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018-2020 Laird Connectivity + * + * This software file (the "File") is distributed by Laird, PLC. + * under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _LRD_VENDOR_CMD_H_ +#define _LRD_VENDOR_CMD_H_ + +#define LRD_OUI 0xC0EE40 + +enum lrd_vendor_commands { + LRD_VENDOR_CMD_MFG_START = 1, + LRD_VENDOR_CMD_MFG_WRITE, + LRD_VENDOR_CMD_MFG_STOP, + LRD_VENDOR_CMD_LRU_START, + LRD_VENDOR_CMD_LRU_WRITE, + LRD_VENDOR_CMD_LRU_STOP, + LRD_VENDOR_CMD_LRD_WRITE, + LRD_VENDOR_CMD_MAX, +}; + +enum lrd_vendor_events { + LRD_VENDOR_EVENT_RESTART = 0x8000, +}; + +enum lrd_nlattrs { + LRD_ATTR_CMD_RSP = 1, //BZ13280 Some Android NL libraries discard attributes that are 0 + LRD_ATTR_DATA, + LRD_ATTR_RESTART_REASON, + LRD_ATTR_MAX +}; + +enum lrd_attr_restart_reason { + LRD_REASON_RESET = 1, + LRD_REASON_RESUME +}; + + +void lrd_set_vendor_commands(struct wiphy *wiphy); +void lrd_set_vendor_events (struct wiphy *wiphy); +#endif diff --git a/include/linux/bcma/bcma.h b/include/linux/bcma/bcma.h index 60b94b944e9f1..fe1cb2aeaef38 100644 --- a/include/linux/bcma/bcma.h +++ b/include/linux/bcma/bcma.h @@ -154,6 +154,7 @@ struct bcma_host_ops { #define BCMA_CORE_USB30_DEV 0x83D #define BCMA_CORE_ARM_CR4 0x83E #define BCMA_CORE_GCI 0x840 +#define BCMA_CORE_SR 0x841 #define BCMA_CORE_CMEM 0x846 /* CNDS DDR2/3 memory controller */ #define BCMA_CORE_ARM_CA7 0x847 #define BCMA_CORE_SYS_MEM 0x849 diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h index 74f9d9a6d3307..3108bc0ceb9e5 100644 --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -63,7 +63,7 @@ #define SDIO_DEVICE_ID_BROADCOM_4339 0x4339 #define SDIO_DEVICE_ID_BROADCOM_4345 0x4345 #define SDIO_DEVICE_ID_BROADCOM_4354 0x4354 -#define SDIO_DEVICE_ID_BROADCOM_CYPRESS_89359 0x4355 +#define SDIO_DEVICE_ID_BROADCOM_CYPRESS_89459 0x4355 #define SDIO_DEVICE_ID_BROADCOM_4356 0x4356 #define SDIO_DEVICE_ID_BROADCOM_4359 0x4359 #define SDIO_DEVICE_ID_BROADCOM_CYPRESS_4373 0x4373 @@ -78,6 +78,14 @@ #define SDIO_DEVICE_ID_BROADCOM_43455 0xa9bf #define SDIO_DEVICE_ID_BROADCOM_CYPRESS_43752 0xaae8 +#define SDIO_VENDOR_ID_CYPRESS 0x04b4 +#define SDIO_DEVICE_ID_CYPRESS_54590 0xbd3a +#define SDIO_DEVICE_ID_CYPRESS_54591 0xbd3b +#define SDIO_DEVICE_ID_CYPRESS_54594 0xbd3c +#define SDIO_DEVICE_ID_CYPRESS_43439 0xbd3d +#define SDIO_DEVICE_ID_CYPRESS_55572 0xbd31 +#define SDIO_DEVICE_ID_CYPRESS_55500 0xbd3e + #define SDIO_VENDOR_ID_MARVELL 0x02df #define SDIO_DEVICE_ID_MARVELL_LIBERTAS 0x9103 #define SDIO_DEVICE_ID_MARVELL_8688_WLAN 0x9104 diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 60c8caae77671..8cf76a7956880 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -24,6 +24,7 @@ #include #include #include +#include /** * DOC: Introduction @@ -2458,6 +2459,12 @@ struct cfg80211_scan_request { u32 n_6ghz_params; struct cfg80211_scan_6ghz_params *scan_6ghz_params; +#ifndef _REMOVE_LAIRD_MODS_ + u16 passive_channel_time; + u16 probe_delay_time; + u16 scan_suspend_time; +#endif + /* keep last */ struct ieee80211_channel *channels[]; }; @@ -5304,6 +5311,9 @@ struct wiphy { /* assign these fields before you register the wiphy */ +#define WIPHY_COMPAT_PAD_SIZE 2304 + u8 padding[WIPHY_COMPAT_PAD_SIZE]; + u8 perm_addr[ETH_ALEN]; u8 addr_mask[ETH_ALEN]; @@ -7481,6 +7491,8 @@ struct cfg80211_fils_resp_params { * if the bss is expired during the connection, esp. for those drivers * implementing connect op. Only one parameter among @bssid and @bss needs * to be specified. + * @authorized: Indicates whether the connection is ready to transport + * data packets. */ struct cfg80211_connect_resp_params { int status; @@ -7498,6 +7510,7 @@ struct cfg80211_connect_resp_params { const u8 *bssid; struct cfg80211_bss *bss; } links[IEEE80211_MLD_MAX_NUM_LINKS]; + bool authorized; }; /** @@ -7657,6 +7670,9 @@ cfg80211_connect_timeout(struct net_device *dev, const u8 *bssid, * @links.bss: For MLO roaming, entry of new bss to which STA link got * roamed. For non-MLO roaming, links[0].bss points to entry of bss to * which STA got roamed (may be %NULL if %links.bssid is set) + * @authorized: true if the 802.1X authentication was done by the driver or is + * not needed (e.g., when Fast Transition protocol was used), false + * otherwise. Ignored for networks that don't use 802.1X authentication. */ struct cfg80211_roam_info { const u8 *req_ie; @@ -7673,6 +7689,7 @@ struct cfg80211_roam_info { struct ieee80211_channel *channel; struct cfg80211_bss *bss; } links[IEEE80211_MLD_MAX_NUM_LINKS]; + bool authorized; }; /** diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 72b739dc6d530..ac2bad57933f8 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1827,6 +1827,8 @@ struct ieee80211_vif_cfg { * @drv_priv: data area for driver use, will always be aligned to * sizeof(void \*). * @txq: the multicast data TX queue (if driver uses the TXQ abstraction) + * @txqs_stopped: per AC flag to indicate that intermediate TXQs are stopped, + * protected by fq->lock. * @offload_flags: 802.3 -> 802.11 enapsulation offload flags, see * &enum ieee80211_offload_flags. * @mbssid_tx_vif: Pointer to the transmitting interface if MBSSID is enabled. @@ -1855,6 +1857,8 @@ struct ieee80211_vif { bool probe_req_reg; bool rx_mcast_action_reg; + bool txqs_stopped[IEEE80211_NUM_ACS]; + struct ieee80211_vif *mbssid_tx_vif; /* must be last */ diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index c32e7616a366a..9fb9f2c35a30f 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -6359,6 +6359,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_FILS_CRYPTO_OFFLOAD, NL80211_EXT_FEATURE_RADAR_BACKGROUND, NL80211_EXT_FEATURE_POWERED_ADDR_CHANGE, + NL80211_EXT_FEATURE_ROAM_OFFLOAD, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 27479bbb093ac..843aa08dfcd50 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -117,6 +117,11 @@ struct ieee80211_bss { /* Keep track of what bits of information we have valid info for. */ u8 valid_data; + +#ifndef _REMOVE_LAIRD_MODS_ + // scaled averaged signal level + s32 avg_signal; +#endif }; /** @@ -552,6 +557,14 @@ struct ieee80211_if_managed { */ u8 *assoc_req_ies; size_t assoc_req_ies_len; + +#ifndef _REMOVE_LAIRD_MODS_ + struct { + int enabled; + int ipv4; + int ipv6; + } dms; +#endif }; struct ieee80211_if_ibss { diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 02b5abc7326bc..d54e8872dab5b 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -714,6 +714,11 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, NL80211_EXT_FEATURE_SCAN_RANDOM_SN); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SCAN_MIN_PREQ_CONTENT); + +#ifndef _REMOVE_LAIRD_MODS_ + /* LAIRD: indicate that scan timing is configurable */ + wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); +#endif } if (!ops->set_key) @@ -1279,8 +1284,13 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (hw->queues > IEEE80211_MAX_QUEUES) hw->queues = IEEE80211_MAX_QUEUES; +#ifndef _REMOVE_LAIRD_MODS_ + local->workqueue = + alloc_ordered_workqueue("%s", WQ_HIGHPRI, wiphy_name(local->hw.wiphy)); +#else local->workqueue = alloc_ordered_workqueue("%s", 0, wiphy_name(local->hw.wiphy)); +#endif if (!local->workqueue) { result = -ENOMEM; goto fail_workqueue; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index dc9e7eb7dd857..36a7d413bf8ed 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -2053,6 +2053,10 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link, int chan_pwr = 0, pwr_reduction_80211h = 0; int pwr_level_cisco, pwr_level_80211h; int new_ap_level; +#ifndef _REMOVE_LAIRD_MODS_ + // BZ12222: process 802.11d/h even if SM/RM capabilities are not set + if (country_ie) { +#else __le16 capab = mgmt->u.probe_resp.capab_info; if (ieee80211_is_s1g_beacon(mgmt->frame_control)) @@ -2061,6 +2065,7 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link, if (country_ie && (capab & cpu_to_le16(WLAN_CAPABILITY_SPECTRUM_MGMT) || capab & cpu_to_le16(WLAN_CAPABILITY_RADIO_MEASURE))) { +#endif has_80211h_pwr = ieee80211_find_80211h_pwr_constr( sdata, channel, country_ie, country_ie_len, pwr_constr_ie, &chan_pwr, &pwr_reduction_80211h); @@ -2068,6 +2073,14 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link, max_t(int, 0, chan_pwr - pwr_reduction_80211h); } +#ifndef _REMOVE_LAIRD_MODS_ + // BZ12222: 802.11h TPC (pwr_constr_ie) takes precedence over Cisco TPC + if (has_80211h_pwr && pwr_constr_ie) + ; // do not process cisco_dtpc_ie + else + // fall through to process cisco_dtpc_ie +#endif + if (cisco_dtpc_ie) { ieee80211_find_cisco_dtpc( sdata, channel, cisco_dtpc_ie, &pwr_level_cisco); @@ -2075,7 +2088,13 @@ static u32 ieee80211_handle_pwr_constr(struct ieee80211_link_data *link, } if (!has_80211h_pwr && !has_cisco_pwr) +#ifndef _REMOVE_LAIRD_MODS_ + // BZ1222: TPC elements removed, restore original power level + new_ap_level = IEEE80211_UNSET_POWER_LEVEL; + else +#else return 0; +#endif /* If we have both 802.11h and Cisco DTPC, apply both limits * by picking the smallest of the two power levels advertised. @@ -6429,6 +6448,16 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, bool mlo; int err; +#ifndef _REMOVE_LAIRD_MODS_ + if (sdata->vif.type == NL80211_IFTYPE_STATION) { + /* new connection, reset DMS state */ + /* TBD: add a command to enable DMS */ + /* for now, just enable, and auto-detect DMS usage */ + memset(&sdata->u.mgd.dms, 0, sizeof(sdata->u.mgd.dms)); + sdata->u.mgd.dms.enabled = 1; + } +#endif + if (link_id >= 0) { mlo = true; if (WARN_ON(!ap_mld_addr)) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 0f81492da0b46..2515a4abea567 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -2573,6 +2573,64 @@ bool ieee80211_is_our_addr(struct ieee80211_sub_if_data *sdata, return false; } +#ifndef _REMOVE_LAIRD_MODS_ +/* DMS: auto-detect DMS ipv4 and DMS ipv6 packets */ +static void ieee80211_dms_detect(struct ieee80211_rx_data *rx) +{ + struct ethhdr *ehdr = (struct ethhdr *) rx->skb->data; + + if (rx->sdata->vif.type != NL80211_IFTYPE_STATION) + return; + + if (!rx->sdata->u.mgd.dms.enabled) + return; + + if (!is_multicast_ether_addr(ehdr->h_dest)) + return; + + if (ehdr->h_proto == cpu_to_be16(ETH_P_IP)) { + /* DMS ipv4 packet -- set flag to drop normal mcast ipv4 */ + if (!rx->sdata->u.mgd.dms.ipv4) { + rx->sdata->u.mgd.dms.ipv4 = 1; + } + } else if (ehdr->h_proto == cpu_to_be16(ETH_P_IPV6)) { + /* DMS ipv6 packet -- set flag to drop normal mcast ipv6 */ + if (!rx->sdata->u.mgd.dms.ipv6) { + rx->sdata->u.mgd.dms.ipv6 = 1; + } + } + return; +} + +/* DMS: if using DMS, drop normal multicast */ +static bool ieee80211_dms_allowed(struct ieee80211_rx_data *rx) +{ + struct ethhdr *ehdr = (struct ethhdr *) rx->skb->data; + + if (rx->sdata->vif.type != NL80211_IFTYPE_STATION) + return true; + + if (!rx->sdata->u.mgd.dms.enabled) + return true; + + if (!is_multicast_ether_addr(ehdr->h_dest)) + return true; + + if (ehdr->h_proto == cpu_to_be16(ETH_P_IP)) { + if (rx->sdata->u.mgd.dms.ipv4) { + /* drop multicast ipv4 packet */ + return false; + } + } else if (ehdr->h_proto == cpu_to_be16(ETH_P_IPV6)) { + if (rx->sdata->u.mgd.dms.ipv6) { + /* drop multicast ipv6 packet */ + return false; + } + } + return true; +} +#endif /* _REMOVE_LAIRD_MODS_ */ + /* * requires that rx->skb is a frame with ethernet header */ @@ -2759,6 +2817,9 @@ __ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx, u8 data_offset) struct sk_buff_head frame_list; struct ethhdr ethhdr; const u8 *check_da = ethhdr.h_dest, *check_sa = ethhdr.h_source; +#ifndef __REMOVE_LAIRD_MODS__ + int check_dms = 0; +#endif if (unlikely(ieee80211_has_a4(hdr->frame_control))) { check_da = NULL; @@ -2772,6 +2833,13 @@ __ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx, u8 data_offset) if (!rx->sta || !test_sta_flag(rx->sta, WLAN_STA_TDLS_PEER)) check_sa = NULL; +#ifndef _REMOVE_LAIRD_MODS_ + /* DMS: check for DMS receives, if A-MSDU is from our AP */ + if (rx->sdata->u.mgd.dms.enabled) { + if (ether_addr_equal(hdr->addr2, rx->link->u.mgd.bssid)) + check_dms = 1; + } +#endif break; case NL80211_IFTYPE_MESH_POINT: check_sa = NULL; @@ -2802,6 +2870,12 @@ __ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx, u8 data_offset) continue; } +#ifndef _REMOVE_LAIRD_MODS_ + /* DMS: detect DMS packets */ + if (check_dms) + ieee80211_dms_detect(rx); +#endif + ieee80211_deliver_skb(rx); } @@ -3046,6 +3120,13 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx) if (!ieee80211_frame_allowed(rx, fc)) return RX_DROP_MONITOR; +#ifndef _REMOVE_LAIRD_MODS_ + if (!ieee80211_dms_allowed(rx)) { + /* DMS: drop normal multicast that are being DMS delivered */ + return RX_DROP_MONITOR; + } +#endif + /* directly handle TDLS channel switch requests/responses */ if (unlikely(((struct ethhdr *)rx->skb->data)->h_proto == cpu_to_be16(ETH_P_TDLS))) { diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index dc3cdee51e660..222ce99a8eafa 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -29,6 +29,44 @@ #define IEEE80211_CHANNEL_TIME (HZ / 33) #define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 9) +#ifndef _REMOVE_LAIRD_MODS_ +static inline int __get_PROBE_DELAY(struct cfg80211_scan_request *scan_req) +{ + int msec = scan_req->probe_delay_time; + if (0 < msec && msec <= 250) + return (HZ * msec + 999) / 1000; + return IEEE80211_PROBE_DELAY; +} +#undef IEEE80211_PROBE_DELAY +#define IEEE80211_PROBE_DELAY __get_PROBE_DELAY(scan_req) +static inline int __get_CHANNEL_TIME(struct cfg80211_scan_request *scan_req) +{ + int msec = scan_req->duration; + if (0 < msec && msec <= 250) + return (HZ * msec + 999) / 1000; + return IEEE80211_CHANNEL_TIME; +} +#undef IEEE80211_CHANNEL_TIME +#define IEEE80211_CHANNEL_TIME __get_CHANNEL_TIME(scan_req) +static inline int __get_PASSIVE_CHANNEL_TIME(struct cfg80211_scan_request *scan_req) +{ + int msec = scan_req->passive_channel_time; + if (0 < msec && msec <= 250) + return (HZ * msec + 999) / 1000; + return IEEE80211_PASSIVE_CHANNEL_TIME; +} +#undef IEEE80211_PASSIVE_CHANNEL_TIME +#define IEEE80211_PASSIVE_CHANNEL_TIME __get_PASSIVE_CHANNEL_TIME(scan_req) +// note, this version returns 0 by default, since no macro value available +static inline int __get_SUSPEND_TIME(struct cfg80211_scan_request *scan_req) +{ + int msec = scan_req->scan_suspend_time; + if (0 < msec && msec <= 250) + return (HZ * msec + 999) / 1000; + return 0; // caller will fill in default +} +#endif /* _REMOVE_LAIRD_MODS_ */ + void ieee80211_rx_bss_put(struct ieee80211_local *local, struct ieee80211_bss *bss) { @@ -159,12 +197,52 @@ ieee80211_bss_info_update(struct ieee80211_local *local, size_t baselen; u8 *elements; +#ifndef _REMOVE_LAIRD_MODS_ +// averaging of the signal level of received beacon/probe rsp +// the cumulative value (avg_signal) is multipled by LAIRD_SCALE +// averaging subtracts off 1/LAIRD_AVG before adding the new signal +// note, not using EWMA routines since values are signed +#define LAIRD_AVG (8) +#define LAIRD_SCALE (8*(LAIRD_AVG)) + s32 signal = rx_status->signal; + + cbss = cfg80211_get_bss(local->hw.wiphy, + NULL, /* any channel */ + mgmt->bssid, + NULL, 0, /* any ssid */ + IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY); + + if (cbss) { + bss = (void *)cbss->priv; + if (bss->avg_signal) { + bss->avg_signal += (signal * (LAIRD_SCALE/LAIRD_AVG)) + - ((bss->avg_signal + (LAIRD_AVG/2)) / LAIRD_AVG); + signal = bss->avg_signal; + if (signal >= 0) signal += LAIRD_SCALE/2; + else signal -= LAIRD_SCALE/2; + signal /= LAIRD_SCALE; + } else { + bss->avg_signal = signal * LAIRD_SCALE; + } + cfg80211_put_bss(local->hw.wiphy, cbss); + } +#endif + if (rx_status->flag & RX_FLAG_NO_SIGNAL_VAL) bss_meta.signal = 0; /* invalid signal indication */ else if (ieee80211_hw_check(&local->hw, SIGNAL_DBM)) +#ifndef _REMOVE_LAIRD_MODS_ + bss_meta.signal = signal * 100; +#else bss_meta.signal = rx_status->signal * 100; +#endif else if (ieee80211_hw_check(&local->hw, SIGNAL_UNSPEC)) +#ifndef _REMOVE_LAIRD_MODS_ + bss_meta.signal = (signal * 100) / local->hw.max_signal; +#else bss_meta.signal = (rx_status->signal * 100) / local->hw.max_signal; +#endif bss_meta.scan_width = NL80211_BSS_CHAN_WIDTH_20; if (rx_status->bw == RATE_INFO_BW_5) @@ -697,6 +775,10 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, struct ieee80211_local *local = sdata->local; bool hw_scan = local->ops->hw_scan; int rc; +#ifndef _REMOVE_LAIRD_MODS_ + // need scan_req for fetching next_delay duration values + struct cfg80211_scan_request *scan_req = req; +#endif lockdep_assert_held(&local->mtx); @@ -800,8 +882,15 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, /* We need to ensure power level is at max for scanning. */ ieee80211_hw_config(local, 0); +#ifndef _REMOVE_LAIRD_MODS_ + if ((req->channels[0]->flags & IEEE80211_CHAN_NO_IR) || + !local->scan_req->wdev || + ((req->channels[0]->flags & IEEE80211_CHAN_RADAR) && + local->scan_req->wdev->iftype != NL80211_IFTYPE_STATION) || +#else if ((req->channels[0]->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) || +#endif !req->n_ssids) { next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; if (req->n_ssids) @@ -856,13 +945,22 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, } static unsigned long -ieee80211_scan_get_channel_time(struct ieee80211_channel *chan) +ieee80211_scan_get_channel_time(struct ieee80211_channel *chan +#ifndef _REMOVE_LAIRD_MODS_ + , struct cfg80211_scan_request *scan_req +#endif + ) { /* * TODO: channel switching also consumes quite some time, * add that delay as well to get a better estimation */ +#ifndef _REMOVE_LAIRD_MODS_ + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !scan_req->wdev || + ((chan->flags & IEEE80211_CHAN_RADAR) && scan_req->wdev->iftype != NL80211_IFTYPE_STATION)) +#else if (chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) +#endif return IEEE80211_PASSIVE_CHANNEL_TIME; return IEEE80211_PROBE_DELAY + IEEE80211_CHANNEL_TIME; } @@ -915,7 +1013,11 @@ static void ieee80211_scan_state_decision(struct ieee80211_local *local, */ bad_latency = time_after(jiffies + +#ifndef _REMOVE_LAIRD_MODS_ + ieee80211_scan_get_channel_time(next_chan, scan_req), +#else ieee80211_scan_get_channel_time(next_chan), +#endif local->leave_oper_channel_time + HZ / 8); if (associated && !tx_empty) { @@ -1013,7 +1115,12 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, * * In any case, it is not necessary for a passive scan. */ +#ifndef _REMOVE_LAIRD_MODS_ + if ((chan->flags & IEEE80211_CHAN_NO_IR) || !scan_req->wdev || + ((chan->flags & IEEE80211_CHAN_RADAR) && scan_req->wdev->iftype != NL80211_IFTYPE_STATION) || +#else if ((chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) || +#endif !scan_req->n_ssids) { *next_delay = IEEE80211_PASSIVE_CHANNEL_TIME; local->next_scan_state = SCAN_DECISION; @@ -1030,6 +1137,12 @@ static void ieee80211_scan_state_set_channel(struct ieee80211_local *local, static void ieee80211_scan_state_suspend(struct ieee80211_local *local, unsigned long *next_delay) { +#ifndef _REMOVE_LAIRD_MODS_ + struct cfg80211_scan_request *scan_req; + scan_req = rcu_dereference_protected(local->scan_req, + lockdep_is_held(&local->mtx)); +#endif + /* switch back to the operating channel */ local->scan_chandef.chan = NULL; ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL); @@ -1037,6 +1150,10 @@ static void ieee80211_scan_state_suspend(struct ieee80211_local *local, /* disable PS */ ieee80211_offchannel_return(local); +#ifndef _REMOVE_LAIRD_MODS_ + *next_delay = __get_SUSPEND_TIME(scan_req); + if (!*next_delay) // fall through to default value if zero +#endif *next_delay = HZ / 5; /* afterwards, resume scan & go to next channel */ local->next_scan_state = SCAN_RESUME; diff --git a/net/wireless/core.c b/net/wireless/core.c index b3ec9eaec36b3..b66927c8cb616 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -418,6 +418,17 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, struct cfg80211_registered_device *rdev; int alloc_size; + /* + * Make sure the padding is >= the rest of the struct so that we + * always keep it large enough to pad out the entire original + * kernel's struct. We really only need to make sure it's larger + * than the kernel compat is compiled against, but since it'll + * only increase in size make sure it's larger than the current + * version of it. Subtract since it's included. + */ + BUILD_BUG_ON(WIPHY_COMPAT_PAD_SIZE < + sizeof(struct wiphy) - WIPHY_COMPAT_PAD_SIZE); + WARN_ON(ops->add_key && (!ops->del_key || !ops->set_default_key)); WARN_ON(ops->auth && (!ops->assoc || !ops->deauth || !ops->disassoc)); WARN_ON(ops->connect && !ops->disconnect); @@ -574,13 +585,15 @@ static int wiphy_verify_combinations(struct wiphy *wiphy) c = &wiphy->iface_combinations[i]; +#ifndef _REMOVE_LAIRD_MODS_ +#else /* * Combinations with just one interface aren't real, * however we make an exception for DFS. */ if (WARN_ON((c->max_interfaces < 2) && !c->radar_detect_widths)) return -EINVAL; - +#endif /* Need at least one channel */ if (WARN_ON(!c->num_different_channels)) return -EINVAL; @@ -1341,10 +1354,15 @@ void cfg80211_init_wdev(struct wireless_dev *wdev) /* allow mac80211 to determine the timeout */ wdev->ps_timeout = -1; - if ((wdev->iftype == NL80211_IFTYPE_STATION || - wdev->iftype == NL80211_IFTYPE_P2P_CLIENT || - wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr) - wdev->netdev->priv_flags |= IFF_DONT_BRIDGE; +#ifndef _REMOVE_LAIRD_MODS_ + if (wdev->iftype == NL80211_IFTYPE_P2P_CLIENT && !wdev->use_4addr) + wdev->netdev->priv_flags |= IFF_DONT_BRIDGE; +#else + if ((wdev->iftype == NL80211_IFTYPE_STATION || + wdev->iftype == NL80211_IFTYPE_P2P_CLIENT || + wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr) + wdev->netdev->priv_flags |= IFF_DONT_BRIDGE; +#endif INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk); } diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 51fda37c8e3e1..ce2af61294e0a 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -1036,6 +1036,36 @@ static int query_regdb(const char *alpha2) return -ENODATA; } +#ifndef _REMOVE_LAIRD_MODS_ +int check_regdb(const char *alpha2) +{ + const struct fwdb_header *hdr = regdb; + const struct fwdb_country *country; + + int rc = -ENODATA; + + ASSERT_RTNL(); + + if (IS_ERR(regdb)) { + rc = PTR_ERR(regdb); + goto done; + } + + country = &hdr->country[0]; + while (country->coll_ptr) { + if (alpha2_equal(alpha2, country->alpha2)) { + rc = REG_REQ_OK; + goto done; + } + country++; + } + +done: + return rc; +} +EXPORT_SYMBOL(check_regdb); +#endif + static void regdb_fw_cb(const struct firmware *fw, void *context) { int set_error = 0; diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 6c2b73c0d36e8..053ecf3fc3406 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1942,6 +1942,92 @@ cfg80211_get_bss_channel(struct wiphy *wiphy, const u8 *ie, size_t ielen, return alt_channel; } +#ifndef _REMOVE_LAIRD_MODS_ +// add regulatory hint for bonded non-DFS/RADAR channels +// needed to clear the NO_IR flag for AP creation +// note, this routine only handles 5GHz, 40MHz and 80MHz channels +static +void _hint_found_beacon_bonded(struct wiphy *wiphy, + struct ieee80211_channel *beacon_chan, + gfp_t gfp, + const u8 *ie, size_t ielen) +{ + struct ieee80211_channel *alt_channel; + const u8 *tmp; + struct ieee80211_ht_operation *htop = NULL; + struct ieee80211_vht_operation *vhtop = NULL; + int cfreq0 = 0; + u32 bcnfreq; + + if (beacon_chan->band != NL80211_BAND_5GHZ) + return; + if (beacon_chan->flags & IEEE80211_CHAN_RADAR) + return; + + tmp = cfg80211_find_ie(WLAN_EID_VHT_OPERATION, ie, ielen); + if (tmp && tmp[1] >= sizeof(struct ieee80211_vht_operation)) + vhtop = (void *)(tmp + 2); + if (vhtop && vhtop->chan_width == 1) { + // 80MHz+ + u32 freq; + int i; + cfreq0 = (int)vhtop->center_freq_seg0_idx; + switch (cfreq0) { + case 42: + case 58: + case 106: + case 122: + case 138: + case 155: + break; + default: + return; // invalid center channel + } + bcnfreq = ieee80211_channel_to_khz(beacon_chan); + freq = ieee80211_channel_to_freq_khz(cfreq0-6, beacon_chan->band); + if (!freq) + return; // invalid starting frequency + if (bcnfreq < freq) + return; // beacon frequency outside 80MHz window + if (bcnfreq > freq + 60000) + return; // beacon frequency outsize 80MHz window + for (i=0; i<4; i++, freq +=20000) { + if (freq == bcnfreq) + continue; + alt_channel = ieee80211_get_channel_khz(wiphy, freq); + if (alt_channel) { + if (!alt_channel->beacon_found) + regulatory_hint_found_beacon(wiphy, alt_channel, gfp); + } + } + return; + } + + tmp = cfg80211_find_ie(WLAN_EID_HT_OPERATION, ie, ielen); + if (tmp && tmp[1] >= sizeof(struct ieee80211_ht_operation)) + htop = (void *)(tmp + 2); + if (htop) { + u32 bcnfreq = ieee80211_channel_to_khz(beacon_chan); + u32 freq; + switch (htop->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + freq = bcnfreq + 20000; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + freq = bcnfreq - 20000; + break; + default: + return; + } + alt_channel = ieee80211_get_channel_khz(wiphy, freq); + if (alt_channel) { + if (!alt_channel->beacon_found) + regulatory_hint_found_beacon(wiphy, alt_channel, gfp); + } + } +} +#endif + /* Returned bss is reference counted and must be cleaned up appropriately. */ static struct cfg80211_bss * cfg80211_inform_single_bss_data(struct wiphy *wiphy, @@ -2033,6 +2119,11 @@ cfg80211_inform_single_bss_data(struct wiphy *wiphy, } else { if (res->pub.capability & WLAN_CAPABILITY_ESS) regulatory_hint_found_beacon(wiphy, channel, gfp); + +#ifndef _REMOVE_LAIRD_MODS_ + // add regulatory hint for 5GHz bonded channels (40/80MHz) + _hint_found_beacon_bonded(wiphy, channel, gfp, ie, ielen); +#endif } if (non_tx_data) { @@ -2513,6 +2604,11 @@ cfg80211_inform_single_bss_frame_data(struct wiphy *wiphy, } else { if (res->pub.capability & WLAN_CAPABILITY_ESS) regulatory_hint_found_beacon(wiphy, channel, gfp); + +#ifndef _REMOVE_LAIRD_MODS_ + // add regulatory hint for 5GHz bonded channels (40/80MHz) + _hint_found_beacon_bonded(wiphy, channel, gfp, variable, ielen); +#endif } trace_cfg80211_return_bss(&res->pub); diff --git a/net/wireless/util.c b/net/wireless/util.c index bf9050eeb2885..edda53ef3fe2d 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1035,12 +1035,19 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, return -EOPNOTSUPP; if (ntype != otype) { +#ifndef _REMOVE_LAIRD_MODS_ + /* if it's part of a bridge, reject changing type to ibss */ + if (netif_is_bridge_port(dev) && + ntype == NL80211_IFTYPE_P2P_CLIENT) + return -EBUSY; +#else /* if it's part of a bridge, reject changing type to station/ibss */ if (netif_is_bridge_port(dev) && (ntype == NL80211_IFTYPE_ADHOC || ntype == NL80211_IFTYPE_STATION || ntype == NL80211_IFTYPE_P2P_CLIENT)) return -EBUSY; +#endif dev->ieee80211_ptr->use_4addr = false; wdev_lock(dev->ieee80211_ptr); @@ -1091,15 +1098,20 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, if (!err) { dev->priv_flags &= ~IFF_DONT_BRIDGE; switch (ntype) { +#ifdef _REMOVE_LAIRD_MODS_ case NL80211_IFTYPE_STATION: if (dev->ieee80211_ptr->use_4addr) break; fallthrough; +#endif case NL80211_IFTYPE_OCB: case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_ADHOC: dev->priv_flags |= IFF_DONT_BRIDGE; break; +#ifndef _REMOVE_LAIRD_MODS_ + case NL80211_IFTYPE_STATION: +#endif case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_AP: case NL80211_IFTYPE_AP_VLAN: