diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath6kl')
31 files changed, 9414 insertions, 2193 deletions
diff --git a/drivers/net/wireless/ath/ath6kl/Kconfig b/drivers/net/wireless/ath/ath6kl/Kconfig index 3d5f8be20ea..9c125ff083f 100644 --- a/drivers/net/wireless/ath/ath6kl/Kconfig +++ b/drivers/net/wireless/ath/ath6kl/Kconfig @@ -1,15 +1,65 @@ config ATH6KL - tristate "Atheros ath6kl support" - depends on MMC + tristate "Atheros mobile chipsets support" depends on CFG80211 + ---help--- + This module adds core support for wireless adapters based on + Atheros AR6003 and AR6004 chipsets. You still need separate + bus drivers for USB and SDIO to be able to use real devices. + + If you choose to build it as a module, it will be called + ath6kl_core. Please note that AR6002 and AR6001 are not + supported by this driver. + +config ATH6KL_SDIO + tristate "Atheros ath6kl SDIO support" + depends on ATH6KL + depends on MMC + ---help--- + This module adds support for wireless adapters based on + Atheros AR6003 and AR6004 chipsets running over SDIO. If you + choose to build it as a module, it will be called ath6kl_sdio. + Please note that AR6002 and AR6001 are not supported by this + driver. + +config ATH6KL_USB + tristate "Atheros ath6kl USB support" + depends on ATH6KL + depends on USB ---help--- This module adds support for wireless adapters based on - Atheros AR6003 chipset running over SDIO. If you choose to - build it as a module, it will be called ath6kl. Pls note - that AR6002 and AR6001 are not supported by this driver. + Atheros AR6004 chipset and chipsets based on it running over + USB. If you choose to build it as a module, it will be + called ath6kl_usb. config ATH6KL_DEBUG bool "Atheros ath6kl debugging" depends on ATH6KL ---help--- - Enables debug support + Enables ath6kl debug support, including debug messages + enabled with debug_mask module parameter and debugfs + interface. + + If unsure, say Y to make it easier to debug problems. + +config ATH6KL_TRACING + bool "Atheros ath6kl tracing support" + depends on ATH6KL + depends on EVENT_TRACING + ---help--- + Select this to ath6kl use tracing infrastructure which, for + example, can be enabled with help of trace-cmd. All debug + messages and commands are delivered to using individually + enablable trace points. + + If unsure, say Y to make it easier to debug problems. + +config ATH6KL_REGDOMAIN + bool "Atheros ath6kl regdomain support" + depends on ATH6KL + depends on CFG80211_CERTIFICATION_ONUS + ---help--- + Enabling this makes it possible to change the regdomain in + the firmware. This can be only enabled if regulatory requirements + are taken into account. + + If unsure, say N. diff --git a/drivers/net/wireless/ath/ath6kl/Makefile b/drivers/net/wireless/ath/ath6kl/Makefile index 70706930355..dc2b3b46781 100644 --- a/drivers/net/wireless/ath/ath6kl/Makefile +++ b/drivers/net/wireless/ath/ath6kl/Makefile @@ -1,5 +1,6 @@ #------------------------------------------------------------------------------ -# Copyright (c) 2004-2010 Atheros Communications Inc. +# Copyright (c) 2004-2011 Atheros Communications Inc. +# Copyright (c) 2011-2012 Qualcomm Atheros, Inc. # All rights reserved. # # @@ -21,17 +22,28 @@ # Author(s): ="Atheros" #------------------------------------------------------------------------------ -obj-$(CONFIG_ATH6KL) := ath6kl.o -ath6kl-y += debug.o -ath6kl-y += hif.o -ath6kl-y += htc.o -ath6kl-y += bmi.o -ath6kl-y += cfg80211.o -ath6kl-y += init.o -ath6kl-y += main.o -ath6kl-y += txrx.o -ath6kl-y += wmi.o -ath6kl-y += sdio.o -ath6kl-$(CONFIG_NL80211_TESTMODE) += testmode.o +obj-$(CONFIG_ATH6KL) += ath6kl_core.o +ath6kl_core-y += debug.o +ath6kl_core-y += hif.o +ath6kl_core-y += htc_mbox.o +ath6kl_core-y += htc_pipe.o +ath6kl_core-y += bmi.o +ath6kl_core-y += cfg80211.o +ath6kl_core-y += init.o +ath6kl_core-y += main.o +ath6kl_core-y += txrx.o +ath6kl_core-y += wmi.o +ath6kl_core-y += core.o +ath6kl_core-y += recovery.o -ccflags-y += -D__CHECK_ENDIAN__ +ath6kl_core-$(CONFIG_NL80211_TESTMODE) += testmode.o +ath6kl_core-$(CONFIG_ATH6KL_TRACING) += trace.o + +obj-$(CONFIG_ATH6KL_SDIO) += ath6kl_sdio.o +ath6kl_sdio-y += sdio.o + +obj-$(CONFIG_ATH6KL_USB) += ath6kl_usb.o +ath6kl_usb-y += usb.o + +# for tracing framework to find trace.h +CFLAGS_trace.o := -I$(src) diff --git a/drivers/net/wireless/ath/ath6kl/bmi.c b/drivers/net/wireless/ath/ath6kl/bmi.c index bce3575c310..334dbd834b3 100644 --- a/drivers/net/wireless/ath/ath6kl/bmi.c +++ b/drivers/net/wireless/ath/ath6kl/bmi.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -57,8 +58,14 @@ int ath6kl_bmi_get_target_info(struct ath6kl *ar, return ret; } - ret = ath6kl_hif_bmi_read(ar, (u8 *)&targ_info->version, - sizeof(targ_info->version)); + if (ar->hif_type == ATH6KL_HIF_TYPE_USB) { + ret = ath6kl_hif_bmi_read(ar, (u8 *)targ_info, + sizeof(*targ_info)); + } else { + ret = ath6kl_hif_bmi_read(ar, (u8 *)&targ_info->version, + sizeof(targ_info->version)); + } + if (ret) { ath6kl_err("Unable to recv target info: %d\n", ret); return ret; @@ -99,7 +106,7 @@ int ath6kl_bmi_get_target_info(struct ath6kl *ar, } ath6kl_dbg(ATH6KL_DBG_BMI, "target info (ver: 0x%x type: 0x%x)\n", - targ_info->version, targ_info->type); + targ_info->version, targ_info->type); return 0; } @@ -186,7 +193,7 @@ int ath6kl_bmi_write(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) memset(ar->bmi.cmd_buf, 0, ar->bmi.max_data_size + header); ath6kl_dbg(ATH6KL_DBG_BMI, - "bmi write memory: addr: 0x%x, len: %d\n", addr, len); + "bmi write memory: addr: 0x%x, len: %d\n", addr, len); len_remain = len; while (len_remain) { @@ -428,7 +435,7 @@ int ath6kl_bmi_lz_data(struct ath6kl *ar, u8 *buf, u32 len) memcpy(&(ar->bmi.cmd_buf[offset]), &tx_len, sizeof(tx_len)); offset += sizeof(tx_len); memcpy(&(ar->bmi.cmd_buf[offset]), &buf[len - len_remain], - tx_len); + tx_len); offset += tx_len; ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); diff --git a/drivers/net/wireless/ath/ath6kl/bmi.h b/drivers/net/wireless/ath/ath6kl/bmi.h index f1ca6812456..18fdd69e1f7 100644 --- a/drivers/net/wireless/ath/ath6kl/bmi.h +++ b/drivers/net/wireless/ath/ath6kl/bmi.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -222,6 +223,29 @@ struct ath6kl_bmi_target_info { __le32 type; /* target type */ } __packed; +#define ath6kl_bmi_write_hi32(ar, item, val) \ + ({ \ + u32 addr; \ + __le32 v; \ + \ + addr = ath6kl_get_hi_item_addr(ar, HI_ITEM(item)); \ + v = cpu_to_le32(val); \ + ath6kl_bmi_write(ar, addr, (u8 *) &v, sizeof(v)); \ + }) + +#define ath6kl_bmi_read_hi32(ar, item, val) \ + ({ \ + u32 addr, *check_type = val; \ + __le32 tmp; \ + int ret; \ + \ + (void) (check_type == val); \ + addr = ath6kl_get_hi_item_addr(ar, HI_ITEM(item)); \ + ret = ath6kl_bmi_read(ar, addr, (u8 *) &tmp, 4); \ + *val = le32_to_cpu(tmp); \ + ret; \ + }) + int ath6kl_bmi_init(struct ath6kl *ar); void ath6kl_bmi_cleanup(struct ath6kl *ar); void ath6kl_bmi_reset(struct ath6kl *ar); diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index 6c59a217b1a..0e26f4a34fd 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,7 +15,11 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/moduleparam.h> +#include <linux/inetdevice.h> +#include <linux/export.h> #include "core.h" #include "cfg80211.h" @@ -22,10 +27,6 @@ #include "hif-ops.h" #include "testmode.h" -static unsigned int ath6kl_p2p; - -module_param(ath6kl_p2p, uint, 0644); - #define RATETAB_ENT(_rate, _rateid, _flags) { \ .bitrate = (_rate), \ .flags = (_flags), \ @@ -50,6 +51,13 @@ module_param(ath6kl_p2p, uint, 0644); .max_power = 30, \ } +#define DEFAULT_BG_SCAN_PERIOD 60 + +struct ath6kl_cfg80211_match_probe_ssid { + struct cfg80211_ssid ssid; + u8 flag; +}; + static struct ieee80211_rate ath6kl_rates[] = { RATETAB_ENT(10, 0x1, 0), RATETAB_ENT(20, 0x2, 0), @@ -70,6 +78,11 @@ static struct ieee80211_rate ath6kl_rates[] = { #define ath6kl_g_rates (ath6kl_rates + 0) #define ath6kl_g_rates_size 12 +#define ath6kl_g_htcap IEEE80211_HT_CAP_SGI_20 +#define ath6kl_a_htcap (IEEE80211_HT_CAP_SUP_WIDTH_20_40 | \ + IEEE80211_HT_CAP_SGI_20 | \ + IEEE80211_HT_CAP_SGI_40) + static struct ieee80211_channel ath6kl_2ghz_channels[] = { CHAN2G(1, 2412, 0), CHAN2G(2, 2417, 0), @@ -114,6 +127,8 @@ static struct ieee80211_supported_band ath6kl_band_2ghz = { .channels = ath6kl_2ghz_channels, .n_bitrates = ath6kl_g_rates_size, .bitrates = ath6kl_g_rates, + .ht_cap.cap = ath6kl_g_htcap, + .ht_cap.ht_supported = true, }; static struct ieee80211_supported_band ath6kl_band_5ghz = { @@ -121,6 +136,8 @@ static struct ieee80211_supported_band ath6kl_band_5ghz = { .channels = ath6kl_5ghz_a_channels, .n_bitrates = ath6kl_a_rates_size, .bitrates = ath6kl_a_rates, + .ht_cap.cap = ath6kl_a_htcap, + .ht_cap.ht_supported = true, }; #define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */ @@ -130,15 +147,15 @@ static bool __ath6kl_cfg80211_sscan_stop(struct ath6kl_vif *vif) { struct ath6kl *ar = vif->ar; - if (ar->state != ATH6KL_STATE_SCHED_SCAN) + if (!test_and_clear_bit(SCHED_SCANNING, &vif->flags)) return false; del_timer_sync(&vif->sched_scan_timer); - ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, - ATH6KL_HOST_MODE_AWAKE); + if (ar->state == ATH6KL_STATE_RECOVERY) + return true; - ar->state = ATH6KL_STATE_ON; + ath6kl_wmi_enable_sched_scan_cmd(ar->wmi, vif->fw_vif_idx, false); return true; } @@ -196,7 +213,7 @@ static int ath6kl_set_auth_type(struct ath6kl_vif *vif, break; default: - ath6kl_err("%s: 0x%x not spported\n", __func__, auth_type); + ath6kl_err("%s: 0x%x not supported\n", __func__, auth_type); return -ENOTSUPP; } @@ -284,7 +301,7 @@ static bool ath6kl_cfg80211_ready(struct ath6kl_vif *vif) static bool ath6kl_is_wpa_ie(const u8 *pos) { - return pos[0] == WLAN_EID_WPA && pos[1] >= 4 && + return pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 && pos[2] == 0x00 && pos[3] == 0x50 && pos[4] == 0xf2 && pos[5] == 0x01; } @@ -352,17 +369,13 @@ static int ath6kl_nliftype_to_drv_iftype(enum nl80211_iftype type, u8 *nw_type) { switch (type) { case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: *nw_type = INFRA_NETWORK; break; case NL80211_IFTYPE_ADHOC: *nw_type = ADHOC_NETWORK; break; case NL80211_IFTYPE_AP: - *nw_type = AP_NETWORK; - break; - case NL80211_IFTYPE_P2P_CLIENT: - *nw_type = INFRA_NETWORK; - break; case NL80211_IFTYPE_P2P_GO: *nw_type = AP_NETWORK; break; @@ -383,13 +396,13 @@ static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type, return false; if (ar->ibss_if_active || ((type == NL80211_IFTYPE_ADHOC) && - ar->num_vif)) + ar->num_vif)) return false; if (type == NL80211_IFTYPE_STATION || type == NL80211_IFTYPE_AP || type == NL80211_IFTYPE_ADHOC) { for (i = 0; i < ar->vif_max; i++) { - if ((ar->avail_idx_map >> i) & BIT(0)) { + if ((ar->avail_idx_map) & BIT(i)) { *if_idx = i; return true; } @@ -399,7 +412,7 @@ static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type, if (type == NL80211_IFTYPE_P2P_CLIENT || type == NL80211_IFTYPE_P2P_GO) { for (i = ar->max_norm_iface; i < ar->vif_max; i++) { - if ((ar->avail_idx_map >> i) & BIT(0)) { + if ((ar->avail_idx_map) & BIT(i)) { *if_idx = i; return true; } @@ -409,6 +422,36 @@ static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type, return false; } +static bool ath6kl_is_tx_pending(struct ath6kl *ar) +{ + return ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)] == 0; +} + +static void ath6kl_cfg80211_sta_bmiss_enhance(struct ath6kl_vif *vif, + bool enable) +{ + int err; + + if (WARN_ON(!test_bit(WMI_READY, &vif->ar->flag))) + return; + + if (vif->nw_type != INFRA_NETWORK) + return; + + if (!test_bit(ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, + vif->ar->fw_capabilities)) + return; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s fw bmiss enhance\n", + enable ? "enable" : "disable"); + + err = ath6kl_wmi_sta_bmiss_enhance_cmd(vif->ar->wmi, + vif->fw_vif_idx, enable); + if (err) + ath6kl_err("failed to %s enhanced bmiss detection: %d\n", + enable ? "enable" : "disable", err); +} + static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_connect_params *sme) { @@ -416,6 +459,7 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, struct ath6kl_vif *vif = netdev_priv(dev); int status; u8 nw_subtype = (ar->p2p) ? SUBTYPE_P2PDEV : SUBTYPE_NONE; + u16 interval; ath6kl_cfg80211_sscan_disable(vif); @@ -452,8 +496,8 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, * sleep until the command queue drains */ wait_event_interruptible_timeout(ar->event_wq, - ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)] == 0, - WMI_TIMEOUT); + ath6kl_is_tx_pending(ar), + WMI_TIMEOUT); if (signal_pending(current)) { ath6kl_err("cmd queue drain timeout\n"); up(&ar->sem); @@ -461,13 +505,13 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, } } - if (sme->ie && (sme->ie_len > 0)) { - status = ath6kl_set_assoc_req_ies(vif, sme->ie, sme->ie_len); - if (status) { - up(&ar->sem); - return status; - } - } else + status = ath6kl_set_assoc_req_ies(vif, sme->ie, sme->ie_len); + if (status) { + up(&ar->sem); + return status; + } + + if (sme->ie == NULL || sme->ie_len == 0) ar->connect_ctrl_flags &= ~CONNECT_WPS_FLAG; if (test_bit(CONNECTED, &vif->flags) && @@ -523,8 +567,7 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, (vif->prwise_crypto == WEP_CRYPT)) { struct ath6kl_key *key = NULL; - if (sme->key_idx < WMI_MIN_KEY_INDEX || - sme->key_idx > WMI_MAX_KEY_INDEX) { + if (sme->key_idx > WMI_MAX_KEY_INDEX) { ath6kl_err("key index %d out of bounds\n", sme->key_idx); up(&ar->sem); @@ -549,7 +592,7 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, if (!ar->usr_bss_filter) { clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); if (ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, - ALL_BSS_FILTER, 0) != 0) { + ALL_BSS_FILTER, 0) != 0) { ath6kl_err("couldn't set bss filtering\n"); up(&ar->sem); return -EIO; @@ -558,6 +601,9 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, vif->nw_type = vif->next_mode; + /* enable enhanced bmiss detection if applicable */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, true); + if (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) nw_subtype = SUBTYPE_P2PCLIENT; @@ -571,6 +617,20 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, vif->grp_crypto_len, vif->ch_hint); vif->reconnect_flag = 0; + + if (vif->nw_type == INFRA_NETWORK) { + interval = max_t(u16, vif->listen_intvl_t, + ATH6KL_MAX_WOW_LISTEN_INTL); + status = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + interval, + 0); + if (status) { + ath6kl_err("couldn't set listen intervel\n"); + up(&ar->sem); + return status; + } + } + status = ath6kl_wmi_connect_cmd(ar->wmi, vif->fw_vif_idx, vif->nw_type, vif->dot11_auth_mode, vif->auth_mode, vif->prwise_crypto, @@ -580,6 +640,17 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, vif->req_bssid, vif->ch_hint, ar->connect_ctrl_flags, nw_subtype); + if (sme->bg_scan_period == 0) { + /* disable background scan if period is 0 */ + sme->bg_scan_period = 0xffff; + } else if (sme->bg_scan_period == -1) { + /* configure default value if not specified */ + sme->bg_scan_period = DEFAULT_BG_SCAN_PERIOD; + } + + ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, 0, 0, + sme->bg_scan_period, 0, 0, 0, 3, 0, 0, 0); + up(&ar->sem); if (status == -EINVAL) { @@ -593,8 +664,8 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, } if ((!(ar->connect_ctrl_flags & CONNECT_DO_WPA_OFFLOAD)) && - ((vif->auth_mode == WPA_PSK_AUTH) - || (vif->auth_mode == WPA2_PSK_AUTH))) { + ((vif->auth_mode == WPA_PSK_AUTH) || + (vif->auth_mode == WPA2_PSK_AUTH))) { mod_timer(&vif->disconnect_timer, jiffies + msecs_to_jiffies(DISCON_TIMER_INTVAL)); } @@ -605,11 +676,13 @@ static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, - enum network_type nw_type, - const u8 *bssid, - struct ieee80211_channel *chan, - const u8 *beacon_ie, size_t beacon_ie_len) +static struct cfg80211_bss * +ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, + enum network_type nw_type, + const u8 *bssid, + struct ieee80211_channel *chan, + const u8 *beacon_ie, + size_t beacon_ie_len) { struct ath6kl *ar = vif->ar; struct cfg80211_bss *bss; @@ -638,7 +711,7 @@ static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, */ ie = kmalloc(2 + vif->ssid_len + beacon_ie_len, GFP_KERNEL); if (ie == NULL) - return -ENOMEM; + return NULL; ie[0] = WLAN_EID_SSID; ie[1] = vif->ssid_len; memcpy(ie + 2, vif->ssid, vif->ssid_len); @@ -648,19 +721,14 @@ static int ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, ie, 2 + vif->ssid_len + beacon_ie_len, 0, GFP_KERNEL); if (bss) - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "added bss %pM to " - "cfg80211\n", bssid); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "added bss %pM to cfg80211\n", bssid); kfree(ie); - } else - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "cfg80211 already has a bss " - "entry\n"); - - if (bss == NULL) - return -ENOMEM; - - cfg80211_put_bss(bss); + } else { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "cfg80211 already has a bss\n"); + } - return 0; + return bss; } void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, @@ -672,6 +740,7 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, { struct ieee80211_channel *chan; struct ath6kl *ar = vif->ar; + struct cfg80211_bss *bss; /* capinfo + listen interval */ u8 assoc_req_ie_offset = sizeof(u16) + sizeof(u16); @@ -712,8 +781,9 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, chan = ieee80211_get_channel(ar->wiphy, (int) channel); - if (ath6kl_add_bss_if_needed(vif, nw_type, bssid, chan, assoc_info, - beacon_ie_len) < 0) { + bss = ath6kl_add_bss_if_needed(vif, nw_type, bssid, chan, + assoc_info, beacon_ie_len); + if (!bss) { ath6kl_err("could not add cfg80211 bss entry\n"); return; } @@ -721,7 +791,8 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, if (nw_type & ADHOC_NETWORK) { ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "ad-hoc %s selected\n", nw_type & ADHOC_CREATOR ? "creator" : "joiner"); - cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL); + cfg80211_ibss_joined(vif->ndev, bssid, chan, GFP_KERNEL); + cfg80211_put_bss(ar->wiphy, bss); return; } @@ -732,11 +803,11 @@ void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, assoc_req_ie, assoc_req_len, assoc_resp_ie, assoc_resp_len, WLAN_STATUS_SUCCESS, GFP_KERNEL); + cfg80211_put_bss(ar->wiphy, bss); } else if (vif->sme_state == SME_CONNECTED) { /* inform roam event to cfg80211 */ - cfg80211_roamed(vif->ndev, chan, bssid, - assoc_req_ie, assoc_req_len, - assoc_resp_ie, assoc_resp_len, GFP_KERNEL); + cfg80211_roamed_bss(vif->ndev, bss, assoc_req_ie, assoc_req_len, + assoc_resp_ie, assoc_resp_len, GFP_KERNEL); } } @@ -791,13 +862,9 @@ void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason, } if (vif->nw_type & ADHOC_NETWORK) { - if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) { + if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: ath6k not in ibss mode\n", __func__); - return; - } - memset(bssid, 0, ETH_ALEN); - cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL); return; } @@ -810,41 +877,116 @@ void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason, } } + clear_bit(CONNECT_PEND, &vif->flags); + + if (vif->sme_state == SME_CONNECTING) { + cfg80211_connect_result(vif->ndev, + bssid, NULL, 0, + NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + } else if (vif->sme_state == SME_CONNECTED) { + cfg80211_disconnected(vif->ndev, proto_reason, + NULL, 0, GFP_KERNEL); + } + + vif->sme_state = SME_DISCONNECTED; + /* * Send a disconnect command to target when a disconnect event is * received with reason code other than 3 (DISCONNECT_CMD - disconnect * request from host) to make the firmware stop trying to connect even * after giving disconnect event. There will be one more disconnect * event for this disconnect command with reason code DISCONNECT_CMD - * which will be notified to cfg80211. + * which won't be notified to cfg80211. */ - - if (reason != DISCONNECT_CMD) { + if (reason != DISCONNECT_CMD) ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx); - return; +} + +static int ath6kl_set_probed_ssids(struct ath6kl *ar, + struct ath6kl_vif *vif, + struct cfg80211_ssid *ssids, int n_ssids, + struct cfg80211_match_set *match_set, + int n_match_ssid) +{ + u8 i, j, index_to_add, ssid_found = false; + struct ath6kl_cfg80211_match_probe_ssid ssid_list[MAX_PROBED_SSIDS]; + + memset(ssid_list, 0, sizeof(ssid_list)); + + if (n_ssids > MAX_PROBED_SSIDS || + n_match_ssid > MAX_PROBED_SSIDS) + return -EINVAL; + + for (i = 0; i < n_ssids; i++) { + memcpy(ssid_list[i].ssid.ssid, + ssids[i].ssid, + ssids[i].ssid_len); + ssid_list[i].ssid.ssid_len = ssids[i].ssid_len; + + if (ssids[i].ssid_len) + ssid_list[i].flag = SPECIFIC_SSID_FLAG; + else + ssid_list[i].flag = ANY_SSID_FLAG; + + if (n_match_ssid == 0) + ssid_list[i].flag |= MATCH_SSID_FLAG; } - clear_bit(CONNECT_PEND, &vif->flags); + index_to_add = i; - if (vif->sme_state == SME_CONNECTING) { - cfg80211_connect_result(vif->ndev, - bssid, NULL, 0, - NULL, 0, - WLAN_STATUS_UNSPECIFIED_FAILURE, - GFP_KERNEL); - } else if (vif->sme_state == SME_CONNECTED) { - cfg80211_disconnected(vif->ndev, reason, - NULL, 0, GFP_KERNEL); + for (i = 0; i < n_match_ssid; i++) { + ssid_found = false; + + for (j = 0; j < n_ssids; j++) { + if ((match_set[i].ssid.ssid_len == + ssid_list[j].ssid.ssid_len) && + (!memcmp(ssid_list[j].ssid.ssid, + match_set[i].ssid.ssid, + match_set[i].ssid.ssid_len))) { + ssid_list[j].flag |= MATCH_SSID_FLAG; + ssid_found = true; + break; + } + } + + if (ssid_found) + continue; + + if (index_to_add >= MAX_PROBED_SSIDS) + continue; + + ssid_list[index_to_add].ssid.ssid_len = + match_set[i].ssid.ssid_len; + memcpy(ssid_list[index_to_add].ssid.ssid, + match_set[i].ssid.ssid, + match_set[i].ssid.ssid_len); + ssid_list[index_to_add].flag |= MATCH_SSID_FLAG; + index_to_add++; } - vif->sme_state = SME_DISCONNECTED; + for (i = 0; i < index_to_add; i++) { + ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, i, + ssid_list[i].flag, + ssid_list[i].ssid.ssid_len, + ssid_list[i].ssid.ssid); + } + + /* Make sure no old entries are left behind */ + for (i = index_to_add; i < MAX_PROBED_SSIDS; i++) { + ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, i, + DISABLE_SSID_FLAG, 0, NULL); + } + + return 0; } -static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, +static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) { - struct ath6kl *ar = ath6kl_priv(ndev); - struct ath6kl_vif *vif = netdev_priv(ndev); + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(request->wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); s8 n_channels = 0; u16 *channels = NULL; int ret = 0; @@ -857,42 +999,26 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, if (!ar->usr_bss_filter) { clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); - ret = ath6kl_wmi_bssfilter_cmd( - ar->wmi, vif->fw_vif_idx, - (test_bit(CONNECTED, &vif->flags) ? - ALL_BUT_BSS_FILTER : ALL_BSS_FILTER), 0); + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + ALL_BSS_FILTER, 0); if (ret) { ath6kl_err("couldn't set bss filtering\n"); return ret; } } - if (request->n_ssids && request->ssids[0].ssid_len) { - u8 i; - - if (request->n_ssids > (MAX_PROBED_SSID_INDEX - 1)) - request->n_ssids = MAX_PROBED_SSID_INDEX - 1; - - for (i = 0; i < request->n_ssids; i++) - ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, - i + 1, SPECIFIC_SSID_FLAG, - request->ssids[i].ssid_len, - request->ssids[i].ssid); - } + ret = ath6kl_set_probed_ssids(ar, vif, request->ssids, + request->n_ssids, NULL, 0); + if (ret < 0) + return ret; - /* - * FIXME: we should clear the IE in fw if it's not set so just - * remove the check altogether - */ - if (request->ie) { - ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, - WMI_FRAME_PROBE_REQ, - request->ie, request->ie_len); - if (ret) { - ath6kl_err("failed to set Probe Request appie for " - "scan"); - return ret; - } + /* this also clears IE in fw if it's not set */ + ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_PROBE_REQ, + request->ie, request->ie_len); + if (ret) { + ath6kl_err("failed to set Probe Request appie for scan\n"); + return ret; } /* @@ -908,8 +1034,7 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, channels = kzalloc(n_channels * sizeof(u16), GFP_KERNEL); if (channels == NULL) { - ath6kl_warn("failed to set scan channels, " - "scan all channels"); + ath6kl_warn("failed to set scan channels, scan all channels"); n_channels = 0; } @@ -920,29 +1045,19 @@ static int ath6kl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, if (test_bit(CONNECTED, &vif->flags)) force_fg_scan = 1; - if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, - ar->fw_capabilities)) { - /* - * If capable of doing P2P mgmt operations using - * station interface, send additional information like - * supported rates to advertise and xmit rates for - * probe requests - */ - ret = ath6kl_wmi_beginscan_cmd(ar->wmi, vif->fw_vif_idx, - WMI_LONG_SCAN, force_fg_scan, - false, 0, 0, n_channels, - channels, request->no_cck, - request->rates); - } else { - ret = ath6kl_wmi_startscan_cmd(ar->wmi, vif->fw_vif_idx, - WMI_LONG_SCAN, force_fg_scan, - false, 0, 0, n_channels, - channels); + vif->scan_req = request; + + ret = ath6kl_wmi_beginscan_cmd(ar->wmi, vif->fw_vif_idx, + WMI_LONG_SCAN, force_fg_scan, + false, 0, + ATH6KL_FG_SCAN_INTERVAL, + n_channels, channels, + request->no_cck, + request->rates); + if (ret) { + ath6kl_err("failed to start scan: %d\n", ret); + vif->scan_req = NULL; } - if (ret) - ath6kl_err("wmi_startscan_cmd failed\n"); - else - vif->scan_req = request; kfree(channels); @@ -976,6 +1091,25 @@ out: vif->scan_req = NULL; } +void ath6kl_cfg80211_ch_switch_notify(struct ath6kl_vif *vif, int freq, + enum wmi_phy_mode mode) +{ + struct cfg80211_chan_def chandef; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "channel switch notify nw_type %d freq %d mode %d\n", + vif->nw_type, freq, mode); + + cfg80211_chandef_create(&chandef, + ieee80211_get_channel(vif->ar->wiphy, freq), + (mode == WMI_11G_HT20) ? + NL80211_CHAN_HT20 : NL80211_CHAN_NO_HT); + + mutex_lock(&vif->wdev.mtx); + cfg80211_ch_switch_notify(vif->ndev, &chandef); + mutex_unlock(&vif->wdev.mtx); +} + static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, u8 key_index, bool pairwise, const u8 *mac_addr, @@ -984,6 +1118,7 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, struct ath6kl *ar = ath6kl_priv(ndev); struct ath6kl_vif *vif = netdev_priv(ndev); struct ath6kl_key *key = NULL; + int seq_len; u8 key_usage; u8 key_type; @@ -997,7 +1132,7 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, params->key); } - if (key_index < WMI_MIN_KEY_INDEX || key_index > WMI_MAX_KEY_INDEX) { + if (key_index > WMI_MAX_KEY_INDEX) { ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: key index %d out of bounds\n", __func__, key_index); @@ -1012,23 +1147,21 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, else key_usage = GROUP_USAGE; - if (params) { - int seq_len = params->seq_len; - if (params->cipher == WLAN_CIPHER_SUITE_SMS4 && - seq_len > ATH6KL_KEY_SEQ_LEN) { - /* Only first half of the WPI PN is configured */ - seq_len = ATH6KL_KEY_SEQ_LEN; - } - if (params->key_len > WLAN_MAX_KEY_LEN || - seq_len > sizeof(key->seq)) - return -EINVAL; - - key->key_len = params->key_len; - memcpy(key->key, params->key, key->key_len); - key->seq_len = seq_len; - memcpy(key->seq, params->seq, key->seq_len); - key->cipher = params->cipher; + seq_len = params->seq_len; + if (params->cipher == WLAN_CIPHER_SUITE_SMS4 && + seq_len > ATH6KL_KEY_SEQ_LEN) { + /* Only first half of the WPI PN is configured */ + seq_len = ATH6KL_KEY_SEQ_LEN; } + if (params->key_len > WLAN_MAX_KEY_LEN || + seq_len > sizeof(key->seq)) + return -EINVAL; + + key->key_len = params->key_len; + memcpy(key->key, params->key, key->key_len); + key->seq_len = seq_len; + memcpy(key->seq, params->seq, key->seq_len); + key->cipher = params->cipher; switch (key->cipher) { case WLAN_CIPHER_SUITE_WEP40: @@ -1051,9 +1184,9 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, return -ENOTSUPP; } - if (((vif->auth_mode == WPA_PSK_AUTH) - || (vif->auth_mode == WPA2_PSK_AUTH)) - && (key_usage & GROUP_USAGE)) + if (((vif->auth_mode == WPA_PSK_AUTH) || + (vif->auth_mode == WPA2_PSK_AUTH)) && + (key_usage & GROUP_USAGE)) del_timer(&vif->disconnect_timer); ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, @@ -1063,16 +1196,15 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, if (vif->nw_type == AP_NETWORK && !pairwise && (key_type == TKIP_CRYPT || key_type == AES_CRYPT || - key_type == WAPI_CRYPT) && params) { + key_type == WAPI_CRYPT)) { ar->ap_mode_bkey.valid = true; ar->ap_mode_bkey.key_index = key_index; ar->ap_mode_bkey.key_type = key_type; ar->ap_mode_bkey.key_len = key->key_len; memcpy(ar->ap_mode_bkey.key, key->key, key->key_len); if (!test_bit(CONNECTED, &vif->flags)) { - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delay initial group " - "key configuration until AP mode has been " - "started\n"); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delay initial group key configuration until AP mode has been started\n"); /* * The key will be set in ath6kl_connect_ap_mode() once * the connected event is received from the target. @@ -1088,8 +1220,8 @@ static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, * the AP mode has properly started * (ath6kl_install_statioc_wep_keys). */ - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delay WEP key configuration " - "until AP mode has been started\n"); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delay WEP key configuration until AP mode has been started\n"); vif->wep_key_list[key_index].key_len = key->key_len; memcpy(vif->wep_key_list[key_index].key, key->key, key->key_len); @@ -1115,7 +1247,7 @@ static int ath6kl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, if (!ath6kl_cfg80211_ready(vif)) return -EIO; - if (key_index < WMI_MIN_KEY_INDEX || key_index > WMI_MAX_KEY_INDEX) { + if (key_index > WMI_MAX_KEY_INDEX) { ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: key index %d out of bounds\n", __func__, key_index); @@ -1148,7 +1280,7 @@ static int ath6kl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, if (!ath6kl_cfg80211_ready(vif)) return -EIO; - if (key_index < WMI_MIN_KEY_INDEX || key_index > WMI_MAX_KEY_INDEX) { + if (key_index > WMI_MAX_KEY_INDEX) { ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: key index %d out of bounds\n", __func__, key_index); @@ -1184,7 +1316,7 @@ static int ath6kl_cfg80211_set_default_key(struct wiphy *wiphy, if (!ath6kl_cfg80211_ready(vif)) return -EIO; - if (key_index < WMI_MIN_KEY_INDEX || key_index > WMI_MAX_KEY_INDEX) { + if (key_index > WMI_MAX_KEY_INDEX) { ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: key index %d out of bounds\n", __func__, key_index); @@ -1258,17 +1390,13 @@ static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) return 0; } -/* - * The type nl80211_tx_power_setting replaces the following - * data type from 2.6.36 onwards -*/ static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, + struct wireless_dev *wdev, enum nl80211_tx_power_setting type, int mbm) { struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); struct ath6kl_vif *vif; - u8 ath6kl_dbm; int dbm = MBM_TO_DBM(mbm); ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x, dbm %d\n", __func__, @@ -1285,7 +1413,7 @@ static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, case NL80211_TX_POWER_AUTOMATIC: return 0; case NL80211_TX_POWER_LIMITED: - ar->tx_pwr = ath6kl_dbm = dbm; + ar->tx_pwr = dbm; break; default: ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x not supported\n", @@ -1293,12 +1421,14 @@ static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, return -EOPNOTSUPP; } - ath6kl_wmi_set_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx, ath6kl_dbm); + ath6kl_wmi_set_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx, dbm); return 0; } -static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy, int *dbm) +static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy, + struct wireless_dev *wdev, + int *dbm) { struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); struct ath6kl_vif *vif; @@ -1346,15 +1476,15 @@ static int ath6kl_cfg80211_set_power_mgmt(struct wiphy *wiphy, return -EIO; if (pmgmt) { - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: max perf\n", __func__); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: rec power\n", __func__); mode.pwr_mode = REC_POWER; } else { - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: rec power\n", __func__); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: max perf\n", __func__); mode.pwr_mode = MAX_PERF_POWER; } if (ath6kl_wmi_powermode_cmd(ar->wmi, vif->fw_vif_idx, - mode.pwr_mode) != 0) { + mode.pwr_mode) != 0) { ath6kl_err("wmi_powermode_cmd failed\n"); return -EIO; } @@ -1362,14 +1492,14 @@ static int ath6kl_cfg80211_set_power_mgmt(struct wiphy *wiphy, return 0; } -static struct net_device *ath6kl_cfg80211_add_iface(struct wiphy *wiphy, - char *name, - enum nl80211_iftype type, - u32 *flags, - struct vif_params *params) +static struct wireless_dev *ath6kl_cfg80211_add_iface(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) { struct ath6kl *ar = wiphy_priv(wiphy); - struct net_device *ndev; + struct wireless_dev *wdev; u8 if_idx, nw_type; if (ar->num_vif == ar->vif_max) { @@ -1382,28 +1512,30 @@ static struct net_device *ath6kl_cfg80211_add_iface(struct wiphy *wiphy, return ERR_PTR(-EINVAL); } - ndev = ath6kl_interface_add(ar, name, type, if_idx, nw_type); - if (!ndev) + wdev = ath6kl_interface_add(ar, name, type, if_idx, nw_type); + if (!wdev) return ERR_PTR(-ENOMEM); ar->num_vif++; - return ndev; + return wdev; } static int ath6kl_cfg80211_del_iface(struct wiphy *wiphy, - struct net_device *ndev) + struct wireless_dev *wdev) { struct ath6kl *ar = wiphy_priv(wiphy); - struct ath6kl_vif *vif = netdev_priv(ndev); + struct ath6kl_vif *vif = netdev_priv(wdev->netdev); spin_lock_bh(&ar->list_lock); list_del(&vif->list); spin_unlock_bh(&ar->list_lock); - ath6kl_cleanup_vif(vif, test_bit(WMI_READY, &ar->flag)); + ath6kl_cfg80211_vif_stop(vif, test_bit(WMI_READY, &ar->flag)); - ath6kl_deinit_if_data(vif); + rtnl_lock(); + ath6kl_cfg80211_vif_cleanup(vif); + rtnl_unlock(); return 0; } @@ -1414,22 +1546,50 @@ static int ath6kl_cfg80211_change_iface(struct wiphy *wiphy, struct vif_params *params) { struct ath6kl_vif *vif = netdev_priv(ndev); + int i; ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type %u\n", __func__, type); + /* + * Don't bring up p2p on an interface which is not initialized + * for p2p operation where fw does not have capability to switch + * dynamically between non-p2p and p2p type interface. + */ + if (!test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + vif->ar->fw_capabilities) && + (type == NL80211_IFTYPE_P2P_CLIENT || + type == NL80211_IFTYPE_P2P_GO)) { + if (vif->ar->vif_max == 1) { + if (vif->fw_vif_idx != 0) + return -EINVAL; + else + goto set_iface_type; + } + + for (i = vif->ar->max_norm_iface; i < vif->ar->vif_max; i++) { + if (i == vif->fw_vif_idx) + break; + } + + if (i == vif->ar->vif_max) { + ath6kl_err("Invalid interface to bring up P2P\n"); + return -EINVAL; + } + } + + /* need to clean up enhanced bmiss detection fw state */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, false); + +set_iface_type: switch (type) { case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: vif->next_mode = INFRA_NETWORK; break; case NL80211_IFTYPE_ADHOC: vif->next_mode = ADHOC_NETWORK; break; case NL80211_IFTYPE_AP: - vif->next_mode = AP_NETWORK; - break; - case NL80211_IFTYPE_P2P_CLIENT: - vif->next_mode = INFRA_NETWORK; - break; case NL80211_IFTYPE_P2P_GO: vif->next_mode = AP_NETWORK; break; @@ -1457,8 +1617,8 @@ static int ath6kl_cfg80211_join_ibss(struct wiphy *wiphy, vif->ssid_len = ibss_param->ssid_len; memcpy(vif->ssid, ibss_param->ssid, vif->ssid_len); - if (ibss_param->channel) - vif->ch_hint = ibss_param->channel->center_freq; + if (ibss_param->chandef.chan) + vif->ch_hint = ibss_param->chandef.chan->center_freq; if (ibss_param->channel_fixed) { /* @@ -1599,7 +1759,7 @@ static bool is_rate_ht40(s32 rate, u8 *mcs, bool *sgi) } static int ath6kl_get_station(struct wiphy *wiphy, struct net_device *dev, - u8 *mac, struct station_info *sinfo) + const u8 *mac, struct station_info *sinfo) { struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev); @@ -1638,14 +1798,14 @@ static int ath6kl_get_station(struct wiphy *wiphy, struct net_device *dev, if (vif->target_stats.rx_byte) { sinfo->rx_bytes = vif->target_stats.rx_byte; - sinfo->filled |= STATION_INFO_RX_BYTES; + sinfo->filled |= STATION_INFO_RX_BYTES64; sinfo->rx_packets = vif->target_stats.rx_pkt; sinfo->filled |= STATION_INFO_RX_PACKETS; } if (vif->target_stats.tx_byte) { sinfo->tx_bytes = vif->target_stats.tx_byte; - sinfo->filled |= STATION_INFO_TX_BYTES; + sinfo->filled |= STATION_INFO_TX_BYTES64; sinfo->tx_packets = vif->target_stats.tx_pkt; sinfo->filled |= STATION_INFO_TX_PACKETS; } @@ -1728,31 +1888,15 @@ static int ath6kl_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev) return 0; } -static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +static int ath6kl_wow_usr(struct ath6kl *ar, struct ath6kl_vif *vif, + struct cfg80211_wowlan *wow, u32 *filter) { - struct ath6kl_vif *vif; - int ret, pos, left; - u32 filter = 0; + int ret, pos; + u8 mask[WOW_PATTERN_SIZE]; u16 i; - u8 mask[WOW_MASK_SIZE]; - - vif = ath6kl_vif_first(ar); - if (!vif) - return -EIO; - - if (!ath6kl_cfg80211_ready(vif)) - return -EIO; - - if (!test_bit(CONNECTED, &vif->flags)) - return -EINVAL; - /* Clear existing WOW patterns */ - for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++) - ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx, - WOW_LIST_ID, i); - /* Configure new WOW patterns */ + /* Configure the patterns that we received from the user. */ for (i = 0; i < wow->n_patterns; i++) { - /* * Convert given nl80211 specific mask value to equivalent * driver specific mask value and send it to the chip along @@ -1773,44 +1917,186 @@ static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) * matched from the first byte of received pkt in the firmware. */ ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, - vif->fw_vif_idx, WOW_LIST_ID, - wow->patterns[i].pattern_len, - 0 /* pattern offset */, - wow->patterns[i].pattern, mask); + vif->fw_vif_idx, WOW_LIST_ID, + wow->patterns[i].pattern_len, + 0 /* pattern offset */, + wow->patterns[i].pattern, mask); if (ret) return ret; } if (wow->disconnect) - filter |= WOW_FILTER_OPTION_NWK_DISASSOC; + *filter |= WOW_FILTER_OPTION_NWK_DISASSOC; if (wow->magic_pkt) - filter |= WOW_FILTER_OPTION_MAGIC_PACKET; + *filter |= WOW_FILTER_OPTION_MAGIC_PACKET; if (wow->gtk_rekey_failure) - filter |= WOW_FILTER_OPTION_GTK_ERROR; + *filter |= WOW_FILTER_OPTION_GTK_ERROR; if (wow->eap_identity_req) - filter |= WOW_FILTER_OPTION_EAP_REQ; + *filter |= WOW_FILTER_OPTION_EAP_REQ; if (wow->four_way_handshake) - filter |= WOW_FILTER_OPTION_8021X_4WAYHS; + *filter |= WOW_FILTER_OPTION_8021X_4WAYHS; - ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx, - ATH6KL_WOW_MODE_ENABLE, - filter, - WOW_HOST_REQ_DELAY); - if (ret) + return 0; +} + +static int ath6kl_wow_ap(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + static const u8 unicst_pattern[] = { 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08 }; + static const u8 unicst_mask[] = { 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7f }; + u8 unicst_offset = 0; + static const u8 arp_pattern[] = { 0x08, 0x06 }; + static const u8 arp_mask[] = { 0xff, 0xff }; + u8 arp_offset = 20; + static const u8 discvr_pattern[] = { 0xe0, 0x00, 0x00, 0xf8 }; + static const u8 discvr_mask[] = { 0xf0, 0x00, 0x00, 0xf8 }; + u8 discvr_offset = 38; + static const u8 dhcp_pattern[] = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 /* port 67 */ }; + static const u8 dhcp_mask[] = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff /* port 67 */ }; + u8 dhcp_offset = 0; + int ret; + + /* Setup unicast IP, EAPOL-like and ARP pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(unicst_pattern), unicst_offset, + unicst_pattern, unicst_mask); + if (ret) { + ath6kl_err("failed to add WOW unicast IP pattern\n"); + return ret; + } + + /* Setup all ARP pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(arp_pattern), arp_offset, + arp_pattern, arp_mask); + if (ret) { + ath6kl_err("failed to add WOW ARP pattern\n"); + return ret; + } + + /* + * Setup multicast pattern for mDNS 224.0.0.251, + * SSDP 239.255.255.250 and LLMNR 224.0.0.252 + */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(discvr_pattern), discvr_offset, + discvr_pattern, discvr_mask); + if (ret) { + ath6kl_err("failed to add WOW mDNS/SSDP/LLMNR pattern\n"); return ret; + } + + /* Setup all DHCP broadcast pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(dhcp_pattern), dhcp_offset, + dhcp_pattern, dhcp_mask); + if (ret) { + ath6kl_err("failed to add WOW DHCP broadcast pattern\n"); + return ret; + } + + return 0; +} + +static int ath6kl_wow_sta(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + struct net_device *ndev = vif->ndev; + static const u8 discvr_pattern[] = { 0xe0, 0x00, 0x00, 0xf8 }; + static const u8 discvr_mask[] = { 0xf0, 0x00, 0x00, 0xf8 }; + u8 discvr_offset = 38; + u8 mac_mask[ETH_ALEN]; + int ret; + + /* Setup unicast pkt pattern */ + memset(mac_mask, 0xff, ETH_ALEN); + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + ETH_ALEN, 0, ndev->dev_addr, + mac_mask); + if (ret) { + ath6kl_err("failed to add WOW unicast pattern\n"); + return ret; + } + + /* + * Setup multicast pattern for mDNS 224.0.0.251, + * SSDP 239.255.255.250 and LLMNR 224.0.0.252 + */ + if ((ndev->flags & IFF_ALLMULTI) || + (ndev->flags & IFF_MULTICAST && netdev_mc_count(ndev) > 0)) { + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(discvr_pattern), discvr_offset, + discvr_pattern, discvr_mask); + if (ret) { + ath6kl_err("failed to add WOW mDNS/SSDP/LLMNR pattern\n"); + return ret; + } + } + + return 0; +} + +static int is_hsleep_mode_procsed(struct ath6kl_vif *vif) +{ + return test_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); +} + +static bool is_ctrl_ep_empty(struct ath6kl *ar) +{ + return !ar->tx_pending[ar->ctrl_ep]; +} + +static int ath6kl_cfg80211_host_sleep(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + int ret, left; + + clear_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, ATH6KL_HOST_MODE_ASLEEP); if (ret) return ret; + left = wait_event_interruptible_timeout(ar->event_wq, + is_hsleep_mode_procsed(vif), + WMI_TIMEOUT); + if (left == 0) { + ath6kl_warn("timeout, didn't get host sleep cmd processed event\n"); + ret = -ETIMEDOUT; + } else if (left < 0) { + ath6kl_warn("error while waiting for host sleep cmd processed event %d\n", + left); + ret = left; + } + if (ar->tx_pending[ar->ctrl_ep]) { left = wait_event_interruptible_timeout(ar->event_wq, - ar->tx_pending[ar->ctrl_ep] == 0, WMI_TIMEOUT); + is_ctrl_ep_empty(ar), + WMI_TIMEOUT); if (left == 0) { ath6kl_warn("clear wmi ctrl data timeout\n"); ret = -ETIMEDOUT; @@ -1823,24 +2109,305 @@ static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) return ret; } +static int ath6kl_wow_suspend_vif(struct ath6kl_vif *vif, + struct cfg80211_wowlan *wow, u32 *filter) +{ + struct ath6kl *ar = vif->ar; + struct in_device *in_dev; + struct in_ifaddr *ifa; + int ret; + u16 i, bmiss_time; + __be32 ips[MAX_IP_ADDRS]; + u8 index = 0; + + if (!test_bit(NETDEV_MCAST_ALL_ON, &vif->flags) && + test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + ar->fw_capabilities)) { + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, false); + if (ret) + return ret; + } + + /* Clear existing WOW patterns */ + for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++) + ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx, + WOW_LIST_ID, i); + + /* + * Skip the default WOW pattern configuration + * if the driver receives any WOW patterns from + * the user. + */ + if (wow) + ret = ath6kl_wow_usr(ar, vif, wow, filter); + else if (vif->nw_type == AP_NETWORK) + ret = ath6kl_wow_ap(ar, vif); + else + ret = ath6kl_wow_sta(ar, vif); + + if (ret) + return ret; + + netif_stop_queue(vif->ndev); + + if (vif->nw_type != AP_NETWORK) { + ret = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_MAX_WOW_LISTEN_INTL, + 0); + if (ret) + return ret; + + /* Set listen interval x 15 times as bmiss time */ + bmiss_time = ATH6KL_MAX_WOW_LISTEN_INTL * 15; + if (bmiss_time > ATH6KL_MAX_BMISS_TIME) + bmiss_time = ATH6KL_MAX_BMISS_TIME; + + ret = ath6kl_wmi_bmisstime_cmd(ar->wmi, vif->fw_vif_idx, + bmiss_time, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0xFFFF, 0, 0xFFFF, 0, 0, 0, + 0, 0, 0, 0); + if (ret) + return ret; + } + + /* Setup own IP addr for ARP agent. */ + in_dev = __in_dev_get_rtnl(vif->ndev); + if (!in_dev) + return 0; + + ifa = in_dev->ifa_list; + memset(&ips, 0, sizeof(ips)); + + /* Configure IP addr only if IP address count < MAX_IP_ADDRS */ + while (index < MAX_IP_ADDRS && ifa) { + ips[index] = ifa->ifa_local; + ifa = ifa->ifa_next; + index++; + } + + if (ifa) { + ath6kl_err("total IP addr count is exceeding fw limit\n"); + return -EINVAL; + } + + ret = ath6kl_wmi_set_ip_cmd(ar->wmi, vif->fw_vif_idx, ips[0], ips[1]); + if (ret) { + ath6kl_err("fail to setup ip for arp agent\n"); + return ret; + } + + return ret; +} + +static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + struct ath6kl_vif *first_vif, *vif; + int ret = 0; + u32 filter = 0; + bool connected = false; + + /* enter / leave wow suspend on first vif always */ + first_vif = ath6kl_vif_first(ar); + if (WARN_ON(unlikely(!first_vif)) || + !ath6kl_cfg80211_ready(first_vif)) + return -EIO; + + if (wow && (wow->n_patterns > WOW_MAX_FILTERS_PER_LIST)) + return -EINVAL; + + /* install filters for each connected vif */ + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (!test_bit(CONNECTED, &vif->flags) || + !ath6kl_cfg80211_ready(vif)) + continue; + connected = true; + + ret = ath6kl_wow_suspend_vif(vif, wow, &filter); + if (ret) + break; + } + spin_unlock_bh(&ar->list_lock); + + if (!connected) + return -ENOTCONN; + else if (ret) + return ret; + + ar->state = ATH6KL_STATE_SUSPENDING; + + ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, first_vif->fw_vif_idx, + ATH6KL_WOW_MODE_ENABLE, + filter, + WOW_HOST_REQ_DELAY); + if (ret) + return ret; + + return ath6kl_cfg80211_host_sleep(ar, first_vif); +} + +static int ath6kl_wow_resume_vif(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + int ret; + + if (vif->nw_type != AP_NETWORK) { + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + vif->listen_intvl_t, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_bmisstime_cmd(ar->wmi, vif->fw_vif_idx, + vif->bmiss_time_t, 0); + if (ret) + return ret; + } + + if (!test_bit(NETDEV_MCAST_ALL_OFF, &vif->flags) && + test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + ar->fw_capabilities)) { + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, true); + if (ret) + return ret; + } + + netif_wake_queue(vif->ndev); + + return 0; +} + static int ath6kl_wow_resume(struct ath6kl *ar) { struct ath6kl_vif *vif; int ret; vif = ath6kl_vif_first(ar); - if (!vif) + if (WARN_ON(unlikely(!vif)) || + !ath6kl_cfg80211_ready(vif)) return -EIO; + ar->state = ATH6KL_STATE_RESUMING; + ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, ATH6KL_HOST_MODE_AWAKE); + if (ret) { + ath6kl_warn("Failed to configure host sleep mode for wow resume: %d\n", + ret); + goto cleanup; + } + + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (!test_bit(CONNECTED, &vif->flags) || + !ath6kl_cfg80211_ready(vif)) + continue; + ret = ath6kl_wow_resume_vif(vif); + if (ret) + break; + } + spin_unlock_bh(&ar->list_lock); + + if (ret) + goto cleanup; + + ar->state = ATH6KL_STATE_ON; + return 0; + +cleanup: + ar->state = ATH6KL_STATE_WOW; return ret; } +static int ath6kl_cfg80211_deepsleep_suspend(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + int ret; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + if (!test_bit(WMI_READY, &ar->flag)) { + ath6kl_err("deepsleep failed as wmi is not ready\n"); + return -EIO; + } + + ath6kl_cfg80211_stop_all(ar); + + /* Save the current power mode before enabling power save */ + ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode; + + ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER); + if (ret) + return ret; + + /* Disable WOW mode */ + ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_WOW_MODE_DISABLE, + 0, 0); + if (ret) + return ret; + + /* Flush all non control pkts in TX path */ + ath6kl_tx_data_cleanup(ar); + + ret = ath6kl_cfg80211_host_sleep(ar, vif); + if (ret) + return ret; + + return 0; +} + +static int ath6kl_cfg80211_deepsleep_resume(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + int ret; + + vif = ath6kl_vif_first(ar); + + if (!vif) + return -EIO; + + if (ar->wmi->pwr_mode != ar->wmi->saved_pwr_mode) { + ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, + ar->wmi->saved_pwr_mode); + if (ret) + return ret; + } + + ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_HOST_MODE_AWAKE); + if (ret) + return ret; + + ar->state = ATH6KL_STATE_ON; + + /* Reset scan parameter to default values */ + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0); + if (ret) + return ret; + + return 0; +} + int ath6kl_cfg80211_suspend(struct ath6kl *ar, enum ath6kl_cfg_suspend_mode mode, struct cfg80211_wowlan *wow) { + struct ath6kl_vif *vif; + enum ath6kl_state prev_state; int ret; switch (mode) { @@ -1851,25 +2418,25 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar, /* Flush all non control pkts in TX path */ ath6kl_tx_data_cleanup(ar); + prev_state = ar->state; + ret = ath6kl_wow_suspend(ar, wow); if (ret) { - ath6kl_err("wow suspend failed: %d\n", ret); + ar->state = prev_state; return ret; } + ar->state = ATH6KL_STATE_WOW; break; case ATH6KL_CFG_SUSPEND_DEEPSLEEP: - ath6kl_cfg80211_stop_all(ar); + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "deep sleep suspend\n"); - /* save the current power mode before enabling power save */ - ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode; - - ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER); + ret = ath6kl_cfg80211_deepsleep_suspend(ar); if (ret) { - ath6kl_warn("wmi powermode command failed during suspend: %d\n", - ret); + ath6kl_err("deepsleep suspend failed: %d\n", ret); + return ret; } ar->state = ATH6KL_STATE_DEEPSLEEP; @@ -1898,19 +2465,16 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar, break; - case ATH6KL_CFG_SUSPEND_SCHED_SCAN: - /* - * Nothing needed for schedule scan, firmware is already in - * wow mode and sleeping most of the time. - */ - break; - default: break; } + list_for_each_entry(vif, &ar->vif_list, list) + ath6kl_cfg80211_scan_complete_event(vif, true); + return 0; } +EXPORT_SYMBOL(ath6kl_cfg80211_suspend); int ath6kl_cfg80211_resume(struct ath6kl *ar) { @@ -1926,21 +2490,16 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar) return ret; } - ar->state = ATH6KL_STATE_ON; break; case ATH6KL_STATE_DEEPSLEEP: - if (ar->wmi->pwr_mode != ar->wmi->saved_pwr_mode) { - ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, - ar->wmi->saved_pwr_mode); - if (ret) { - ath6kl_warn("wmi powermode command failed during resume: %d\n", - ret); - } - } - - ar->state = ATH6KL_STATE_ON; + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "deep sleep resume\n"); + ret = ath6kl_cfg80211_deepsleep_resume(ar); + if (ret) { + ath6kl_warn("deep sleep resume failed: %d\n", ret); + return ret; + } break; case ATH6KL_STATE_CUTPOWER: @@ -1953,15 +2512,13 @@ int ath6kl_cfg80211_resume(struct ath6kl *ar) } break; - case ATH6KL_STATE_SCHED_SCAN: - break; - default: break; } return 0; } +EXPORT_SYMBOL(ath6kl_cfg80211_resume); #ifdef CONFIG_PM @@ -1971,14 +2528,23 @@ static int __ath6kl_cfg80211_suspend(struct wiphy *wiphy, { struct ath6kl *ar = wiphy_priv(wiphy); + ath6kl_recovery_suspend(ar); + return ath6kl_hif_suspend(ar, wow); } static int __ath6kl_cfg80211_resume(struct wiphy *wiphy) { struct ath6kl *ar = wiphy_priv(wiphy); + int err; + + err = ath6kl_hif_resume(ar); + if (err) + return err; - return ath6kl_hif_resume(ar); + ath6kl_recovery_resume(ar); + + return 0; } /* @@ -1999,6 +2565,9 @@ static int __ath6kl_cfg80211_resume(struct wiphy *wiphy) */ void ath6kl_check_wow_status(struct ath6kl *ar) { + if (ar->state == ATH6KL_STATE_SUSPENDING) + return; + if (ar->state == ATH6KL_STATE_WOW) ath6kl_cfg80211_resume(ar); } @@ -2010,20 +2579,43 @@ void ath6kl_check_wow_status(struct ath6kl *ar) } #endif -static int ath6kl_set_channel(struct wiphy *wiphy, struct net_device *dev, - struct ieee80211_channel *chan, - enum nl80211_channel_type channel_type) +static int ath6kl_set_htcap(struct ath6kl_vif *vif, enum ieee80211_band band, + bool ht_enable) { - struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl_htcap *htcap = &vif->htcap[band]; - if (!ath6kl_cfg80211_ready(vif)) - return -EIO; + if (htcap->ht_enable == ht_enable) + return 0; - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: center_freq=%u hw_value=%u\n", - __func__, chan->center_freq, chan->hw_value); - vif->next_chan = chan->center_freq; + if (ht_enable) { + /* Set default ht capabilities */ + htcap->ht_enable = true; + htcap->cap_info = (band == IEEE80211_BAND_2GHZ) ? + ath6kl_g_htcap : ath6kl_a_htcap; + htcap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K; + } else /* Disable ht */ + memset(htcap, 0, sizeof(*htcap)); - return 0; + return ath6kl_wmi_set_htcap_cmd(vif->ar->wmi, vif->fw_vif_idx, + band, htcap); +} + +static int ath6kl_restore_htcap(struct ath6kl_vif *vif) +{ + struct wiphy *wiphy = vif->ar->wiphy; + int band, ret = 0; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + if (!wiphy->bands[band]) + continue; + + ret = ath6kl_set_htcap(vif, band, + wiphy->bands[band]->ht_cap.ht_supported); + if (ret) + return ret; + } + + return ret; } static bool ath6kl_is_p2p_ie(const u8 *pos) @@ -2069,19 +2661,105 @@ static int ath6kl_set_ap_probe_resp_ies(struct ath6kl_vif *vif, return ret; } -static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, - struct beacon_parameters *info, bool add) +static int ath6kl_set_ies(struct ath6kl_vif *vif, + struct cfg80211_beacon_data *info) +{ + struct ath6kl *ar = vif->ar; + int res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_BEACON, + info->beacon_ies, + info->beacon_ies_len); + if (res) + return res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_set_ap_probe_resp_ies(vif, info->proberesp_ies, + info->proberesp_ies_len); + if (res) + return res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_ASSOC_RESP, + info->assocresp_ies, + info->assocresp_ies_len); + if (res) + return res; + + return 0; +} + +static int ath6kl_get_rsn_capab(struct cfg80211_beacon_data *beacon, + u8 *rsn_capab) +{ + const u8 *rsn_ie; + size_t rsn_ie_len; + u16 cnt; + + if (!beacon->tail) + return -EINVAL; + + rsn_ie = cfg80211_find_ie(WLAN_EID_RSN, beacon->tail, beacon->tail_len); + if (!rsn_ie) + return -EINVAL; + + rsn_ie_len = *(rsn_ie + 1); + /* skip element id and length */ + rsn_ie += 2; + + /* skip version */ + if (rsn_ie_len < 2) + return -EINVAL; + rsn_ie += 2; + rsn_ie_len -= 2; + + /* skip group cipher suite */ + if (rsn_ie_len < 4) + return 0; + rsn_ie += 4; + rsn_ie_len -= 4; + + /* skip pairwise cipher suite */ + if (rsn_ie_len < 2) + return 0; + cnt = get_unaligned_le16(rsn_ie); + rsn_ie += (2 + cnt * 4); + rsn_ie_len -= (2 + cnt * 4); + + /* skip akm suite */ + if (rsn_ie_len < 2) + return 0; + cnt = get_unaligned_le16(rsn_ie); + rsn_ie += (2 + cnt * 4); + rsn_ie_len -= (2 + cnt * 4); + + if (rsn_ie_len < 2) + return 0; + + memcpy(rsn_capab, rsn_ie, 2); + + return 0; +} + +static int ath6kl_start_ap(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *info) { struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev); struct ieee80211_mgmt *mgmt; + bool hidden = false; u8 *ies; int ies_len; struct wmi_connect_cmd p; int res; int i, ret; + u16 rsn_capab = 0; + int inactivity_timeout = 0; - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: add=%d\n", __func__, add); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s:\n", __func__); if (!ath6kl_cfg80211_ready(vif)) return -EIO; @@ -2089,53 +2767,41 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, if (vif->next_mode != AP_NETWORK) return -EOPNOTSUPP; - if (info->beacon_ies) { - res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, - WMI_FRAME_BEACON, - info->beacon_ies, - info->beacon_ies_len); - if (res) - return res; - } - if (info->proberesp_ies) { - res = ath6kl_set_ap_probe_resp_ies(vif, info->proberesp_ies, - info->proberesp_ies_len); - if (res) - return res; - } - if (info->assocresp_ies) { - res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, - WMI_FRAME_ASSOC_RESP, - info->assocresp_ies, - info->assocresp_ies_len); - if (res) - return res; - } - - if (!add) - return 0; + res = ath6kl_set_ies(vif, &info->beacon); ar->ap_mode_bkey.valid = false; - /* TODO: - * info->interval - * info->dtim_period - */ + ret = ath6kl_wmi_ap_set_beacon_intvl_cmd(ar->wmi, vif->fw_vif_idx, + info->beacon_interval); + + if (ret) + ath6kl_warn("Failed to set beacon interval: %d\n", ret); + + ret = ath6kl_wmi_ap_set_dtim_cmd(ar->wmi, vif->fw_vif_idx, + info->dtim_period); - if (info->head == NULL) + /* ignore error, just print a warning and continue normally */ + if (ret) + ath6kl_warn("Failed to set dtim_period in beacon: %d\n", ret); + + if (info->beacon.head == NULL) return -EINVAL; - mgmt = (struct ieee80211_mgmt *) info->head; + mgmt = (struct ieee80211_mgmt *) info->beacon.head; ies = mgmt->u.beacon.variable; - if (ies > info->head + info->head_len) + if (ies > info->beacon.head + info->beacon.head_len) return -EINVAL; - ies_len = info->head + info->head_len - ies; + ies_len = info->beacon.head + info->beacon.head_len - ies; if (info->ssid == NULL) return -EINVAL; memcpy(vif->ssid, info->ssid, info->ssid_len); vif->ssid_len = info->ssid_len; if (info->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE) - return -EOPNOTSUPP; /* TODO */ + hidden = true; + + res = ath6kl_wmi_ap_hidden_ssid(ar->wmi, vif->fw_vif_idx, hidden); + if (res) + return res; ret = ath6kl_set_auth_type(vif, info->auth_type); if (ret) @@ -2183,8 +2849,9 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, if (p.prwise_crypto_type == 0) { p.prwise_crypto_type = NONE_CRYPT; ath6kl_set_cipher(vif, 0, true); - } else if (info->crypto.n_ciphers_pairwise == 1) + } else if (info->crypto.n_ciphers_pairwise == 1) { ath6kl_set_cipher(vif, info->crypto.ciphers_pairwise[0], true); + } switch (info->crypto.cipher_group) { case WLAN_CIPHER_SUITE_WEP40: @@ -2212,7 +2879,12 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, p.ssid_len = vif->ssid_len; memcpy(p.ssid, vif->ssid, vif->ssid_len); p.dot11_auth_mode = vif->dot11_auth_mode; - p.ch = cpu_to_le16(vif->next_chan); + p.ch = cpu_to_le16(info->chandef.chan->center_freq); + + /* Enable uAPSD support by default */ + res = ath6kl_wmi_ap_set_apsd(ar->wmi, vif->fw_vif_idx, true); + if (res < 0) + return res; if (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) { p.nw_subtype = SUBTYPE_P2PGO; @@ -2224,6 +2896,43 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, p.nw_subtype = SUBTYPE_NONE; } + if (info->inactivity_timeout) { + inactivity_timeout = info->inactivity_timeout; + + if (ar->hw.flags & ATH6KL_HW_AP_INACTIVITY_MINS) + inactivity_timeout = DIV_ROUND_UP(inactivity_timeout, + 60); + + res = ath6kl_wmi_set_inact_period(ar->wmi, vif->fw_vif_idx, + inactivity_timeout); + if (res < 0) + return res; + } + + if (ath6kl_set_htcap(vif, info->chandef.chan->band, + cfg80211_get_chandef_type(&info->chandef) + != NL80211_CHAN_NO_HT)) + return -EIO; + + /* + * Get the PTKSA replay counter in the RSN IE. Supplicant + * will use the RSN IE in M3 message and firmware has to + * advertise the same in beacon/probe response. Send + * the complete RSN IE capability field to firmware + */ + if (!ath6kl_get_rsn_capab(&info->beacon, (u8 *) &rsn_capab) && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities)) { + res = ath6kl_wmi_set_ie_cmd(ar->wmi, vif->fw_vif_idx, + WLAN_EID_RSN, WMI_RSN_IE_CAPB, + (const u8 *) &rsn_capab, + sizeof(rsn_capab)); + vif->rsn_capab = rsn_capab; + if (res < 0) + return res; + } + + memcpy(&vif->profile, &p, sizeof(p)); res = ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, &p); if (res < 0) return res; @@ -2231,19 +2940,21 @@ static int ath6kl_ap_beacon(struct wiphy *wiphy, struct net_device *dev, return 0; } -static int ath6kl_add_beacon(struct wiphy *wiphy, struct net_device *dev, - struct beacon_parameters *info) +static int ath6kl_change_beacon(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_beacon_data *beacon) { - return ath6kl_ap_beacon(wiphy, dev, info, true); -} + struct ath6kl_vif *vif = netdev_priv(dev); -static int ath6kl_set_beacon(struct wiphy *wiphy, struct net_device *dev, - struct beacon_parameters *info) -{ - return ath6kl_ap_beacon(wiphy, dev, info, false); + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (vif->next_mode != AP_NETWORK) + return -EOPNOTSUPP; + + return ath6kl_set_ies(vif, beacon); } -static int ath6kl_del_beacon(struct wiphy *wiphy, struct net_device *dev) +static int ath6kl_stop_ap(struct wiphy *wiphy, struct net_device *dev) { struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev); @@ -2256,21 +2967,38 @@ static int ath6kl_del_beacon(struct wiphy *wiphy, struct net_device *dev) ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx); clear_bit(CONNECTED, &vif->flags); - return 0; + /* Restore ht setting in firmware */ + return ath6kl_restore_htcap(vif); +} + +static const u8 bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static int ath6kl_del_station(struct wiphy *wiphy, struct net_device *dev, + const u8 *mac) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + const u8 *addr = mac ? mac : bcast_addr; + + return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, WMI_AP_DEAUTH, + addr, WLAN_REASON_PREV_AUTH_NOT_VALID); } static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev, - u8 *mac, struct station_parameters *params) + const u8 *mac, + struct station_parameters *params) { struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev); + int err; if (vif->nw_type != AP_NETWORK) return -EOPNOTSUPP; - /* Use this only for authorizing/unauthorizing a station */ - if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) - return -EOPNOTSUPP; + err = cfg80211_check_station_change(wiphy, params, + CFG80211_STA_AP_MLME_CLIENT); + if (err) + return err; if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, @@ -2280,14 +3008,13 @@ static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev, } static int ath6kl_remain_on_channel(struct wiphy *wiphy, - struct net_device *dev, + struct wireless_dev *wdev, struct ieee80211_channel *chan, - enum nl80211_channel_type channel_type, unsigned int duration, u64 *cookie) { - struct ath6kl *ar = ath6kl_priv(dev); - struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); u32 id; /* TODO: if already pending or ongoing remain-on-channel, @@ -2304,11 +3031,11 @@ static int ath6kl_remain_on_channel(struct wiphy *wiphy, } static int ath6kl_cancel_remain_on_channel(struct wiphy *wiphy, - struct net_device *dev, + struct wireless_dev *wdev, u64 cookie) { - struct ath6kl *ar = ath6kl_priv(dev); - struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); if (cookie != vif->last_roc_id) return -ENOENT; @@ -2354,29 +3081,126 @@ static int ath6kl_send_go_probe_resp(struct ath6kl_vif *vif, return ret; } -static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, - struct ieee80211_channel *chan, bool offchan, - enum nl80211_channel_type channel_type, - bool channel_type_valid, unsigned int wait, - const u8 *buf, size_t len, bool no_cck, - bool dont_wait_for_ack, u64 *cookie) +static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, + u32 id, + u32 freq, + u32 wait, + const u8 *buf, + size_t len, + bool *more_data, + bool no_cck) +{ + struct ieee80211_mgmt *mgmt; + struct ath6kl_sta *conn; + bool is_psq_empty = false; + struct ath6kl_mgmt_buff *mgmt_buf; + size_t mgmt_buf_size; + struct ath6kl *ar = vif->ar; + + mgmt = (struct ieee80211_mgmt *) buf; + if (is_multicast_ether_addr(mgmt->da)) + return false; + + conn = ath6kl_find_sta(vif, mgmt->da); + if (!conn) + return false; + + if (conn->sta_flags & STA_PS_SLEEP) { + if (!(conn->sta_flags & STA_PS_POLLED)) { + /* Queue the frames if the STA is sleeping */ + mgmt_buf_size = len + sizeof(struct ath6kl_mgmt_buff); + mgmt_buf = kmalloc(mgmt_buf_size, GFP_KERNEL); + if (!mgmt_buf) + return false; + + INIT_LIST_HEAD(&mgmt_buf->list); + mgmt_buf->id = id; + mgmt_buf->freq = freq; + mgmt_buf->wait = wait; + mgmt_buf->len = len; + mgmt_buf->no_cck = no_cck; + memcpy(mgmt_buf->buf, buf, len); + spin_lock_bh(&conn->psq_lock); + is_psq_empty = skb_queue_empty(&conn->psq) && + (conn->mgmt_psq_len == 0); + list_add_tail(&mgmt_buf->list, &conn->mgmt_psq); + conn->mgmt_psq_len++; + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this + * STA. + */ + if (is_psq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, + conn->aid, 1); + return true; + } + + /* + * This tx is because of a PsPoll. + * Determine if MoreData bit has to be set. + */ + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->psq) || (conn->mgmt_psq_len != 0)) + *more_data = true; + spin_unlock_bh(&conn->psq_lock); + } + + return false; +} + +/* Check if SSID length is greater than DIRECT- */ +static bool ath6kl_is_p2p_go_ssid(const u8 *buf, size_t len) { - struct ath6kl *ar = ath6kl_priv(dev); - struct ath6kl_vif *vif = netdev_priv(dev); - u32 id; const struct ieee80211_mgmt *mgmt; + mgmt = (const struct ieee80211_mgmt *) buf; + + /* variable[1] contains the SSID tag length */ + if (buf + len >= &mgmt->u.probe_resp.variable[1] && + (mgmt->u.probe_resp.variable[1] > P2P_WILDCARD_SSID_LEN)) { + return true; + } + + return false; +} + +static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct cfg80211_mgmt_tx_params *params, u64 *cookie) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); + struct ieee80211_channel *chan = params->chan; + const u8 *buf = params->buf; + size_t len = params->len; + unsigned int wait = params->wait; + bool no_cck = params->no_cck; + u32 id, freq; + const struct ieee80211_mgmt *mgmt; + bool more_data, queued; + + /* default to the current channel, but use the one specified as argument + * if any + */ + freq = vif->ch_hint; + if (chan) + freq = chan->center_freq; + + /* never send freq zero to the firmware */ + if (WARN_ON(freq == 0)) + return -EINVAL; mgmt = (const struct ieee80211_mgmt *) buf; - if (buf + len >= mgmt->u.probe_resp.variable && - vif->nw_type == AP_NETWORK && test_bit(CONNECTED, &vif->flags) && - ieee80211_is_probe_resp(mgmt->frame_control)) { + if (vif->nw_type == AP_NETWORK && test_bit(CONNECTED, &vif->flags) && + ieee80211_is_probe_resp(mgmt->frame_control) && + ath6kl_is_p2p_go_ssid(buf, len)) { /* - * Send Probe Response frame in AP mode using a separate WMI + * Send Probe Response frame in GO mode using a separate WMI * command to allow the target to fill in the generic IEs. */ *cookie = 0; /* TX status not supported */ - return ath6kl_send_go_probe_resp(vif, buf, len, - chan->center_freq); + return ath6kl_send_go_probe_resp(vif, buf, len, freq); } id = vif->send_action_id++; @@ -2390,29 +3214,23 @@ static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, *cookie = id; - if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, - ar->fw_capabilities)) { - /* - * If capable of doing P2P mgmt operations using - * station interface, send additional information like - * supported rates to advertise and xmit rates for - * probe requests - */ - return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, - chan->center_freq, wait, - buf, len, no_cck); - } else { - return ath6kl_wmi_send_action_cmd(ar->wmi, vif->fw_vif_idx, id, - chan->center_freq, wait, - buf, len); + /* AP mode Power saving processing */ + if (vif->nw_type == AP_NETWORK) { + queued = ath6kl_mgmt_powersave_ap(vif, id, freq, wait, buf, len, + &more_data, no_cck); + if (queued) + return 0; } + + return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, freq, + wait, buf, len, no_cck); } static void ath6kl_mgmt_frame_register(struct wiphy *wiphy, - struct net_device *dev, + struct wireless_dev *wdev, u16 frame_type, bool reg) { - struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: frame_type=0x%x reg=%d\n", __func__, frame_type, reg); @@ -2433,8 +3251,16 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy, struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev); u16 interval; - int ret; - u8 i; + int ret, rssi_thold; + int n_match_sets = request->n_match_sets; + + /* + * If there's a matchset w/o an SSID, then assume it's just for + * the RSSI (nothing else is currently supported) and ignore it. + * The device only supports a global RSSI filter that we set below. + */ + if (n_match_sets == 1 && !request->match_sets[0].ssid.ssid_len) + n_match_sets = 0; if (ar->state != ATH6KL_STATE_ON) return -EIO; @@ -2442,10 +3268,42 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy, if (vif->sme_state != SME_DISCONNECTED) return -EBUSY; - for (i = 0; i < ar->wiphy->max_sched_scan_ssids; i++) { - ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, - i, DISABLE_SSID_FLAG, - 0, NULL); + ath6kl_cfg80211_scan_complete_event(vif, true); + + ret = ath6kl_set_probed_ssids(ar, vif, request->ssids, + request->n_ssids, + request->match_sets, + n_match_sets); + if (ret < 0) + return ret; + + if (!n_match_sets) { + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + ALL_BSS_FILTER, 0); + if (ret < 0) + return ret; + } else { + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + MATCHED_SSID_FILTER, 0); + if (ret < 0) + return ret; + } + + if (test_bit(ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, + ar->fw_capabilities)) { + if (request->min_rssi_thold <= NL80211_SCAN_RSSI_THOLD_OFF) + rssi_thold = 0; + else if (request->min_rssi_thold < -127) + rssi_thold = -127; + else + rssi_thold = request->min_rssi_thold; + + ret = ath6kl_wmi_set_rssi_filter_cmd(ar->wmi, vif->fw_vif_idx, + rssi_thold); + if (ret) { + ath6kl_err("failed to set RSSI threshold for scan\n"); + return ret; + } } /* fw uses seconds, also make sure that it's >0 */ @@ -2453,47 +3311,25 @@ static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy, ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, interval, interval, - 10, 0, 0, 0, 3, 0, 0, 0); - - if (request->n_ssids && request->ssids[0].ssid_len) { - for (i = 0; i < request->n_ssids; i++) { - ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, - i, SPECIFIC_SSID_FLAG, - request->ssids[i].ssid_len, - request->ssids[i].ssid); - } - } - - ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx, - ATH6KL_WOW_MODE_ENABLE, - WOW_FILTER_SSID, - WOW_HOST_REQ_DELAY); - if (ret) { - ath6kl_warn("Failed to enable wow with ssid filter: %d\n", ret); - return ret; - } + vif->bg_scan_period, 0, 0, 0, 3, 0, 0, 0); /* this also clears IE in fw if it's not set */ ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, WMI_FRAME_PROBE_REQ, request->ie, request->ie_len); if (ret) { - ath6kl_warn("Failed to set probe request IE for scheduled scan: %d", + ath6kl_warn("Failed to set probe request IE for scheduled scan: %d\n", ret); return ret; } - ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, - ATH6KL_HOST_MODE_ASLEEP); - if (ret) { - ath6kl_warn("Failed to enable host sleep mode for sched scan: %d\n", - ret); + ret = ath6kl_wmi_enable_sched_scan_cmd(ar->wmi, vif->fw_vif_idx, true); + if (ret) return ret; - } - ar->state = ATH6KL_STATE_SCHED_SCAN; + set_bit(SCHED_SCANNING, &vif->flags); - return ret; + return 0; } static int ath6kl_cfg80211_sscan_stop(struct wiphy *wiphy, @@ -2510,6 +3346,39 @@ static int ath6kl_cfg80211_sscan_stop(struct wiphy *wiphy, return 0; } +static int ath6kl_cfg80211_set_bitrate(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + return ath6kl_wmi_set_bitrate_mask(ar->wmi, vif->fw_vif_idx, + mask); +} + +static int ath6kl_cfg80211_set_txe_config(struct wiphy *wiphy, + struct net_device *dev, + u32 rate, u32 pkts, u32 intvl) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + if (vif->nw_type != INFRA_NETWORK || + !test_bit(ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, ar->fw_capabilities)) + return -EOPNOTSUPP; + + if (vif->sme_state != SME_CONNECTED) + return -ENOTCONN; + + /* save this since the firmware won't report the interval */ + vif->txe_intvl = intvl; + + return ath6kl_wmi_set_txe_notify(ar->wmi, vif->fw_vif_idx, + rate, pkts, intvl); +} + static const struct ieee80211_txrx_stypes ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { [NL80211_IFTYPE_STATION] = { @@ -2518,6 +3387,12 @@ ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, + [NL80211_IFTYPE_AP] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, [NL80211_IFTYPE_P2P_CLIENT] = { .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_RESP >> 4), @@ -2558,10 +3433,10 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = { .suspend = __ath6kl_cfg80211_suspend, .resume = __ath6kl_cfg80211_resume, #endif - .set_channel = ath6kl_set_channel, - .add_beacon = ath6kl_add_beacon, - .set_beacon = ath6kl_set_beacon, - .del_beacon = ath6kl_del_beacon, + .start_ap = ath6kl_start_ap, + .change_beacon = ath6kl_change_beacon, + .stop_ap = ath6kl_stop_ap, + .del_station = ath6kl_del_station, .change_station = ath6kl_change_station, .remain_on_channel = ath6kl_remain_on_channel, .cancel_remain_on_channel = ath6kl_cancel_remain_on_channel, @@ -2569,6 +3444,8 @@ static struct cfg80211_ops ath6kl_cfg80211_ops = { .mgmt_frame_register = ath6kl_mgmt_frame_register, .sched_scan_start = ath6kl_cfg80211_sscan_start, .sched_scan_stop = ath6kl_cfg80211_sscan_stop, + .set_bitrate_mask = ath6kl_cfg80211_set_bitrate, + .set_cqm_txe_config = ath6kl_cfg80211_set_txe_config, }; void ath6kl_cfg80211_stop(struct ath6kl_vif *vif) @@ -2589,16 +3466,22 @@ void ath6kl_cfg80211_stop(struct ath6kl_vif *vif) break; } - if (test_bit(CONNECTED, &vif->flags) || - test_bit(CONNECT_PEND, &vif->flags)) + if (vif->ar->state != ATH6KL_STATE_RECOVERY && + (test_bit(CONNECTED, &vif->flags) || + test_bit(CONNECT_PEND, &vif->flags))) ath6kl_wmi_disconnect_cmd(vif->ar->wmi, vif->fw_vif_idx); vif->sme_state = SME_DISCONNECTED; clear_bit(CONNECTED, &vif->flags); clear_bit(CONNECT_PEND, &vif->flags); + /* Stop netdev queues, needed during recovery */ + netif_stop_queue(vif->ndev); + netif_carrier_off(vif->ndev); + /* disable scanning */ - if (ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx, 0xFFFF, + if (vif->ar->state != ATH6KL_STATE_RECOVERY && + ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx, 0xFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0) != 0) ath6kl_warn("failed to disable scan during stop\n"); @@ -2610,13 +3493,12 @@ void ath6kl_cfg80211_stop_all(struct ath6kl *ar) struct ath6kl_vif *vif; vif = ath6kl_vif_first(ar); - if (!vif) { + if (!vif && ar->state != ATH6KL_STATE_RECOVERY) { /* save the current power mode before enabling power save */ ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode; if (ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER) != 0) - ath6kl_warn("ath6kl_deep_sleep_enable: " - "wmi_powermode_cmd failed\n"); + ath6kl_warn("ath6kl_deep_sleep_enable: wmi_powermode_cmd failed\n"); return; } @@ -2629,122 +3511,53 @@ void ath6kl_cfg80211_stop_all(struct ath6kl *ar) ath6kl_cfg80211_stop(vif); } -struct ath6kl *ath6kl_core_alloc(struct device *dev) -{ - struct ath6kl *ar; - struct wiphy *wiphy; - u8 ctr; - - /* create a new wiphy for use with cfg80211 */ - wiphy = wiphy_new(&ath6kl_cfg80211_ops, sizeof(struct ath6kl)); - - if (!wiphy) { - ath6kl_err("couldn't allocate wiphy device\n"); - return NULL; - } - - ar = wiphy_priv(wiphy); - ar->p2p = !!ath6kl_p2p; - ar->wiphy = wiphy; - ar->dev = dev; - - ar->vif_max = 1; - - ar->max_norm_iface = 1; - - spin_lock_init(&ar->lock); - spin_lock_init(&ar->mcastpsq_lock); - spin_lock_init(&ar->list_lock); - - init_waitqueue_head(&ar->event_wq); - sema_init(&ar->sem, 1); - - INIT_LIST_HEAD(&ar->amsdu_rx_buffer_queue); - INIT_LIST_HEAD(&ar->vif_list); - - clear_bit(WMI_ENABLED, &ar->flag); - clear_bit(SKIP_SCAN, &ar->flag); - clear_bit(DESTROY_IN_PROGRESS, &ar->flag); - - ar->listen_intvl_t = A_DEFAULT_LISTEN_INTERVAL; - ar->listen_intvl_b = 0; - ar->tx_pwr = 0; - - ar->intra_bss = 1; - ar->lrssi_roam_threshold = DEF_LRSSI_ROAM_THRESHOLD; - - ar->state = ATH6KL_STATE_OFF; - - memset((u8 *)ar->sta_list, 0, - AP_MAX_NUM_STA * sizeof(struct ath6kl_sta)); - - /* Init the PS queues */ - for (ctr = 0; ctr < AP_MAX_NUM_STA; ctr++) { - spin_lock_init(&ar->sta_list[ctr].psq_lock); - skb_queue_head_init(&ar->sta_list[ctr].psq); - } - - skb_queue_head_init(&ar->mcastpsq); - - memcpy(ar->ap_country_code, DEF_AP_COUNTRY_CODE, 3); - - return ar; -} - -int ath6kl_register_ieee80211_hw(struct ath6kl *ar) +static void ath6kl_cfg80211_reg_notify(struct wiphy *wiphy, + struct regulatory_request *request) { - struct wiphy *wiphy = ar->wiphy; - int ret; - - wiphy->mgmt_stypes = ath6kl_mgmt_stypes; + struct ath6kl *ar = wiphy_priv(wiphy); + u32 rates[IEEE80211_NUM_BANDS]; + int ret, i; - wiphy->max_remain_on_channel_duration = 5000; + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "cfg reg_notify %c%c%s%s initiator %d hint_type %d\n", + request->alpha2[0], request->alpha2[1], + request->intersect ? " intersect" : "", + request->processed ? " processed" : "", + request->initiator, request->user_reg_hint_type); - /* set device pointer for wiphy */ - set_wiphy_dev(wiphy, ar->dev); + if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE) + return; - wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_ADHOC) | - BIT(NL80211_IFTYPE_AP); - if (ar->p2p) { - wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_GO) | - BIT(NL80211_IFTYPE_P2P_CLIENT); + ret = ath6kl_wmi_set_regdomain_cmd(ar->wmi, request->alpha2); + if (ret) { + ath6kl_err("failed to set regdomain: %d\n", ret); + return; } - /* max num of ssids that can be probed during scanning */ - wiphy->max_scan_ssids = MAX_PROBED_SSID_INDEX; - wiphy->max_scan_ie_len = 1000; /* FIX: what is correct limit? */ - wiphy->bands[IEEE80211_BAND_2GHZ] = &ath6kl_band_2ghz; - wiphy->bands[IEEE80211_BAND_5GHZ] = &ath6kl_band_5ghz; - wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; - - wiphy->cipher_suites = cipher_suites; - wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + /* + * Firmware will apply the regdomain change only after a scan is + * issued and it will send a WMI_REGDOMAIN_EVENTID when it has been + * changed. + */ - wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | - WIPHY_WOWLAN_DISCONNECT | - WIPHY_WOWLAN_GTK_REKEY_FAILURE | - WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | - WIPHY_WOWLAN_EAP_IDENTITY_REQ | - WIPHY_WOWLAN_4WAY_HANDSHAKE; - wiphy->wowlan.n_patterns = WOW_MAX_FILTERS_PER_LIST; - wiphy->wowlan.pattern_min_len = 1; - wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE; + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + if (wiphy->bands[i]) + rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1; - wiphy->max_sched_scan_ssids = 10; - ret = wiphy_register(wiphy); - if (ret < 0) { - ath6kl_err("couldn't register wiphy device\n"); - return ret; + ret = ath6kl_wmi_beginscan_cmd(ar->wmi, 0, WMI_LONG_SCAN, false, + false, 0, ATH6KL_FG_SCAN_INTERVAL, + 0, NULL, false, rates); + if (ret) { + ath6kl_err("failed to start scan for a regdomain change: %d\n", + ret); + return; } - - return 0; } -static int ath6kl_init_if_data(struct ath6kl_vif *vif) +static int ath6kl_cfg80211_vif_init(struct ath6kl_vif *vif) { - vif->aggr_cntxt = aggr_init(vif->ndev); + vif->aggr_cntxt = aggr_init(vif); if (!vif->aggr_cntxt) { ath6kl_err("failed to initialize aggr\n"); return -ENOMEM; @@ -2758,12 +3571,46 @@ static int ath6kl_init_if_data(struct ath6kl_vif *vif) set_bit(WMM_ENABLED, &vif->flags); spin_lock_init(&vif->if_lock); + INIT_LIST_HEAD(&vif->mc_filter); + return 0; } -void ath6kl_deinit_if_data(struct ath6kl_vif *vif) +void ath6kl_cfg80211_vif_stop(struct ath6kl_vif *vif, bool wmi_ready) +{ + static u8 bcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + bool discon_issued; + + netif_stop_queue(vif->ndev); + + clear_bit(WLAN_ENABLED, &vif->flags); + + if (wmi_ready) { + discon_issued = test_bit(CONNECTED, &vif->flags) || + test_bit(CONNECT_PEND, &vif->flags); + ath6kl_disconnect(vif); + del_timer(&vif->disconnect_timer); + + if (discon_issued) + ath6kl_disconnect_event(vif, DISCONNECT_CMD, + (vif->nw_type & AP_NETWORK) ? + bcast_mac : vif->bssid, + 0, NULL, 0); + } + + if (vif->scan_req) { + cfg80211_scan_done(vif->scan_req, true); + vif->scan_req = NULL; + } + + /* need to clean up enhanced bmiss detection fw state */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, false); +} + +void ath6kl_cfg80211_vif_cleanup(struct ath6kl_vif *vif) { struct ath6kl *ar = vif->ar; + struct ath6kl_mc_filter *mc_filter, *tmp; aggr_module_destroy(vif->aggr_cntxt); @@ -2772,14 +3619,19 @@ void ath6kl_deinit_if_data(struct ath6kl_vif *vif) if (vif->nw_type == ADHOC_NETWORK) ar->ibss_if_active = false; + list_for_each_entry_safe(mc_filter, tmp, &vif->mc_filter, list) { + list_del(&mc_filter->list); + kfree(mc_filter); + } + unregister_netdevice(vif->ndev); ar->num_vif--; } -struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, - enum nl80211_iftype type, u8 fw_vif_idx, - u8 nw_type) +struct wireless_dev *ath6kl_interface_add(struct ath6kl *ar, const char *name, + enum nl80211_iftype type, + u8 fw_vif_idx, u8 nw_type) { struct net_device *ndev; struct ath6kl_vif *vif; @@ -2797,19 +3649,28 @@ struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, vif->wdev.netdev = ndev; vif->wdev.iftype = type; vif->fw_vif_idx = fw_vif_idx; - vif->nw_type = vif->next_mode = nw_type; + vif->nw_type = nw_type; + vif->next_mode = nw_type; + vif->listen_intvl_t = ATH6KL_DEFAULT_LISTEN_INTVAL; + vif->bmiss_time_t = ATH6KL_DEFAULT_BMISS_TIME; + vif->bg_scan_period = 0; + vif->htcap[IEEE80211_BAND_2GHZ].ht_enable = true; + vif->htcap[IEEE80211_BAND_5GHZ].ht_enable = true; memcpy(ndev->dev_addr, ar->mac_addr, ETH_ALEN); - if (fw_vif_idx != 0) + if (fw_vif_idx != 0) { ndev->dev_addr[0] = (ndev->dev_addr[0] ^ (1 << fw_vif_idx)) | 0x2; + if (test_bit(ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, + ar->fw_capabilities)) + ndev->dev_addr[4] ^= 0x80; + } init_netdev(ndev); ath6kl_init_control_info(vif); - /* TODO: Pass interface specific pointer instead of ar */ - if (ath6kl_init_if_data(vif)) + if (ath6kl_cfg80211_vif_init(vif)) goto err; if (register_netdevice(ndev)) @@ -2819,7 +3680,6 @@ struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, vif->sme_state = SME_DISCONNECTED; set_bit(WLAN_ENABLED, &vif->flags); ar->wlan_pwr_state = WLAN_POWER_STATE_ON; - set_bit(NETDEV_REGISTERED, &vif->flags); if (type == NL80211_IFTYPE_ADHOC) ar->ibss_if_active = true; @@ -2828,7 +3688,7 @@ struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, list_add_tail(&vif->list, &ar->vif_list); spin_unlock_bh(&ar->list_lock); - return ndev; + return &vif->wdev; err: aggr_module_destroy(vif->aggr_cntxt); @@ -2836,8 +3696,180 @@ err: return NULL; } -void ath6kl_deinit_ieee80211_hw(struct ath6kl *ar) +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support ath6kl_wowlan_support = { + .flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_4WAY_HANDSHAKE, + .n_patterns = WOW_MAX_FILTERS_PER_LIST, + .pattern_min_len = 1, + .pattern_max_len = WOW_PATTERN_SIZE, +}; +#endif + +int ath6kl_cfg80211_init(struct ath6kl *ar) +{ + struct wiphy *wiphy = ar->wiphy; + bool band_2gig = false, band_5gig = false, ht = false; + int ret; + + wiphy->mgmt_stypes = ath6kl_mgmt_stypes; + + wiphy->max_remain_on_channel_duration = 5000; + + /* set device pointer for wiphy */ + set_wiphy_dev(wiphy, ar->dev); + + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); + if (ar->p2p) { + wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT); + } + + if (config_enabled(CONFIG_ATH6KL_REGDOMAIN) && + test_bit(ATH6KL_FW_CAPABILITY_REGDOMAIN, ar->fw_capabilities)) { + wiphy->reg_notifier = ath6kl_cfg80211_reg_notify; + ar->wiphy->features |= NL80211_FEATURE_CELL_BASE_REG_HINTS; + } + + /* max num of ssids that can be probed during scanning */ + wiphy->max_scan_ssids = MAX_PROBED_SSIDS; + + /* max num of ssids that can be matched after scan */ + if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, + ar->fw_capabilities)) + wiphy->max_match_sets = MAX_PROBED_SSIDS; + + wiphy->max_scan_ie_len = 1000; /* FIX: what is correct limit? */ + switch (ar->hw.cap) { + case WMI_11AN_CAP: + ht = true; + case WMI_11A_CAP: + band_5gig = true; + break; + case WMI_11GN_CAP: + ht = true; + case WMI_11G_CAP: + band_2gig = true; + break; + case WMI_11AGN_CAP: + ht = true; + case WMI_11AG_CAP: + band_2gig = true; + band_5gig = true; + break; + default: + ath6kl_err("invalid phy capability!\n"); + return -EINVAL; + } + + /* + * Even if the fw has HT support, advertise HT cap only when + * the firmware has support to override RSN capability, otherwise + * 4-way handshake would fail. + */ + if (!(ht && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities))) { + ath6kl_band_2ghz.ht_cap.cap = 0; + ath6kl_band_2ghz.ht_cap.ht_supported = false; + ath6kl_band_5ghz.ht_cap.cap = 0; + ath6kl_band_5ghz.ht_cap.ht_supported = false; + } + + if (ar->hw.flags & ATH6KL_HW_64BIT_RATES) { + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[1] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[1] = 0xff; + } else { + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[0] = 0xff; + } + + if (band_2gig) + wiphy->bands[IEEE80211_BAND_2GHZ] = &ath6kl_band_2ghz; + if (band_5gig) + wiphy->bands[IEEE80211_BAND_5GHZ] = &ath6kl_band_5ghz; + + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wiphy->cipher_suites = cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + +#ifdef CONFIG_PM + wiphy->wowlan = &ath6kl_wowlan_support; +#endif + + wiphy->max_sched_scan_ssids = MAX_PROBED_SSIDS; + + ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM | + WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + + if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, ar->fw_capabilities)) + ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; + + if (test_bit(ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, + ar->fw_capabilities)) + ar->wiphy->features |= NL80211_FEATURE_INACTIVITY_TIMER; + + ar->wiphy->probe_resp_offload = + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + ret = wiphy_register(wiphy); + if (ret < 0) { + ath6kl_err("couldn't register wiphy device\n"); + return ret; + } + + ar->wiphy_registered = true; + + return 0; +} + +void ath6kl_cfg80211_cleanup(struct ath6kl *ar) { wiphy_unregister(ar->wiphy); + + ar->wiphy_registered = false; +} + +struct ath6kl *ath6kl_cfg80211_create(void) +{ + struct ath6kl *ar; + struct wiphy *wiphy; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&ath6kl_cfg80211_ops, sizeof(struct ath6kl)); + + if (!wiphy) { + ath6kl_err("couldn't allocate wiphy device\n"); + return NULL; + } + + ar = wiphy_priv(wiphy); + ar->wiphy = wiphy; + + return ar; +} + +/* Note: ar variable must not be accessed after calling this! */ +void ath6kl_cfg80211_destroy(struct ath6kl *ar) +{ + int i; + + for (i = 0; i < AP_MAX_NUM_STA; i++) + kfree(ar->sta_list[i].aggr_conn); + wiphy_free(ar->wiphy); } + diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.h b/drivers/net/wireless/ath/ath6kl/cfg80211.h index 81f20a57231..b59becd91ae 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.h +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,16 +22,13 @@ enum ath6kl_cfg_suspend_mode { ATH6KL_CFG_SUSPEND_DEEPSLEEP, ATH6KL_CFG_SUSPEND_CUTPOWER, ATH6KL_CFG_SUSPEND_WOW, - ATH6KL_CFG_SUSPEND_SCHED_SCAN, }; -struct net_device *ath6kl_interface_add(struct ath6kl *ar, char *name, - enum nl80211_iftype type, - u8 fw_vif_idx, u8 nw_type); -int ath6kl_register_ieee80211_hw(struct ath6kl *ar); -struct ath6kl *ath6kl_core_alloc(struct device *dev); -void ath6kl_deinit_ieee80211_hw(struct ath6kl *ar); - +struct wireless_dev *ath6kl_interface_add(struct ath6kl *ar, const char *name, + enum nl80211_iftype type, + u8 fw_vif_idx, u8 nw_type); +void ath6kl_cfg80211_ch_switch_notify(struct ath6kl_vif *vif, int freq, + enum wmi_phy_mode mode); void ath6kl_cfg80211_scan_complete_event(struct ath6kl_vif *vif, bool aborted); void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, @@ -53,7 +51,15 @@ int ath6kl_cfg80211_suspend(struct ath6kl *ar, int ath6kl_cfg80211_resume(struct ath6kl *ar); +void ath6kl_cfg80211_vif_cleanup(struct ath6kl_vif *vif); + void ath6kl_cfg80211_stop(struct ath6kl_vif *vif); void ath6kl_cfg80211_stop_all(struct ath6kl *ar); +int ath6kl_cfg80211_init(struct ath6kl *ar); +void ath6kl_cfg80211_cleanup(struct ath6kl *ar); + +struct ath6kl *ath6kl_cfg80211_create(void); +void ath6kl_cfg80211_destroy(struct ath6kl *ar); + #endif /* ATH6KL_CFG80211_H */ diff --git a/drivers/net/wireless/ath/ath6kl/common.h b/drivers/net/wireless/ath/ath6kl/common.h index bfd6597763d..05debf700a8 100644 --- a/drivers/net/wireless/ath/ath6kl/common.h +++ b/drivers/net/wireless/ath/ath6kl/common.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,7 +22,7 @@ #define ATH6KL_MAX_IE 256 -extern int ath6kl_printk(const char *level, const char *fmt, ...); +__printf(2, 3) int ath6kl_printk(const char *level, const char *fmt, ...); /* * Reflects the version of binary interface exposed by ATH6KL target @@ -76,11 +77,9 @@ enum crypto_type { struct htc_endpoint_credit_dist; struct ath6kl; +struct ath6kl_htcap; enum htc_credit_dist_reason; struct ath6kl_htc_credit_info; -struct ath6kl *ath6kl_core_alloc(struct device *sdev); -int ath6kl_core_init(struct ath6kl *ar); -void ath6kl_core_cleanup(struct ath6kl *ar); struct sk_buff *ath6kl_buf_alloc(int size); #endif /* COMMON_H */ diff --git a/drivers/net/wireless/ath/ath6kl/core.c b/drivers/net/wireless/ath/ath6kl/core.c new file mode 100644 index 00000000000..b0b65204276 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/core.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/export.h> +#include <linux/vmalloc.h> + +#include "debug.h" +#include "hif-ops.h" +#include "htc-ops.h" +#include "cfg80211.h" + +unsigned int debug_mask; +static unsigned int suspend_mode; +static unsigned int wow_mode; +static unsigned int uart_debug; +static unsigned int ath6kl_p2p; +static unsigned int testmode; +static unsigned int recovery_enable; +static unsigned int heart_beat_poll; + +module_param(debug_mask, uint, 0644); +module_param(suspend_mode, uint, 0644); +module_param(wow_mode, uint, 0644); +module_param(uart_debug, uint, 0644); +module_param(ath6kl_p2p, uint, 0644); +module_param(testmode, uint, 0644); +module_param(recovery_enable, uint, 0644); +module_param(heart_beat_poll, uint, 0644); +MODULE_PARM_DESC(recovery_enable, "Enable recovery from firmware error"); +MODULE_PARM_DESC(heart_beat_poll, + "Enable fw error detection periodic polling in msecs - Also set recovery_enable for this to be effective"); + + +void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb) +{ + ath6kl_htc_tx_complete(ar, skb); +} +EXPORT_SYMBOL(ath6kl_core_tx_complete); + +void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe) +{ + ath6kl_htc_rx_complete(ar, skb, pipe); +} +EXPORT_SYMBOL(ath6kl_core_rx_complete); + +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) +{ + struct ath6kl_bmi_target_info targ_info; + struct wireless_dev *wdev; + int ret = 0, i; + + switch (htc_type) { + case ATH6KL_HTC_TYPE_MBOX: + ath6kl_htc_mbox_attach(ar); + break; + case ATH6KL_HTC_TYPE_PIPE: + ath6kl_htc_pipe_attach(ar); + break; + default: + WARN_ON(1); + return -ENOMEM; + } + + ar->ath6kl_wq = create_singlethread_workqueue("ath6kl"); + if (!ar->ath6kl_wq) + return -ENOMEM; + + ret = ath6kl_bmi_init(ar); + if (ret) + goto err_wq; + + /* + * Turn on power to get hardware (target) version and leave power + * on delibrately as we will boot the hardware anyway within few + * seconds. + */ + ret = ath6kl_hif_power_on(ar); + if (ret) + goto err_bmi_cleanup; + + ret = ath6kl_bmi_get_target_info(ar, &targ_info); + if (ret) + goto err_power_off; + + ar->version.target_ver = le32_to_cpu(targ_info.version); + ar->target_type = le32_to_cpu(targ_info.type); + ar->wiphy->hw_version = le32_to_cpu(targ_info.version); + + ret = ath6kl_init_hw_params(ar); + if (ret) + goto err_power_off; + + ar->htc_target = ath6kl_htc_create(ar); + + if (!ar->htc_target) { + ret = -ENOMEM; + goto err_power_off; + } + + ar->testmode = testmode; + + ret = ath6kl_init_fetch_firmwares(ar); + if (ret) + goto err_htc_cleanup; + + /* FIXME: we should free all firmwares in the error cases below */ + + /* Indicate that WMI is enabled (although not ready yet) */ + set_bit(WMI_ENABLED, &ar->flag); + ar->wmi = ath6kl_wmi_init(ar); + if (!ar->wmi) { + ath6kl_err("failed to initialize wmi\n"); + ret = -EIO; + goto err_htc_cleanup; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: got wmi @ 0x%p.\n", __func__, ar->wmi); + + /* setup access class priority mappings */ + ar->ac_stream_pri_map[WMM_AC_BK] = 0; /* lowest */ + ar->ac_stream_pri_map[WMM_AC_BE] = 1; + ar->ac_stream_pri_map[WMM_AC_VI] = 2; + ar->ac_stream_pri_map[WMM_AC_VO] = 3; /* highest */ + + /* allocate some buffers that handle larger AMSDU frames */ + ath6kl_refill_amsdu_rxbufs(ar, ATH6KL_MAX_AMSDU_RX_BUFFERS); + + ath6kl_cookie_init(ar); + + ar->conf_flags = ATH6KL_CONF_IGNORE_ERP_BARKER | + ATH6KL_CONF_ENABLE_11N | ATH6KL_CONF_ENABLE_TX_BURST; + + if (suspend_mode && + suspend_mode >= WLAN_POWER_STATE_CUT_PWR && + suspend_mode <= WLAN_POWER_STATE_WOW) + ar->suspend_mode = suspend_mode; + else + ar->suspend_mode = 0; + + if (suspend_mode == WLAN_POWER_STATE_WOW && + (wow_mode == WLAN_POWER_STATE_CUT_PWR || + wow_mode == WLAN_POWER_STATE_DEEP_SLEEP)) + ar->wow_suspend_mode = wow_mode; + else + ar->wow_suspend_mode = 0; + + if (uart_debug) + ar->conf_flags |= ATH6KL_CONF_UART_DEBUG; + + set_bit(FIRST_BOOT, &ar->flag); + + ath6kl_debug_init(ar); + + ret = ath6kl_init_hw_start(ar); + if (ret) { + ath6kl_err("Failed to start hardware: %d\n", ret); + goto err_rxbuf_cleanup; + } + + /* give our connected endpoints some buffers */ + ath6kl_rx_refill(ar->htc_target, ar->ctrl_ep); + ath6kl_rx_refill(ar->htc_target, ar->ac2ep_map[WMM_AC_BE]); + + ret = ath6kl_cfg80211_init(ar); + if (ret) + goto err_rxbuf_cleanup; + + ret = ath6kl_debug_init_fs(ar); + if (ret) { + wiphy_unregister(ar->wiphy); + goto err_rxbuf_cleanup; + } + + for (i = 0; i < ar->vif_max; i++) + ar->avail_idx_map |= BIT(i); + + rtnl_lock(); + + /* Add an initial station interface */ + wdev = ath6kl_interface_add(ar, "wlan%d", NL80211_IFTYPE_STATION, 0, + INFRA_NETWORK); + + rtnl_unlock(); + + if (!wdev) { + ath6kl_err("Failed to instantiate a network device\n"); + ret = -ENOMEM; + wiphy_unregister(ar->wiphy); + goto err_rxbuf_cleanup; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: name=%s dev=0x%p, ar=0x%p\n", + __func__, wdev->netdev->name, wdev->netdev, ar); + + ar->fw_recovery.enable = !!recovery_enable; + if (!ar->fw_recovery.enable) + return ret; + + if (heart_beat_poll && + test_bit(ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, + ar->fw_capabilities)) + ar->fw_recovery.hb_poll = heart_beat_poll; + + ath6kl_recovery_init(ar); + + return ret; + +err_rxbuf_cleanup: + ath6kl_debug_cleanup(ar); + ath6kl_htc_flush_rx_buf(ar->htc_target); + ath6kl_cleanup_amsdu_rxbufs(ar); + ath6kl_wmi_shutdown(ar->wmi); + clear_bit(WMI_ENABLED, &ar->flag); + ar->wmi = NULL; +err_htc_cleanup: + ath6kl_htc_cleanup(ar->htc_target); +err_power_off: + ath6kl_hif_power_off(ar); +err_bmi_cleanup: + ath6kl_bmi_cleanup(ar); +err_wq: + destroy_workqueue(ar->ath6kl_wq); + + return ret; +} +EXPORT_SYMBOL(ath6kl_core_init); + +struct ath6kl *ath6kl_core_create(struct device *dev) +{ + struct ath6kl *ar; + u8 ctr; + + ar = ath6kl_cfg80211_create(); + if (!ar) + return NULL; + + ar->p2p = !!ath6kl_p2p; + ar->dev = dev; + + ar->vif_max = 1; + + ar->max_norm_iface = 1; + + spin_lock_init(&ar->lock); + spin_lock_init(&ar->mcastpsq_lock); + spin_lock_init(&ar->list_lock); + + init_waitqueue_head(&ar->event_wq); + sema_init(&ar->sem, 1); + + INIT_LIST_HEAD(&ar->amsdu_rx_buffer_queue); + INIT_LIST_HEAD(&ar->vif_list); + + clear_bit(WMI_ENABLED, &ar->flag); + clear_bit(SKIP_SCAN, &ar->flag); + clear_bit(DESTROY_IN_PROGRESS, &ar->flag); + + ar->tx_pwr = 0; + ar->intra_bss = 1; + ar->lrssi_roam_threshold = DEF_LRSSI_ROAM_THRESHOLD; + + ar->state = ATH6KL_STATE_OFF; + + memset((u8 *)ar->sta_list, 0, + AP_MAX_NUM_STA * sizeof(struct ath6kl_sta)); + + /* Init the PS queues */ + for (ctr = 0; ctr < AP_MAX_NUM_STA; ctr++) { + spin_lock_init(&ar->sta_list[ctr].psq_lock); + skb_queue_head_init(&ar->sta_list[ctr].psq); + skb_queue_head_init(&ar->sta_list[ctr].apsdq); + ar->sta_list[ctr].mgmt_psq_len = 0; + INIT_LIST_HEAD(&ar->sta_list[ctr].mgmt_psq); + ar->sta_list[ctr].aggr_conn = + kzalloc(sizeof(struct aggr_info_conn), GFP_KERNEL); + if (!ar->sta_list[ctr].aggr_conn) { + ath6kl_err("Failed to allocate memory for sta aggregation information\n"); + ath6kl_core_destroy(ar); + return NULL; + } + } + + skb_queue_head_init(&ar->mcastpsq); + + memcpy(ar->ap_country_code, DEF_AP_COUNTRY_CODE, 3); + + return ar; +} +EXPORT_SYMBOL(ath6kl_core_create); + +void ath6kl_core_cleanup(struct ath6kl *ar) +{ + ath6kl_hif_power_off(ar); + + ath6kl_recovery_cleanup(ar); + + destroy_workqueue(ar->ath6kl_wq); + + if (ar->htc_target) + ath6kl_htc_cleanup(ar->htc_target); + + ath6kl_cookie_cleanup(ar); + + ath6kl_cleanup_amsdu_rxbufs(ar); + + ath6kl_bmi_cleanup(ar); + + ath6kl_debug_cleanup(ar); + + kfree(ar->fw_board); + kfree(ar->fw_otp); + vfree(ar->fw); + kfree(ar->fw_patch); + kfree(ar->fw_testscript); + + ath6kl_cfg80211_cleanup(ar); +} +EXPORT_SYMBOL(ath6kl_core_cleanup); + +void ath6kl_core_destroy(struct ath6kl *ar) +{ + ath6kl_cfg80211_destroy(ar); +} +EXPORT_SYMBOL(ath6kl_core_destroy); + +MODULE_AUTHOR("Qualcomm Atheros"); +MODULE_DESCRIPTION("Core module for AR600x SDIO and USB devices."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h index c863a28f2e0..26b0f92424e 100644 --- a/drivers/net/wireless/ath/ath6kl/core.h +++ b/drivers/net/wireless/ath/ath6kl/core.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -44,6 +45,10 @@ #define ATH6KL_MAX_ENDPOINTS 4 #define MAX_NODE_NUM 15 +#define ATH6KL_APSD_ALL_FRAME 0xFFFF +#define ATH6KL_APSD_NUM_OF_AC 0x4 +#define ATH6KL_APSD_FRAME_MASK 0xF + /* Extra bytes for htc header alignment */ #define ATH6KL_HTC_ALIGN_BYTES 3 @@ -55,8 +60,9 @@ #define MAX_DEFAULT_SEND_QUEUE_DEPTH (MAX_DEF_COOKIE_NUM / WMM_NUM_AC) #define DISCON_TIMER_INTVAL 10000 /* in msec */ -#define A_DEFAULT_LISTEN_INTERVAL 100 -#define A_MAX_WOW_LISTEN_INTERVAL 1000 + +/* Channel dwell time in fg scan */ +#define ATH6KL_FG_SCAN_INTERVAL 50 /* in ms */ /* includes also the null byte */ #define ATH6KL_FIRMWARE_MAGIC "QCA-ATH6KL" @@ -85,6 +91,51 @@ enum ath6kl_fw_capability { */ ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + /* + * Firmware has support to cleanup inactive stations + * in AP mode. + */ + ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, + + /* Firmware has support to override rsn cap of rsn ie */ + ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + + /* + * Multicast support in WOW and host awake mode. + * Allow all multicast in host awake mode. + * Apply multicast filter in WOW mode. + */ + ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + + /* Firmware supports enhanced bmiss detection */ + ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, + + /* + * FW supports matching of ssid in schedule scan + */ + ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, + + /* Firmware supports filtering BSS results by RSSI */ + ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, + + /* FW sets mac_addr[4] ^= 0x80 for newly created interfaces */ + ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, + + /* Firmware supports TX error rate notification */ + ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, + + /* supports WMI_SET_REGDOMAIN_CMDID command */ + ATH6KL_FW_CAPABILITY_REGDOMAIN, + + /* Firmware supports sched scan decoupled from host sleep */ + ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, + + /* + * Firmware capability for hang detection through heart beat + * challenge messages. + */ + ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, + /* this needs to be last */ ATH6KL_FW_CAPABILITY_MAX, }; @@ -97,53 +148,82 @@ struct ath6kl_fw_ie { u8 data[0]; }; +enum ath6kl_hw_flags { + ATH6KL_HW_64BIT_RATES = BIT(0), + ATH6KL_HW_AP_INACTIVITY_MINS = BIT(1), + ATH6KL_HW_MAP_LP_ENDPOINT = BIT(2), + ATH6KL_HW_SDIO_CRC_ERROR_WAR = BIT(3), +}; + +#define ATH6KL_FW_API2_FILE "fw-2.bin" +#define ATH6KL_FW_API3_FILE "fw-3.bin" +#define ATH6KL_FW_API4_FILE "fw-4.bin" + /* AR6003 1.0 definitions */ #define AR6003_HW_1_0_VERSION 0x300002ba /* AR6003 2.0 definitions */ #define AR6003_HW_2_0_VERSION 0x30000384 #define AR6003_HW_2_0_PATCH_DOWNLOAD_ADDRESS 0x57e910 -#define AR6003_HW_2_0_OTP_FILE "ath6k/AR6003/hw2.0/otp.bin.z77" -#define AR6003_HW_2_0_FIRMWARE_FILE "ath6k/AR6003/hw2.0/athwlan.bin.z77" -#define AR6003_HW_2_0_TCMD_FIRMWARE_FILE "ath6k/AR6003/hw2.0/athtcmd_ram.bin" -#define AR6003_HW_2_0_PATCH_FILE "ath6k/AR6003/hw2.0/data.patch.bin" -#define AR6003_HW_2_0_FIRMWARE_2_FILE "ath6k/AR6003/hw2.0/fw-2.bin" -#define AR6003_HW_2_0_BOARD_DATA_FILE "ath6k/AR6003/hw2.0/bdata.bin" +#define AR6003_HW_2_0_FW_DIR "ath6k/AR6003/hw2.0" +#define AR6003_HW_2_0_OTP_FILE "otp.bin.z77" +#define AR6003_HW_2_0_FIRMWARE_FILE "athwlan.bin.z77" +#define AR6003_HW_2_0_TCMD_FIRMWARE_FILE "athtcmd_ram.bin" +#define AR6003_HW_2_0_PATCH_FILE "data.patch.bin" +#define AR6003_HW_2_0_BOARD_DATA_FILE AR6003_HW_2_0_FW_DIR "/bdata.bin" #define AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE \ - "ath6k/AR6003/hw2.0/bdata.SD31.bin" + AR6003_HW_2_0_FW_DIR "/bdata.SD31.bin" /* AR6003 3.0 definitions */ #define AR6003_HW_2_1_1_VERSION 0x30000582 -#define AR6003_HW_2_1_1_OTP_FILE "ath6k/AR6003/hw2.1.1/otp.bin" -#define AR6003_HW_2_1_1_FIRMWARE_FILE "ath6k/AR6003/hw2.1.1/athwlan.bin" -#define AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE \ - "ath6k/AR6003/hw2.1.1/athtcmd_ram.bin" -#define AR6003_HW_2_1_1_PATCH_FILE "ath6k/AR6003/hw2.1.1/data.patch.bin" -#define AR6003_HW_2_1_1_FIRMWARE_2_FILE "ath6k/AR6003/hw2.1.1/fw-2.bin" -#define AR6003_HW_2_1_1_BOARD_DATA_FILE "ath6k/AR6003/hw2.1.1/bdata.bin" +#define AR6003_HW_2_1_1_FW_DIR "ath6k/AR6003/hw2.1.1" +#define AR6003_HW_2_1_1_OTP_FILE "otp.bin" +#define AR6003_HW_2_1_1_FIRMWARE_FILE "athwlan.bin" +#define AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE "athtcmd_ram.bin" +#define AR6003_HW_2_1_1_UTF_FIRMWARE_FILE "utf.bin" +#define AR6003_HW_2_1_1_TESTSCRIPT_FILE "nullTestFlow.bin" +#define AR6003_HW_2_1_1_PATCH_FILE "data.patch.bin" +#define AR6003_HW_2_1_1_BOARD_DATA_FILE AR6003_HW_2_1_1_FW_DIR "/bdata.bin" #define AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE \ - "ath6k/AR6003/hw2.1.1/bdata.SD31.bin" + AR6003_HW_2_1_1_FW_DIR "/bdata.SD31.bin" /* AR6004 1.0 definitions */ #define AR6004_HW_1_0_VERSION 0x30000623 -#define AR6004_HW_1_0_FIRMWARE_2_FILE "ath6k/AR6004/hw1.0/fw-2.bin" -#define AR6004_HW_1_0_FIRMWARE_FILE "ath6k/AR6004/hw1.0/fw.ram.bin" -#define AR6004_HW_1_0_BOARD_DATA_FILE "ath6k/AR6004/hw1.0/bdata.bin" +#define AR6004_HW_1_0_FW_DIR "ath6k/AR6004/hw1.0" +#define AR6004_HW_1_0_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_0_BOARD_DATA_FILE AR6004_HW_1_0_FW_DIR "/bdata.bin" #define AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE \ - "ath6k/AR6004/hw1.0/bdata.DB132.bin" + AR6004_HW_1_0_FW_DIR "/bdata.DB132.bin" /* AR6004 1.1 definitions */ #define AR6004_HW_1_1_VERSION 0x30000001 -#define AR6004_HW_1_1_FIRMWARE_2_FILE "ath6k/AR6004/hw1.1/fw-2.bin" -#define AR6004_HW_1_1_FIRMWARE_FILE "ath6k/AR6004/hw1.1/fw.ram.bin" -#define AR6004_HW_1_1_BOARD_DATA_FILE "ath6k/AR6004/hw1.1/bdata.bin" +#define AR6004_HW_1_1_FW_DIR "ath6k/AR6004/hw1.1" +#define AR6004_HW_1_1_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_1_BOARD_DATA_FILE AR6004_HW_1_1_FW_DIR "/bdata.bin" #define AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE \ - "ath6k/AR6004/hw1.1/bdata.DB132.bin" + AR6004_HW_1_1_FW_DIR "/bdata.DB132.bin" + +/* AR6004 1.2 definitions */ +#define AR6004_HW_1_2_VERSION 0x300007e8 +#define AR6004_HW_1_2_FW_DIR "ath6k/AR6004/hw1.2" +#define AR6004_HW_1_2_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_2_BOARD_DATA_FILE AR6004_HW_1_2_FW_DIR "/bdata.bin" +#define AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE \ + AR6004_HW_1_2_FW_DIR "/bdata.bin" + +/* AR6004 1.3 definitions */ +#define AR6004_HW_1_3_VERSION 0x31c8088a +#define AR6004_HW_1_3_FW_DIR "ath6k/AR6004/hw1.3" +#define AR6004_HW_1_3_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_3_BOARD_DATA_FILE "ath6k/AR6004/hw1.3/bdata.bin" +#define AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE "ath6k/AR6004/hw1.3/bdata.bin" /* Per STA data, used in AP mode */ #define STA_PS_AWAKE BIT(0) #define STA_PS_SLEEP BIT(1) #define STA_PS_POLLED BIT(2) +#define STA_PS_APSD_TRIGGER BIT(3) +#define STA_PS_APSD_EOSP BIT(4) /* HTC TX packet tagging definitions */ #define ATH6KL_CONTROL_PKT_TAG HTC_TX_PACKET_TAG_USER_DEFINED @@ -167,12 +247,17 @@ struct ath6kl_fw_ie { #define AGGR_NUM_OF_FREE_NETBUFS 16 -#define AGGR_RX_TIMEOUT 400 /* in ms */ +#define AGGR_RX_TIMEOUT 100 /* in ms */ #define WMI_TIMEOUT (2 * HZ) #define MBOX_YIELD_LIMIT 99 +#define ATH6KL_DEFAULT_LISTEN_INTVAL 100 /* in TUs */ +#define ATH6KL_DEFAULT_BMISS_TIME 1500 +#define ATH6KL_MAX_WOW_LISTEN_INTL 300 /* in TUs */ +#define ATH6KL_MAX_BMISS_TIME 5000 + /* configuration lags */ /* * ATH6KL_CONF_IGNORE_ERP_BARKER: Ignore the barker premable in @@ -186,7 +271,9 @@ struct ath6kl_fw_ie { #define ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN BIT(1) #define ATH6KL_CONF_ENABLE_11N BIT(2) #define ATH6KL_CONF_ENABLE_TX_BURST BIT(3) -#define ATH6KL_CONF_SUSPEND_CUTPOWER BIT(4) +#define ATH6KL_CONF_UART_DEBUG BIT(4) + +#define P2P_WILDCARD_SSID_LEN 7 /* DIRECT- */ enum wlan_low_pwr_state { WLAN_POWER_STATE_ON, @@ -209,13 +296,24 @@ struct skb_hold_q { struct rxtid { bool aggr; - bool progress; bool timer_mon; u16 win_sz; u16 seq_next; u32 hold_q_sz; struct skb_hold_q *hold_q; struct sk_buff_head q; + + /* + * lock mainly protects seq_next and hold_q. Movement of seq_next + * needs to be protected between aggr_timeout() and + * aggr_process_recv_frm(). hold_q will be holding the pending + * reorder frames and it's access should also be protected. + * Some of the other fields like hold_q_sz, win_sz and aggr are + * initialized/reset when receiving addba/delba req, also while + * deleting aggr state all the pending buffers are flushed before + * resetting these fields, so there should not be any race in accessing + * these fields. + */ spinlock_t lock; }; @@ -231,14 +329,19 @@ struct rxtid_stats { u32 num_bar; }; -struct aggr_info { +struct aggr_info_conn { u8 aggr_sz; u8 timer_scheduled; struct timer_list timer; struct net_device *dev; struct rxtid rx_tid[NUM_OF_TIDS]; - struct sk_buff_head free_q; struct rxtid_stats stat[NUM_OF_TIDS]; + struct aggr_info *aggr_info; +}; + +struct aggr_info { + struct aggr_info_conn *aggr_conn; + struct sk_buff_head rx_amsdu_freeq; }; struct ath6kl_wep_key { @@ -270,6 +373,16 @@ struct ath6kl_cookie { struct ath6kl_cookie *arc_list_next; }; +struct ath6kl_mgmt_buff { + struct list_head list; + u32 freq; + u32 wait; + u32 id; + bool no_cck; + size_t len; + u8 buf[0]; +}; + struct ath6kl_sta { u16 sta_flags; u8 mac[ETH_ALEN]; @@ -279,7 +392,15 @@ struct ath6kl_sta { u8 auth; u8 wpa_ie[ATH6KL_MAX_IE]; struct sk_buff_head psq; + + /* protects psq, mgmt_psq, apsdq, and mgmt_psq_len fields */ spinlock_t psq_lock; + + struct list_head mgmt_psq; + size_t mgmt_psq_len; + u8 apsd_info; + struct sk_buff_head apsdq; + struct aggr_info_conn *aggr_conn; }; struct ath6kl_version { @@ -408,6 +529,24 @@ enum ath6kl_hif_type { ATH6KL_HIF_TYPE_USB, }; +enum ath6kl_htc_type { + ATH6KL_HTC_TYPE_MBOX, + ATH6KL_HTC_TYPE_PIPE, +}; + +/* Max number of filters that hw supports */ +#define ATH6K_MAX_MC_FILTERS_PER_LIST 7 +struct ath6kl_mc_filter { + struct list_head list; + char hw_addr[ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE]; +}; + +struct ath6kl_htcap { + bool ht_enable; + u8 ampdu_factor; + unsigned short cap_info; +}; + /* * Driver's maximum limit, note that some firmwares support only one vif * and the runtime (current) limit must be checked from ar->vif_max. @@ -421,11 +560,14 @@ enum ath6kl_vif_state { WMM_ENABLED, NETQ_STOPPED, DTIM_EXPIRED, - NETDEV_REGISTERED, CLEAR_BSSFILTER_ON_BEACON, DTIM_PERIOD_AVAIL, WLAN_ENABLED, STATS_UPDATE_PEND, + HOST_SLEEP_MODE_CMD_PROCESSED, + NETDEV_MCAST_ALL_ON, + NETDEV_MCAST_ALL_OFF, + SCHED_SCANNING, }; struct ath6kl_vif { @@ -455,6 +597,7 @@ struct ath6kl_vif { struct ath6kl_wep_key wep_key_list[WMI_MAX_KEY_INDEX + 1]; struct ath6kl_key keys[WMI_MAX_KEY_INDEX + 1]; struct aggr_info *aggr_cntxt; + struct ath6kl_htcap htcap[IEEE80211_NUM_BANDS]; struct timer_list disconnect_timer; struct timer_list sched_scan_timer; @@ -466,13 +609,25 @@ struct ath6kl_vif { u32 last_cancel_roc_id; u32 send_action_id; bool probe_req_report; - u16 next_chan; u16 assoc_bss_beacon_int; + u16 listen_intvl_t; + u16 bmiss_time_t; + u32 txe_intvl; + u16 bg_scan_period; u8 assoc_bss_dtim_period; struct net_device_stats net_stats; struct target_stats target_stats; + struct wmi_connect_cmd profile; + u16 rsn_capab; + + struct list_head mc_filter; }; +static inline struct ath6kl_vif *ath6kl_vif_from_wdev(struct wireless_dev *wdev) +{ + return container_of(wdev, struct ath6kl_vif, wdev); +} + #define WOW_LIST_ID 0 #define WOW_HOST_REQ_DELAY 500 /* ms */ @@ -488,15 +643,27 @@ enum ath6kl_dev_state { SKIP_SCAN, ROAM_TBL_PEND, FIRST_BOOT, + RECOVERY_CLEANUP, }; enum ath6kl_state { ATH6KL_STATE_OFF, ATH6KL_STATE_ON, + ATH6KL_STATE_SUSPENDING, + ATH6KL_STATE_RESUMING, ATH6KL_STATE_DEEPSLEEP, ATH6KL_STATE_CUTPOWER, ATH6KL_STATE_WOW, - ATH6KL_STATE_SCHED_SCAN, + ATH6KL_STATE_RECOVERY, +}; + +/* Fw error recovery */ +#define ATH6KL_HB_RESP_MISS_THRES 5 + +enum ath6kl_fw_err { + ATH6KL_FW_ASSERT, + ATH6KL_FW_HB_RESP_FAILURE, + ATH6KL_FW_EP_FULL, }; struct ath6kl { @@ -504,9 +671,11 @@ struct ath6kl { struct wiphy *wiphy; enum ath6kl_state state; + unsigned int testmode; struct ath6kl_bmi bmi; const struct ath6kl_hif_ops *hif_ops; + const struct ath6kl_htc_ops *htc_ops; struct wmi *wmi; int tx_pending[ENDPOINT_MAX]; int total_tx_data_pend; @@ -520,10 +689,14 @@ struct ath6kl { unsigned int vif_max; u8 max_norm_iface; u8 avail_idx_map; + + /* + * Protects at least amsdu_rx_buffer_queue, ath6kl_alloc_cookie() + * calls, tx_pending and total_tx_data_pend. + */ spinlock_t lock; + struct semaphore sem; - u16 listen_intvl_b; - u16 listen_intvl_t; u8 lrssi_roam_threshold; struct ath6kl_version version; u32 target_type; @@ -549,7 +722,15 @@ struct ath6kl { u8 sta_list_index; struct ath6kl_req_key ap_mode_bkey; struct sk_buff_head mcastpsq; + u32 want_ch_switch; + u16 last_ch; + + /* + * FIXME: protects access to mcastpsq but is actually useless as + * all skbe_queue_*() functions provide serialisation themselves + */ spinlock_t mcastpsq_lock; + u8 intra_bss; struct wmi_ap_mode_stat ap_stats; u8 ap_country_code[3]; @@ -574,17 +755,28 @@ struct ath6kl { u32 board_addr; u32 refclk_hz; u32 uarttx_pin; + u32 testscript_addr; + enum wmi_phy_cap cap; + + u32 flags; + + struct ath6kl_hw_fw { + const char *dir; + const char *otp; + const char *fw; + const char *tcmd; + const char *patch; + const char *utf; + const char *testscript; + } fw; - const char *fw_otp; - const char *fw; - const char *fw_tcmd; - const char *fw_patch; - const char *fw_api2; const char *fw_board; const char *fw_default_board; } hw; u16 conf_flags; + u16 suspend_mode; + u16 wow_suspend_mode; wait_queue_head_t event_wq; struct ath6kl_mbox_info mbox_info; @@ -603,6 +795,10 @@ struct ath6kl { u8 *fw_patch; size_t fw_patch_len; + u8 *fw_testscript; + size_t fw_testscript_len; + + unsigned int fw_api; unsigned long fw_capabilities[ATH6KL_CAPABILITY_LEN]; struct workqueue_struct *ath6kl_wq; @@ -611,12 +807,27 @@ struct ath6kl { bool p2p; + bool wiphy_registered; + + struct ath6kl_fw_recovery { + struct work_struct recovery_work; + unsigned long err_reason; + unsigned long hb_poll; + struct timer_list hb_timer; + u32 seq_num; + bool hb_pending; + u8 hb_misscnt; + bool enable; + } fw_recovery; + #ifdef CONFIG_ATH6KL_DEBUG struct { - struct circ_buf fwlog_buf; - spinlock_t fwlog_lock; - void *fwlog_tmp; + struct sk_buff_head fwlog_queue; + struct completion fwlog_completion; + bool fwlog_open; + u32 fwlog_mask; + unsigned int dbgfs_diag_reg; u32 diag_reg_addr_wr; u32 diag_reg_val_wr; @@ -659,7 +870,8 @@ void init_netdev(struct net_device *dev); void ath6kl_cookie_init(struct ath6kl *ar); void ath6kl_cookie_cleanup(struct ath6kl *ar); void ath6kl_rx(struct htc_target *target, struct htc_packet *packet); -void ath6kl_tx_complete(void *context, struct list_head *packet_queue); +void ath6kl_tx_complete(struct htc_target *context, + struct list_head *packet_queue); enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, struct htc_packet *packet); void ath6kl_stop_txrx(struct ath6kl *ar); @@ -676,7 +888,9 @@ struct ath6kl_cookie *ath6kl_alloc_cookie(struct ath6kl *ar); void ath6kl_free_cookie(struct ath6kl *ar, struct ath6kl_cookie *cookie); int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev); -struct aggr_info *aggr_init(struct net_device *dev); +struct aggr_info *aggr_init(struct ath6kl_vif *vif); +void aggr_conn_init(struct ath6kl_vif *vif, struct aggr_info *aggr_info, + struct aggr_info_conn *aggr_conn); void ath6kl_rx_refill(struct htc_target *target, enum htc_endpoint_id endpoint); void ath6kl_refill_amsdu_rxbufs(struct ath6kl *ar, int count); @@ -684,12 +898,13 @@ struct htc_packet *ath6kl_alloc_amsdu_rxbuf(struct htc_target *target, enum htc_endpoint_id endpoint, int len); void aggr_module_destroy(struct aggr_info *aggr_info); -void aggr_reset_state(struct aggr_info *aggr_info); +void aggr_reset_state(struct aggr_info_conn *aggr_conn); -struct ath6kl_sta *ath6kl_find_sta(struct ath6kl_vif *vif, u8 * node_addr); +struct ath6kl_sta *ath6kl_find_sta(struct ath6kl_vif *vif, u8 *node_addr); struct ath6kl_sta *ath6kl_find_sta_by_aid(struct ath6kl *ar, u8 aid); -void ath6kl_ready_event(void *devt, u8 * datap, u32 sw_ver, u32 abi_ver); +void ath6kl_ready_event(void *devt, u8 *datap, u32 sw_ver, u32 abi_ver, + enum wmi_phy_cap cap); int ath6kl_control_tx(void *devt, struct sk_buff *skb, enum htc_endpoint_id eid); void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, @@ -700,7 +915,7 @@ void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel); void ath6kl_connect_ap_mode_sta(struct ath6kl_vif *vif, u16 aid, u8 *mac_addr, u8 keymgmt, u8 ucipher, u8 auth, - u8 assoc_req_len, u8 *assoc_info); + u8 assoc_req_len, u8 *assoc_info, u8 apsd_info); void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, u8 *bssid, u8 assoc_resp_len, u8 *assoc_info, u16 prot_reason_status); @@ -720,15 +935,30 @@ void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid, u16 seq_no, u8 win_sz); void ath6kl_wakeup_event(void *dev); -void ath6kl_reset_device(struct ath6kl *ar, u32 target_type, - bool wait_fot_compltn, bool cold_reset); void ath6kl_init_control_info(struct ath6kl_vif *vif); -void ath6kl_deinit_if_data(struct ath6kl_vif *vif); -void ath6kl_core_free(struct ath6kl *ar); struct ath6kl_vif *ath6kl_vif_first(struct ath6kl *ar); -void ath6kl_cleanup_vif(struct ath6kl_vif *vif, bool wmi_ready); +void ath6kl_cfg80211_vif_stop(struct ath6kl_vif *vif, bool wmi_ready); int ath6kl_init_hw_start(struct ath6kl *ar); int ath6kl_init_hw_stop(struct ath6kl *ar); +int ath6kl_init_fetch_firmwares(struct ath6kl *ar); +int ath6kl_init_hw_params(struct ath6kl *ar); + void ath6kl_check_wow_status(struct ath6kl *ar); +void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb); +void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); + +struct ath6kl *ath6kl_core_create(struct device *dev); +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type); +void ath6kl_core_cleanup(struct ath6kl *ar); +void ath6kl_core_destroy(struct ath6kl *ar); + +/* Fw error recovery */ +void ath6kl_init_hw_restart(struct ath6kl *ar); +void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason); +void ath6kl_recovery_hb_event(struct ath6kl *ar, u32 cookie); +void ath6kl_recovery_init(struct ath6kl *ar); +void ath6kl_recovery_cleanup(struct ath6kl *ar); +void ath6kl_recovery_suspend(struct ath6kl *ar); +void ath6kl_recovery_resume(struct ath6kl *ar); #endif /* CORE_H */ diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c index eb808b46f94..55c4064dd50 100644 --- a/drivers/net/wireless/ath/ath6kl/debug.c +++ b/drivers/net/wireless/ath/ath6kl/debug.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,7 +17,7 @@ #include "core.h" -#include <linux/circ_buf.h> +#include <linux/skbuff.h> #include <linux/fs.h> #include <linux/vmalloc.h> #include <linux/export.h> @@ -32,9 +33,8 @@ struct ath6kl_fwlog_slot { u8 payload[0]; }; -#define ATH6KL_FWLOG_SIZE 32768 -#define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \ - ATH6KL_FWLOG_PAYLOAD_SIZE) +#define ATH6KL_FWLOG_MAX_ENTRIES 20 + #define ATH6KL_FWLOG_VALID_MASK 0x1ffff int ath6kl_printk(const char *level, const char *fmt, ...) @@ -54,9 +54,100 @@ int ath6kl_printk(const char *level, const char *fmt, ...) return rtn; } +EXPORT_SYMBOL(ath6kl_printk); + +int ath6kl_info(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_INFO, "%pV", &vaf); + trace_ath6kl_log_info(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_info); + +int ath6kl_err(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_ERR, "%pV", &vaf); + trace_ath6kl_log_err(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_err); + +int ath6kl_warn(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_WARNING, "%pV", &vaf); + trace_ath6kl_log_warn(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_warn); #ifdef CONFIG_ATH6KL_DEBUG +void ath6kl_dbg(enum ATH6K_DEBUG_MASK mask, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (debug_mask & mask) + ath6kl_printk(KERN_DEBUG, "%pV", &vaf); + + trace_ath6kl_log_dbg(mask, &vaf); + + va_end(args); +} +EXPORT_SYMBOL(ath6kl_dbg); + +void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, + const char *msg, const char *prefix, + const void *buf, size_t len) +{ + if (debug_mask & mask) { + if (msg) + ath6kl_dbg(mask, "%s\n", msg); + + print_hex_dump_bytes(prefix, DUMP_PREFIX_OFFSET, buf, len); + } + + /* tracing code doesn't like null strings :/ */ + trace_ath6kl_log_dbg_dump(msg ? msg : "", prefix ? prefix : "", + buf, len); +} +EXPORT_SYMBOL(ath6kl_dbg_dump); + #define REG_OUTPUT_LEN_PER_LINE 25 #define REGTYPE_STR_LEN 100 @@ -81,64 +172,62 @@ void ath6kl_dump_registers(struct ath6kl_device *dev, struct ath6kl_irq_proc_registers *irq_proc_reg, struct ath6kl_irq_enable_reg *irq_enable_reg) { - - ath6kl_dbg(ATH6KL_DBG_ANY, ("<------- Register Table -------->\n")); + ath6kl_dbg(ATH6KL_DBG_IRQ, ("<------- Register Table -------->\n")); if (irq_proc_reg != NULL) { - ath6kl_dbg(ATH6KL_DBG_ANY, - "Host Int status: 0x%x\n", - irq_proc_reg->host_int_status); - ath6kl_dbg(ATH6KL_DBG_ANY, + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Host Int status: 0x%x\n", + irq_proc_reg->host_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, "CPU Int status: 0x%x\n", - irq_proc_reg->cpu_int_status); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->cpu_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Error Int status: 0x%x\n", - irq_proc_reg->error_int_status); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->error_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Counter Int status: 0x%x\n", - irq_proc_reg->counter_int_status); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->counter_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Mbox Frame: 0x%x\n", - irq_proc_reg->mbox_frame); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->mbox_frame); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Rx Lookahead Valid: 0x%x\n", - irq_proc_reg->rx_lkahd_valid); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->rx_lkahd_valid); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Rx Lookahead 0: 0x%x\n", - irq_proc_reg->rx_lkahd[0]); - ath6kl_dbg(ATH6KL_DBG_ANY, + irq_proc_reg->rx_lkahd[0]); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Rx Lookahead 1: 0x%x\n", - irq_proc_reg->rx_lkahd[1]); + irq_proc_reg->rx_lkahd[1]); if (dev->ar->mbox_info.gmbox_addr != 0) { /* * If the target supports GMBOX hardware, dump some * additional state. */ - ath6kl_dbg(ATH6KL_DBG_ANY, - "GMBOX Host Int status 2: 0x%x\n", - irq_proc_reg->host_int_status2); - ath6kl_dbg(ATH6KL_DBG_ANY, - "GMBOX RX Avail: 0x%x\n", - irq_proc_reg->gmbox_rx_avail); - ath6kl_dbg(ATH6KL_DBG_ANY, - "GMBOX lookahead alias 0: 0x%x\n", - irq_proc_reg->rx_gmbox_lkahd_alias[0]); - ath6kl_dbg(ATH6KL_DBG_ANY, - "GMBOX lookahead alias 1: 0x%x\n", - irq_proc_reg->rx_gmbox_lkahd_alias[1]); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX Host Int status 2: 0x%x\n", + irq_proc_reg->host_int_status2); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX RX Avail: 0x%x\n", + irq_proc_reg->gmbox_rx_avail); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX lookahead alias 0: 0x%x\n", + irq_proc_reg->rx_gmbox_lkahd_alias[0]); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX lookahead alias 1: 0x%x\n", + irq_proc_reg->rx_gmbox_lkahd_alias[1]); } - } if (irq_enable_reg != NULL) { - ath6kl_dbg(ATH6KL_DBG_ANY, - "Int status Enable: 0x%x\n", - irq_enable_reg->int_status_en); - ath6kl_dbg(ATH6KL_DBG_ANY, "Counter Int status Enable: 0x%x\n", - irq_enable_reg->cntr_int_status_en); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Int status Enable: 0x%x\n", + irq_enable_reg->int_status_en); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Counter Int status Enable: 0x%x\n", + irq_enable_reg->cntr_int_status_en); } - ath6kl_dbg(ATH6KL_DBG_ANY, "<------------------------------->\n"); + ath6kl_dbg(ATH6KL_DBG_IRQ, "<------------------------------->\n"); } static void dump_cred_dist(struct htc_endpoint_credit_dist *ep_dist) @@ -175,9 +264,6 @@ void dump_cred_dist_stats(struct htc_target *target) { struct htc_endpoint_credit_dist *ep_list; - if (!AR_DBG_LVL_CHECK(ATH6KL_DBG_CREDIT)) - return; - list_for_each_entry(ep_list, &target->cred_dist_list, list) dump_cred_dist(ep_list); @@ -187,12 +273,6 @@ void dump_cred_dist_stats(struct htc_target *target) target->credit_info->cur_free_credits); } -static int ath6kl_debugfs_open(struct inode *inode, struct file *file) -{ - file->private_data = inode->i_private; - return 0; -} - void ath6kl_debug_war(struct ath6kl *ar, enum ath6kl_war war) { switch (war) { @@ -233,110 +313,108 @@ static ssize_t read_file_war_stats(struct file *file, char __user *user_buf, static const struct file_operations fops_war_stats = { .read = read_file_war_stats, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; -static void ath6kl_debug_fwlog_add(struct ath6kl *ar, const void *buf, - size_t buf_len) +void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len) { - struct circ_buf *fwlog = &ar->debug.fwlog_buf; - size_t space; - int i; + struct ath6kl_fwlog_slot *slot; + struct sk_buff *skb; + size_t slot_len; - /* entries must all be equal size */ - if (WARN_ON(buf_len != ATH6KL_FWLOG_SLOT_SIZE)) + if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE)) return; - space = CIRC_SPACE(fwlog->head, fwlog->tail, ATH6KL_FWLOG_SIZE); - if (space < buf_len) - /* discard oldest slot */ - fwlog->tail = (fwlog->tail + ATH6KL_FWLOG_SLOT_SIZE) & - (ATH6KL_FWLOG_SIZE - 1); + slot_len = sizeof(*slot) + ATH6KL_FWLOG_PAYLOAD_SIZE; - for (i = 0; i < buf_len; i += space) { - space = CIRC_SPACE_TO_END(fwlog->head, fwlog->tail, - ATH6KL_FWLOG_SIZE); + skb = alloc_skb(slot_len, GFP_KERNEL); + if (!skb) + return; - if ((size_t) space > buf_len - i) - space = buf_len - i; + slot = (struct ath6kl_fwlog_slot *) skb_put(skb, slot_len); + slot->timestamp = cpu_to_le32(jiffies); + slot->length = cpu_to_le32(len); + memcpy(slot->payload, buf, len); - memcpy(&fwlog->buf[fwlog->head], buf, space); - fwlog->head = (fwlog->head + space) & (ATH6KL_FWLOG_SIZE - 1); - } + /* Need to pad each record to fixed length ATH6KL_FWLOG_PAYLOAD_SIZE */ + memset(slot->payload + len, 0, ATH6KL_FWLOG_PAYLOAD_SIZE - len); -} + spin_lock(&ar->debug.fwlog_queue.lock); -void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len) -{ - struct ath6kl_fwlog_slot *slot = ar->debug.fwlog_tmp; - size_t slot_len; + __skb_queue_tail(&ar->debug.fwlog_queue, skb); + complete(&ar->debug.fwlog_completion); - if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE)) - return; + /* drop oldest entries */ + while (skb_queue_len(&ar->debug.fwlog_queue) > + ATH6KL_FWLOG_MAX_ENTRIES) { + skb = __skb_dequeue(&ar->debug.fwlog_queue); + kfree_skb(skb); + } - spin_lock_bh(&ar->debug.fwlog_lock); + spin_unlock(&ar->debug.fwlog_queue.lock); - slot->timestamp = cpu_to_le32(jiffies); - slot->length = cpu_to_le32(len); - memcpy(slot->payload, buf, len); + return; +} - slot_len = sizeof(*slot) + len; +static int ath6kl_fwlog_open(struct inode *inode, struct file *file) +{ + struct ath6kl *ar = inode->i_private; - if (slot_len < ATH6KL_FWLOG_SLOT_SIZE) - memset(slot->payload + len, 0, - ATH6KL_FWLOG_SLOT_SIZE - slot_len); + if (ar->debug.fwlog_open) + return -EBUSY; - ath6kl_debug_fwlog_add(ar, slot, ATH6KL_FWLOG_SLOT_SIZE); + ar->debug.fwlog_open = true; - spin_unlock_bh(&ar->debug.fwlog_lock); + file->private_data = inode->i_private; + return 0; } -static bool ath6kl_debug_fwlog_empty(struct ath6kl *ar) +static int ath6kl_fwlog_release(struct inode *inode, struct file *file) { - return CIRC_CNT(ar->debug.fwlog_buf.head, - ar->debug.fwlog_buf.tail, - ATH6KL_FWLOG_SLOT_SIZE) == 0; + struct ath6kl *ar = inode->i_private; + + ar->debug.fwlog_open = false; + + return 0; } static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct ath6kl *ar = file->private_data; - struct circ_buf *fwlog = &ar->debug.fwlog_buf; - size_t len = 0, buf_len = count; + struct sk_buff *skb; ssize_t ret_cnt; + size_t len = 0; char *buf; - int ccnt; - buf = vmalloc(buf_len); + buf = vmalloc(count); if (!buf) return -ENOMEM; /* read undelivered logs from firmware */ ath6kl_read_fwlogs(ar); - spin_lock_bh(&ar->debug.fwlog_lock); + spin_lock(&ar->debug.fwlog_queue.lock); - while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) { - ccnt = CIRC_CNT_TO_END(fwlog->head, fwlog->tail, - ATH6KL_FWLOG_SIZE); + while ((skb = __skb_dequeue(&ar->debug.fwlog_queue))) { + if (skb->len > count - len) { + /* not enough space, put skb back and leave */ + __skb_queue_head(&ar->debug.fwlog_queue, skb); + break; + } - if ((size_t) ccnt > buf_len - len) - ccnt = buf_len - len; - memcpy(buf + len, &fwlog->buf[fwlog->tail], ccnt); - len += ccnt; + memcpy(buf + len, skb->data, skb->len); + len += skb->len; - fwlog->tail = (fwlog->tail + ccnt) & - (ATH6KL_FWLOG_SIZE - 1); + kfree_skb(skb); } - spin_unlock_bh(&ar->debug.fwlog_lock); + spin_unlock(&ar->debug.fwlog_queue.lock); - if (WARN_ON(len > buf_len)) - len = buf_len; + /* FIXME: what to do if len == 0? */ ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); @@ -346,12 +424,89 @@ static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf, } static const struct file_operations fops_fwlog = { - .open = ath6kl_debugfs_open, + .open = ath6kl_fwlog_open, + .release = ath6kl_fwlog_release, .read = ath6kl_fwlog_read, .owner = THIS_MODULE, .llseek = default_llseek, }; +static ssize_t ath6kl_fwlog_block_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct sk_buff *skb; + ssize_t ret_cnt; + size_t len = 0, not_copied; + char *buf; + int ret; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + + spin_lock(&ar->debug.fwlog_queue.lock); + + if (skb_queue_len(&ar->debug.fwlog_queue) == 0) { + /* we must init under queue lock */ + init_completion(&ar->debug.fwlog_completion); + + spin_unlock(&ar->debug.fwlog_queue.lock); + + ret = wait_for_completion_interruptible( + &ar->debug.fwlog_completion); + if (ret == -ERESTARTSYS) { + vfree(buf); + return ret; + } + + spin_lock(&ar->debug.fwlog_queue.lock); + } + + while ((skb = __skb_dequeue(&ar->debug.fwlog_queue))) { + if (skb->len > count - len) { + /* not enough space, put skb back and leave */ + __skb_queue_head(&ar->debug.fwlog_queue, skb); + break; + } + + + memcpy(buf + len, skb->data, skb->len); + len += skb->len; + + kfree_skb(skb); + } + + spin_unlock(&ar->debug.fwlog_queue.lock); + + /* FIXME: what to do if len == 0? */ + + not_copied = copy_to_user(user_buf, buf, len); + if (not_copied != 0) { + ret_cnt = -EFAULT; + goto out; + } + + *ppos = *ppos + len; + + ret_cnt = len; + +out: + vfree(buf); + + return ret_cnt; +} + +static const struct file_operations fops_fwlog_block = { + .open = ath6kl_fwlog_open, + .release = ath6kl_fwlog_release, + .read = ath6kl_fwlog_block_read, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + static ssize_t ath6kl_fwlog_mask_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { @@ -385,7 +540,7 @@ static ssize_t ath6kl_fwlog_mask_write(struct file *file, } static const struct file_operations fops_fwlog_mask = { - .open = ath6kl_debugfs_open, + .open = simple_open, .read = ath6kl_fwlog_mask_read, .write = ath6kl_fwlog_mask_write, .owner = THIS_MODULE, @@ -519,6 +674,12 @@ static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, "Num disconnects", tgt_stats->cs_discon_cnt); len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", "Beacon avg rssi", tgt_stats->cs_ave_beacon_rssi); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt received", tgt_stats->arp_received); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt matched", tgt_stats->arp_matched); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt replied", tgt_stats->arp_replied); if (len > buf_len) len = buf_len; @@ -531,7 +692,7 @@ static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, static const struct file_operations fops_tgt_stats = { .read = read_file_tgt_stats, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -596,7 +757,7 @@ static ssize_t read_file_credit_dist_stats(struct file *file, static const struct file_operations fops_credit_dist_stats = { .read = read_file_credit_dist_stats, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -637,9 +798,13 @@ static ssize_t ath6kl_endpoint_stats_read(struct file *file, return -ENOMEM; #define EPSTAT(name) \ - len = print_endpoint_stat(target, buf, buf_len, len, \ - offsetof(struct htc_endpoint_stats, name), \ - #name) + do { \ + len = print_endpoint_stat(target, buf, buf_len, len, \ + offsetof(struct htc_endpoint_stats, \ + name), \ + #name); \ + } while (0) + EPSTAT(cred_low_indicate); EPSTAT(tx_issued); EPSTAT(tx_pkt_bundled); @@ -695,7 +860,7 @@ static ssize_t ath6kl_endpoint_stats_write(struct file *file, } static const struct file_operations fops_endpoint_stats = { - .open = ath6kl_debugfs_open, + .open = simple_open, .read = ath6kl_endpoint_stats_read, .write = ath6kl_endpoint_stats_write, .owner = THIS_MODULE, @@ -749,17 +914,9 @@ static ssize_t ath6kl_regread_write(struct file *file, size_t count, loff_t *ppos) { struct ath6kl *ar = file->private_data; - u8 buf[50]; - unsigned int len; unsigned long reg_addr; - len = min(count, sizeof(buf) - 1); - if (copy_from_user(buf, user_buf, len)) - return -EFAULT; - - buf[len] = '\0'; - - if (strict_strtoul(buf, 0, ®_addr)) + if (kstrtoul_from_user(user_buf, count, 0, ®_addr)) return -EINVAL; if ((reg_addr % 4) != 0) @@ -776,7 +933,7 @@ static ssize_t ath6kl_regread_write(struct file *file, static const struct file_operations fops_diag_reg_read = { .read = ath6kl_regread_read, .write = ath6kl_regread_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -873,15 +1030,8 @@ static ssize_t ath6kl_lrssi_roam_write(struct file *file, { struct ath6kl *ar = file->private_data; unsigned long lrssi_roam_threshold; - char buf[32]; - ssize_t len; - - len = min(count, sizeof(buf) - 1); - if (copy_from_user(buf, user_buf, len)) - return -EFAULT; - buf[len] = '\0'; - if (strict_strtoul(buf, 0, &lrssi_roam_threshold)) + if (kstrtoul_from_user(user_buf, count, 0, &lrssi_roam_threshold)) return -EINVAL; ar->lrssi_roam_threshold = lrssi_roam_threshold; @@ -907,7 +1057,7 @@ static ssize_t ath6kl_lrssi_roam_read(struct file *file, static const struct file_operations fops_lrssi_roam_threshold = { .read = ath6kl_lrssi_roam_read, .write = ath6kl_lrssi_roam_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -969,7 +1119,7 @@ static ssize_t ath6kl_regwrite_write(struct file *file, static const struct file_operations fops_diag_reg_write = { .read = ath6kl_regwrite_read, .write = ath6kl_regwrite_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1074,7 +1224,7 @@ static ssize_t ath6kl_roam_table_read(struct file *file, char __user *user_buf, static const struct file_operations fops_roam_table = { .read = ath6kl_roam_table_read, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1088,20 +1238,14 @@ static ssize_t ath6kl_force_roam_write(struct file *file, char buf[20]; size_t len; u8 bssid[ETH_ALEN]; - int i; - int addr[ETH_ALEN]; len = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, len)) return -EFAULT; buf[len] = '\0'; - if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", - &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) - != ETH_ALEN) + if (!mac_pton(buf, bssid)) return -EINVAL; - for (i = 0; i < ETH_ALEN; i++) - bssid[i] = addr[i]; ret = ath6kl_wmi_force_roam_cmd(ar->wmi, bssid); if (ret) @@ -1112,7 +1256,7 @@ static ssize_t ath6kl_force_roam_write(struct file *file, static const struct file_operations fops_force_roam = { .write = ath6kl_force_roam_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1152,7 +1296,7 @@ static ssize_t ath6kl_roam_mode_write(struct file *file, static const struct file_operations fops_roam_mode = { .write = ath6kl_roam_mode_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1194,7 +1338,7 @@ static ssize_t ath6kl_keepalive_write(struct file *file, } static const struct file_operations fops_keepalive = { - .open = ath6kl_debugfs_open, + .open = simple_open, .read = ath6kl_keepalive_read, .write = ath6kl_keepalive_write, .owner = THIS_MODULE, @@ -1239,7 +1383,7 @@ static ssize_t ath6kl_disconnect_timeout_write(struct file *file, } static const struct file_operations fops_disconnect_timeout = { - .open = ath6kl_debugfs_open, + .open = simple_open, .read = ath6kl_disconnect_timeout_read, .write = ath6kl_disconnect_timeout_write, .owner = THIS_MODULE, @@ -1250,7 +1394,6 @@ static ssize_t ath6kl_create_qos_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { - struct ath6kl *ar = file->private_data; struct ath6kl_vif *vif; char buf[200]; @@ -1411,6 +1554,8 @@ static ssize_t ath6kl_create_qos_write(struct file *file, return -EINVAL; pstream.medium_time = cpu_to_le32(val32); + pstream.nominal_phy = le32_to_cpu(pstream.min_phy_rate) / 1000000; + ath6kl_wmi_create_pstream_cmd(ar->wmi, vif->fw_vif_idx, &pstream); return count; @@ -1418,7 +1563,7 @@ static ssize_t ath6kl_create_qos_write(struct file *file, static const struct file_operations fops_create_qos = { .write = ath6kl_create_qos_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1427,7 +1572,6 @@ static ssize_t ath6kl_delete_qos_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { - struct ath6kl *ar = file->private_data; struct ath6kl_vif *vif; char buf[100]; @@ -1466,7 +1610,7 @@ static ssize_t ath6kl_delete_qos_write(struct file *file, static const struct file_operations fops_delete_qos = { .write = ath6kl_delete_qos_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1476,10 +1620,15 @@ static ssize_t ath6kl_bgscan_int_write(struct file *file, size_t count, loff_t *ppos) { struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; u16 bgscan_int; char buf[32]; ssize_t len; + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + len = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, len)) return -EFAULT; @@ -1491,6 +1640,8 @@ static ssize_t ath6kl_bgscan_int_write(struct file *file, if (bgscan_int == 0) bgscan_int = 0xffff; + vif->bg_scan_period = bgscan_int; + ath6kl_wmi_scanparams_cmd(ar->wmi, 0, 0, 0, bgscan_int, 0, 0, 0, 3, 0, 0, 0); @@ -1499,63 +1650,57 @@ static ssize_t ath6kl_bgscan_int_write(struct file *file, static const struct file_operations fops_bgscan_int = { .write = ath6kl_bgscan_int_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static ssize_t ath6kl_listen_int_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) + const char __user *user_buf, + size_t count, loff_t *ppos) { struct ath6kl *ar = file->private_data; - u16 listen_int_t, listen_int_b; + struct ath6kl_vif *vif; + u16 listen_interval; char buf[32]; - char *sptr, *token; ssize_t len; + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + len = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, len)) return -EFAULT; buf[len] = '\0'; - sptr = buf; - - token = strsep(&sptr, " "); - if (!token) + if (kstrtou16(buf, 0, &listen_interval)) return -EINVAL; - if (kstrtou16(token, 0, &listen_int_t)) + if ((listen_interval < 15) || (listen_interval > 3000)) return -EINVAL; - if (kstrtou16(sptr, 0, &listen_int_b)) - return -EINVAL; - - if ((listen_int_t < 15) || (listen_int_t > 5000)) - return -EINVAL; - - if ((listen_int_b < 1) || (listen_int_b > 50)) - return -EINVAL; - - ar->listen_intvl_t = listen_int_t; - ar->listen_intvl_b = listen_int_b; - - ath6kl_wmi_listeninterval_cmd(ar->wmi, 0, ar->listen_intvl_t, - ar->listen_intvl_b); + vif->listen_intvl_t = listen_interval; + ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + vif->listen_intvl_t, 0); return count; } static ssize_t ath6kl_listen_int_read(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) + char __user *user_buf, + size_t count, loff_t *ppos) { struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; char buf[32]; int len; - len = scnprintf(buf, sizeof(buf), "%u %u\n", ar->listen_intvl_t, - ar->listen_intvl_b); + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = scnprintf(buf, sizeof(buf), "%u\n", vif->listen_intvl_t); return simple_read_from_buffer(user_buf, count, ppos, buf, len); } @@ -1563,7 +1708,7 @@ static ssize_t ath6kl_listen_int_read(struct file *file, static const struct file_operations fops_listen_int = { .read = ath6kl_listen_int_read, .write = ath6kl_listen_int_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1623,44 +1768,42 @@ static ssize_t ath6kl_power_params_write(struct file *file, static const struct file_operations fops_power_params = { .write = ath6kl_power_params_write, - .open = ath6kl_debugfs_open, + .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; -int ath6kl_debug_init(struct ath6kl *ar) +void ath6kl_debug_init(struct ath6kl *ar) { - ar->debug.fwlog_buf.buf = vmalloc(ATH6KL_FWLOG_SIZE); - if (ar->debug.fwlog_buf.buf == NULL) - return -ENOMEM; - - ar->debug.fwlog_tmp = kmalloc(ATH6KL_FWLOG_SLOT_SIZE, GFP_KERNEL); - if (ar->debug.fwlog_tmp == NULL) { - vfree(ar->debug.fwlog_buf.buf); - return -ENOMEM; - } - - spin_lock_init(&ar->debug.fwlog_lock); + skb_queue_head_init(&ar->debug.fwlog_queue); + init_completion(&ar->debug.fwlog_completion); /* * Actually we are lying here but don't know how to read the mask * value from the firmware. */ ar->debug.fwlog_mask = 0; +} +/* + * Initialisation needs to happen in two stages as fwlog events can come + * before cfg80211 is initialised, and debugfs depends on cfg80211 + * initialisation. + */ +int ath6kl_debug_init_fs(struct ath6kl *ar) +{ ar->debugfs_phy = debugfs_create_dir("ath6kl", ar->wiphy->debugfsdir); - if (!ar->debugfs_phy) { - vfree(ar->debug.fwlog_buf.buf); - kfree(ar->debug.fwlog_tmp); + if (!ar->debugfs_phy) return -ENOMEM; - } debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar, &fops_tgt_stats); - debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar, - &fops_credit_dist_stats); + if (ar->hif_type == ATH6KL_HIF_TYPE_SDIO) + debugfs_create_file("credit_dist_stats", S_IRUSR, + ar->debugfs_phy, ar, + &fops_credit_dist_stats); debugfs_create_file("endpoint_stats", S_IRUSR | S_IWUSR, ar->debugfs_phy, ar, &fops_endpoint_stats); @@ -1668,6 +1811,9 @@ int ath6kl_debug_init(struct ath6kl *ar) debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar, &fops_fwlog); + debugfs_create_file("fwlog_block", S_IRUSR, ar->debugfs_phy, ar, + &fops_fwlog_block); + debugfs_create_file("fwlog_mask", S_IRUSR | S_IWUSR, ar->debugfs_phy, ar, &fops_fwlog_mask); @@ -1702,24 +1848,27 @@ int ath6kl_debug_init(struct ath6kl *ar) ar->debugfs_phy, ar, &fops_disconnect_timeout); debugfs_create_file("create_qos", S_IWUSR, ar->debugfs_phy, ar, - &fops_create_qos); + &fops_create_qos); debugfs_create_file("delete_qos", S_IWUSR, ar->debugfs_phy, ar, - &fops_delete_qos); + &fops_delete_qos); debugfs_create_file("bgscan_interval", S_IWUSR, - ar->debugfs_phy, ar, &fops_bgscan_int); + ar->debugfs_phy, ar, &fops_bgscan_int); + + debugfs_create_file("listen_interval", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_listen_int); debugfs_create_file("power_params", S_IWUSR, ar->debugfs_phy, ar, - &fops_power_params); + &fops_power_params); return 0; } void ath6kl_debug_cleanup(struct ath6kl *ar) { - vfree(ar->debug.fwlog_buf.buf); - kfree(ar->debug.fwlog_tmp); + skb_queue_purge(&ar->debug.fwlog_queue); + complete(&ar->debug.fwlog_completion); kfree(ar->debug.roam_tbl); } diff --git a/drivers/net/wireless/ath/ath6kl/debug.h b/drivers/net/wireless/ath/ath6kl/debug.h index 9853c9c125c..e194c10d9f0 100644 --- a/drivers/net/wireless/ath/ath6kl/debug.h +++ b/drivers/net/wireless/ath/ath6kl/debug.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,6 +19,7 @@ #define DEBUG_H #include "hif.h" +#include "trace.h" enum ATH6K_DEBUG_MASK { ATH6KL_DBG_CREDIT = BIT(0), @@ -41,49 +43,28 @@ enum ATH6K_DEBUG_MASK { ATH6KL_DBG_BOOT = BIT(18), /* driver init and fw boot */ ATH6KL_DBG_WMI_DUMP = BIT(19), ATH6KL_DBG_SUSPEND = BIT(20), + ATH6KL_DBG_USB = BIT(21), + ATH6KL_DBG_USB_BULK = BIT(22), + ATH6KL_DBG_RECOVERY = BIT(23), ATH6KL_DBG_ANY = 0xffffffff /* enable all logs */ }; extern unsigned int debug_mask; -extern __printf(2, 3) -int ath6kl_printk(const char *level, const char *fmt, ...); - -#define ath6kl_info(fmt, ...) \ - ath6kl_printk(KERN_INFO, fmt, ##__VA_ARGS__) -#define ath6kl_err(fmt, ...) \ - ath6kl_printk(KERN_ERR, fmt, ##__VA_ARGS__) -#define ath6kl_warn(fmt, ...) \ - ath6kl_printk(KERN_WARNING, fmt, ##__VA_ARGS__) - -#define AR_DBG_LVL_CHECK(mask) (debug_mask & mask) +__printf(2, 3) int ath6kl_printk(const char *level, const char *fmt, ...); +__printf(1, 2) int ath6kl_info(const char *fmt, ...); +__printf(1, 2) int ath6kl_err(const char *fmt, ...); +__printf(1, 2) int ath6kl_warn(const char *fmt, ...); enum ath6kl_war { ATH6KL_WAR_INVALID_RATE, }; #ifdef CONFIG_ATH6KL_DEBUG -#define ath6kl_dbg(mask, fmt, ...) \ - ({ \ - int rtn; \ - if (debug_mask & mask) \ - rtn = ath6kl_printk(KERN_DEBUG, fmt, ##__VA_ARGS__); \ - else \ - rtn = 0; \ - \ - rtn; \ - }) -static inline void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, - const char *msg, const char *prefix, - const void *buf, size_t len) -{ - if (debug_mask & mask) { - if (msg) - ath6kl_dbg(mask, "%s\n", msg); - - print_hex_dump_bytes(prefix, DUMP_PREFIX_OFFSET, buf, len); - } -} +void ath6kl_dbg(enum ATH6K_DEBUG_MASK mask, const char *fmt, ...); +void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, + const char *msg, const char *prefix, + const void *buf, size_t len); void ath6kl_dump_registers(struct ath6kl_device *dev, struct ath6kl_irq_proc_registers *irq_proc_reg, @@ -95,7 +76,8 @@ int ath6kl_debug_roam_tbl_event(struct ath6kl *ar, const void *buf, size_t len); void ath6kl_debug_set_keepalive(struct ath6kl *ar, u8 keepalive); void ath6kl_debug_set_disconnect_timeout(struct ath6kl *ar, u8 timeout); -int ath6kl_debug_init(struct ath6kl *ar); +void ath6kl_debug_init(struct ath6kl *ar); +int ath6kl_debug_init_fs(struct ath6kl *ar); void ath6kl_debug_cleanup(struct ath6kl *ar); #else @@ -115,8 +97,8 @@ static inline void ath6kl_dump_registers(struct ath6kl_device *dev, struct ath6kl_irq_proc_registers *irq_proc_reg, struct ath6kl_irq_enable_reg *irq_en_reg) { - } + static inline void dump_cred_dist_stats(struct htc_target *target) { } @@ -145,7 +127,11 @@ static inline void ath6kl_debug_set_disconnect_timeout(struct ath6kl *ar, { } -static inline int ath6kl_debug_init(struct ath6kl *ar) +static inline void ath6kl_debug_init(struct ath6kl *ar) +{ +} + +static inline int ath6kl_debug_init_fs(struct ath6kl *ar) { return 0; } diff --git a/drivers/net/wireless/ath/ath6kl/hif-ops.h b/drivers/net/wireless/ath/ath6kl/hif-ops.h index 2fe1dadfc77..8c9e72d5250 100644 --- a/drivers/net/wireless/ath/ath6kl/hif-ops.h +++ b/drivers/net/wireless/ath/ath6kl/hif-ops.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -149,4 +150,38 @@ static inline void ath6kl_hif_stop(struct ath6kl *ar) ar->hif_ops->stop(ar); } +static inline int ath6kl_hif_pipe_send(struct ath6kl *ar, + u8 pipe, struct sk_buff *hdr_buf, + struct sk_buff *buf) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe send\n"); + + return ar->hif_ops->pipe_send(ar, pipe, hdr_buf, buf); +} + +static inline void ath6kl_hif_pipe_get_default(struct ath6kl *ar, + u8 *ul_pipe, u8 *dl_pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); + + ar->hif_ops->pipe_get_default(ar, ul_pipe, dl_pipe); +} + +static inline int ath6kl_hif_pipe_map_service(struct ath6kl *ar, + u16 service_id, u8 *ul_pipe, + u8 *dl_pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); + + return ar->hif_ops->pipe_map_service(ar, service_id, ul_pipe, dl_pipe); +} + +static inline u16 ath6kl_hif_pipe_get_free_queue_number(struct ath6kl *ar, + u8 pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get free queue number\n"); + + return ar->hif_ops->pipe_get_free_queue_number(ar, pipe); +} + #endif diff --git a/drivers/net/wireless/ath/ath6kl/hif.c b/drivers/net/wireless/ath/ath6kl/hif.c index e57da35e59f..18c070850a0 100644 --- a/drivers/net/wireless/ath/ath6kl/hif.c +++ b/drivers/net/wireless/ath/ath6kl/hif.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,10 +16,13 @@ */ #include "hif.h" +#include <linux/export.h> + #include "core.h" #include "target.h" #include "hif-ops.h" #include "debug.h" +#include "trace.h" #define MAILBOX_FOR_BLOCK_SIZE 1 @@ -33,7 +37,6 @@ static int ath6kl_hif_cp_scat_dma_buf(struct hif_scatter_req *req, buf = req->virt_dma_buf; for (i = 0; i < req->scat_entries; i++) { - if (from_dma) memcpy(req->scat_list[i].buf, buf, req->scat_list[i].len); @@ -59,6 +62,8 @@ int ath6kl_hif_rw_comp_handler(void *context, int status) return 0; } +EXPORT_SYMBOL(ath6kl_hif_rw_comp_handler); + #define REG_DUMP_COUNT_AR6003 60 #define REGISTER_DUMP_LEN_MAX 60 @@ -85,7 +90,7 @@ static void ath6kl_hif_dump_fw_crash(struct ath6kl *ar) } ath6kl_dbg(ATH6KL_DBG_IRQ, "register dump data address 0x%x\n", - regdump_addr); + regdump_addr); regdump_addr = TARG_VTOP(ar->target_type, regdump_addr); /* fetch register dump data */ @@ -102,15 +107,14 @@ static void ath6kl_hif_dump_fw_crash(struct ath6kl *ar) BUILD_BUG_ON(REG_DUMP_COUNT_AR6003 % 4); - for (i = 0; i < REG_DUMP_COUNT_AR6003 / 4; i++) { + for (i = 0; i < REG_DUMP_COUNT_AR6003; i += 4) { ath6kl_info("%d: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n", - 4 * i, + i, le32_to_cpu(regdump_val[i]), le32_to_cpu(regdump_val[i + 1]), le32_to_cpu(regdump_val[i + 2]), le32_to_cpu(regdump_val[i + 3])); } - } static int ath6kl_hif_proc_dbg_intr(struct ath6kl_device *dev) @@ -130,6 +134,8 @@ static int ath6kl_hif_proc_dbg_intr(struct ath6kl_device *dev) ath6kl_warn("Failed to clear debug interrupt: %d\n", ret); ath6kl_hif_dump_fw_crash(dev->ar); + ath6kl_read_fwlogs(dev->ar); + ath6kl_recovery_err_notify(dev->ar, ATH6KL_FW_ASSERT); return ret; } @@ -279,7 +285,7 @@ static int ath6kl_hif_proc_counter_intr(struct ath6kl_device *dev) dev->irq_en_reg.cntr_int_status_en; ath6kl_dbg(ATH6KL_DBG_IRQ, - "valid interrupt source(s) in COUNTER_INT_STATUS: 0x%x\n", + "valid interrupt source(s) in COUNTER_INT_STATUS: 0x%x\n", counter_int_status); /* @@ -332,8 +338,7 @@ static int ath6kl_hif_proc_err_intr(struct ath6kl_device *dev) status = hif_read_write_sync(dev->ar, ERROR_INT_STATUS_ADDRESS, reg_buf, 4, HIF_WR_SYNC_BYTE_FIX); - if (status) - WARN_ON(1); + WARN_ON(status); return status; } @@ -354,7 +359,7 @@ static int ath6kl_hif_proc_cpu_intr(struct ath6kl_device *dev) } ath6kl_dbg(ATH6KL_DBG_IRQ, - "valid interrupt source(s) in CPU_INT_STATUS: 0x%x\n", + "valid interrupt source(s) in CPU_INT_STATUS: 0x%x\n", cpu_int_status); /* Clear the interrupt */ @@ -377,8 +382,7 @@ static int ath6kl_hif_proc_cpu_intr(struct ath6kl_device *dev) status = hif_read_write_sync(dev->ar, CPU_INT_STATUS_ADDRESS, reg_buf, 4, HIF_WR_SYNC_BYTE_FIX); - if (status) - WARN_ON(1); + WARN_ON(status); return status; } @@ -429,9 +433,10 @@ static int proc_pending_irqs(struct ath6kl_device *dev, bool *done) if (status) goto out; - if (AR_DBG_LVL_CHECK(ATH6KL_DBG_IRQ)) - ath6kl_dump_registers(dev, &dev->irq_proc_reg, - &dev->irq_en_reg); + ath6kl_dump_registers(dev, &dev->irq_proc_reg, + &dev->irq_en_reg); + trace_ath6kl_sdio_irq(&dev->irq_en_reg, + sizeof(dev->irq_en_reg)); /* Update only those registers that are enabled */ host_int_status = dev->irq_proc_reg.host_int_status & @@ -561,6 +566,7 @@ int ath6kl_hif_intr_bh_handler(struct ath6kl *ar) return status; } +EXPORT_SYMBOL(ath6kl_hif_intr_bh_handler); static int ath6kl_hif_enable_intrs(struct ath6kl_device *dev) { @@ -693,5 +699,4 @@ int ath6kl_hif_setup(struct ath6kl_device *dev) fail_setup: return status; - } diff --git a/drivers/net/wireless/ath/ath6kl/hif.h b/drivers/net/wireless/ath/ath6kl/hif.h index 699a036f3a4..dc6bd8cd9b8 100644 --- a/drivers/net/wireless/ath/ath6kl/hif.h +++ b/drivers/net/wireless/ath/ath6kl/hif.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -196,7 +197,9 @@ struct hif_scatter_req { /* bounce buffer for upper layers to copy to/from */ u8 *virt_dma_buf; - struct hif_scatter_item scat_list[1]; + u32 scat_q_depth; + + struct hif_scatter_item scat_list[0]; }; struct ath6kl_irq_proc_registers { @@ -220,6 +223,7 @@ struct ath6kl_irq_enable_reg { } __packed; struct ath6kl_device { + /* protects irq_proc_reg and irq_en_reg below */ spinlock_t lock; struct ath6kl_irq_proc_registers irq_proc_reg; struct ath6kl_irq_enable_reg irq_en_reg; @@ -252,6 +256,12 @@ struct ath6kl_hif_ops { int (*power_on)(struct ath6kl *ar); int (*power_off)(struct ath6kl *ar); void (*stop)(struct ath6kl *ar); + int (*pipe_send)(struct ath6kl *ar, u8 pipe, struct sk_buff *hdr_buf, + struct sk_buff *buf); + void (*pipe_get_default)(struct ath6kl *ar, u8 *pipe_ul, u8 *pipe_dl); + int (*pipe_map_service)(struct ath6kl *ar, u16 service_id, u8 *pipe_ul, + u8 *pipe_dl); + u16 (*pipe_get_free_queue_number)(struct ath6kl *ar, u8 pipe); }; int ath6kl_hif_setup(struct ath6kl_device *dev); diff --git a/drivers/net/wireless/ath/ath6kl/htc-ops.h b/drivers/net/wireless/ath/ath6kl/htc-ops.h new file mode 100644 index 00000000000..2d4eed55cfd --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc-ops.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_OPS_H +#define HTC_OPS_H + +#include "htc.h" +#include "debug.h" + +static inline void *ath6kl_htc_create(struct ath6kl *ar) +{ + return ar->htc_ops->create(ar); +} + +static inline int ath6kl_htc_wait_target(struct htc_target *target) +{ + return target->dev->ar->htc_ops->wait_target(target); +} + +static inline int ath6kl_htc_start(struct htc_target *target) +{ + return target->dev->ar->htc_ops->start(target); +} + +static inline int ath6kl_htc_conn_service(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp) +{ + return target->dev->ar->htc_ops->conn_service(target, req, resp); +} + +static inline int ath6kl_htc_tx(struct htc_target *target, + struct htc_packet *packet) +{ + return target->dev->ar->htc_ops->tx(target, packet); +} + +static inline void ath6kl_htc_stop(struct htc_target *target) +{ + return target->dev->ar->htc_ops->stop(target); +} + +static inline void ath6kl_htc_cleanup(struct htc_target *target) +{ + return target->dev->ar->htc_ops->cleanup(target); +} + +static inline void ath6kl_htc_flush_txep(struct htc_target *target, + enum htc_endpoint_id endpoint, + u16 tag) +{ + return target->dev->ar->htc_ops->flush_txep(target, endpoint, tag); +} + +static inline void ath6kl_htc_flush_rx_buf(struct htc_target *target) +{ + return target->dev->ar->htc_ops->flush_rx_buf(target); +} + +static inline void ath6kl_htc_activity_changed(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active) +{ + return target->dev->ar->htc_ops->activity_changed(target, endpoint, + active); +} + +static inline int ath6kl_htc_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + return target->dev->ar->htc_ops->get_rxbuf_num(target, endpoint); +} + +static inline int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pktq) +{ + return target->dev->ar->htc_ops->add_rxbuf_multiple(target, pktq); +} + +static inline int ath6kl_htc_credit_setup(struct htc_target *target, + struct ath6kl_htc_credit_info *info) +{ + return target->dev->ar->htc_ops->credit_setup(target, info); +} + +static inline void ath6kl_htc_tx_complete(struct ath6kl *ar, + struct sk_buff *skb) +{ + ar->htc_ops->tx_complete(ar, skb); +} + + +static inline void ath6kl_htc_rx_complete(struct ath6kl *ar, + struct sk_buff *skb, u8 pipe) +{ + ar->htc_ops->rx_complete(ar, skb, pipe); +} + + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/htc.h b/drivers/net/wireless/ath/ath6kl/htc.h index 57672e1ed1a..14cab1403dd 100644 --- a/drivers/net/wireless/ath/ath6kl/htc.h +++ b/drivers/net/wireless/ath/ath6kl/htc.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,6 +25,7 @@ /* send direction */ #define HTC_FLAGS_NEED_CREDIT_UPDATE (1 << 0) #define HTC_FLAGS_SEND_BUNDLE (1 << 1) +#define HTC_FLAGS_TX_FIXUP_NETBUF (1 << 2) /* receive direction */ #define HTC_FLG_RX_UNUSED (1 << 0) @@ -55,6 +57,10 @@ #define HTC_CONN_FLGS_THRESH_LVL_THREE_QUAT 0x2 #define HTC_CONN_FLGS_REDUCE_CRED_DRIB 0x4 #define HTC_CONN_FLGS_THRESH_MASK 0x3 +/* disable credit flow control on a specific service */ +#define HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL (1 << 3) +#define HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT 8 +#define HTC_CONN_FLGS_SET_RECV_ALLOC_MASK 0xFF00U /* connect response status codes */ #define HTC_SERVICE_SUCCESS 0 @@ -74,6 +80,7 @@ #define HTC_RECORD_LOOKAHEAD_BUNDLE 3 #define HTC_SETUP_COMP_FLG_RX_BNDL_EN (1 << 0) +#define HTC_SETUP_COMP_FLG_DISABLE_TX_CREDIT_FLOW (1 << 1) #define MAKE_SERVICE_ID(group, index) \ (int)(((int)group << 8) | (int)(index)) @@ -87,6 +94,8 @@ #define WMI_DATA_VO_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 4) #define WMI_MAX_SERVICES 5 +#define WMM_NUM_AC 4 + /* reserved and used to flush ALL packets */ #define HTC_TX_PACKET_TAG_ALL 0 #define HTC_SERVICE_TX_PACKET_TAG 1 @@ -106,6 +115,8 @@ /* HTC operational parameters */ #define HTC_TARGET_RESPONSE_TIMEOUT 2000 /* in ms */ +#define HTC_TARGET_RESPONSE_POLL_WAIT 10 +#define HTC_TARGET_RESPONSE_POLL_COUNT 200 #define HTC_TARGET_DEBUG_INTR_MASK 0x01 #define HTC_TARGET_CREDIT_INTR_MASK 0xF0 @@ -125,6 +136,7 @@ #define HTC_RECV_WAIT_BUFFERS (1 << 0) #define HTC_OP_STATE_STOPPING (1 << 0) +#define HTC_OP_STATE_SETUP_COMPLETE (1 << 1) /* * The frame header length and message formats defined herein were selected @@ -308,6 +320,14 @@ struct htc_packet { void (*completion) (struct htc_target *, struct htc_packet *); struct htc_target *context; + + /* + * optimization for network-oriented data, the HTC packet + * can pass the network buffer corresponding to the HTC packet + * lower layers may optimized the transfer knowing this is + * a network buffer + */ + struct sk_buff *skb; }; enum htc_send_full_action { @@ -316,12 +336,14 @@ enum htc_send_full_action { }; struct htc_ep_callbacks { + void (*tx_complete) (struct htc_target *, struct htc_packet *); void (*rx) (struct htc_target *, struct htc_packet *); void (*rx_refill) (struct htc_target *, enum htc_endpoint_id endpoint); enum htc_send_full_action (*tx_full) (struct htc_target *, struct htc_packet *); struct htc_packet *(*rx_allocthresh) (struct htc_target *, enum htc_endpoint_id, int); + void (*tx_comp_multi) (struct htc_target *, struct list_head *); int rx_alloc_thresh; int rx_refill_thresh; }; @@ -498,6 +520,14 @@ struct htc_endpoint { u8 seqno; u32 conn_flags; struct htc_endpoint_stats ep_st; + u16 tx_drop_packet_threshold; + + struct { + u8 pipeid_ul; + u8 pipeid_dl; + struct list_head tx_lookup_queue; + bool tx_credit_flow_enabled; + } pipe; }; struct htc_control_buffer { @@ -505,6 +535,42 @@ struct htc_control_buffer { u8 *buf; }; +struct htc_pipe_txcredit_alloc { + u16 service_id; + u8 credit_alloc; +}; + +enum htc_send_queue_result { + HTC_SEND_QUEUE_OK = 0, /* packet was queued */ + HTC_SEND_QUEUE_DROP = 1, /* this packet should be dropped */ +}; + +struct ath6kl_htc_ops { + void* (*create)(struct ath6kl *ar); + int (*wait_target)(struct htc_target *target); + int (*start)(struct htc_target *target); + int (*conn_service)(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp); + int (*tx)(struct htc_target *target, struct htc_packet *packet); + void (*stop)(struct htc_target *target); + void (*cleanup)(struct htc_target *target); + void (*flush_txep)(struct htc_target *target, + enum htc_endpoint_id endpoint, u16 tag); + void (*flush_rx_buf)(struct htc_target *target); + void (*activity_changed)(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active); + int (*get_rxbuf_num)(struct htc_target *target, + enum htc_endpoint_id endpoint); + int (*add_rxbuf_multiple)(struct htc_target *target, + struct list_head *pktq); + int (*credit_setup)(struct htc_target *target, + struct ath6kl_htc_credit_info *cred_info); + int (*tx_complete)(struct ath6kl *ar, struct sk_buff *skb); + int (*rx_complete)(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); +}; + struct ath6kl_device; /* our HTC target state */ @@ -519,9 +585,16 @@ struct htc_target { struct ath6kl_htc_credit_info *credit_info; int tgt_creds; unsigned int tgt_cred_sz; + + /* protects free_ctrl_txbuf and free_ctrl_rxbuf */ spinlock_t htc_lock; + + /* FIXME: does this protext rx_bufq and endpoint structures or what? */ spinlock_t rx_lock; + + /* protects endpoint->txq */ spinlock_t tx_lock; + struct ath6kl_device *dev; u32 htc_flags; u32 rx_st_flags; @@ -531,7 +604,7 @@ struct htc_target { /* max messages per bundle for HTC */ int msg_per_bndl_max; - bool tx_bndl_enable; + u32 tx_bndl_mask; int rx_bndl_enable; int max_rx_bndl_sz; int max_tx_bndl_sz; @@ -543,36 +616,22 @@ struct htc_target { int max_xfer_szper_scatreq; int chk_irq_status_cnt; + + /* counts the number of Tx without bundling continously per AC */ + u32 ac_tx_count[WMM_NUM_AC]; + + struct { + struct htc_packet *htc_packet_pool; + u8 ctrl_response_buf[HTC_MAX_CTRL_MSG_LEN]; + int ctrl_response_len; + bool ctrl_response_valid; + struct htc_pipe_txcredit_alloc txcredit_alloc[ENDPOINT_MAX]; + } pipe; }; -void *ath6kl_htc_create(struct ath6kl *ar); -void ath6kl_htc_set_credit_dist(struct htc_target *target, - struct ath6kl_htc_credit_info *cred_info, - u16 svc_pri_order[], int len); -int ath6kl_htc_wait_target(struct htc_target *target); -int ath6kl_htc_start(struct htc_target *target); -int ath6kl_htc_conn_service(struct htc_target *target, - struct htc_service_connect_req *req, - struct htc_service_connect_resp *resp); -int ath6kl_htc_tx(struct htc_target *target, struct htc_packet *packet); -void ath6kl_htc_stop(struct htc_target *target); -void ath6kl_htc_cleanup(struct htc_target *target); -void ath6kl_htc_flush_txep(struct htc_target *target, - enum htc_endpoint_id endpoint, u16 tag); -void ath6kl_htc_flush_rx_buf(struct htc_target *target); -void ath6kl_htc_indicate_activity_change(struct htc_target *target, - enum htc_endpoint_id endpoint, - bool active); -int ath6kl_htc_get_rxbuf_num(struct htc_target *target, - enum htc_endpoint_id endpoint); -int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, - struct list_head *pktq); int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, u32 msg_look_ahead, int *n_pkts); -int ath6kl_credit_setup(void *htc_handle, - struct ath6kl_htc_credit_info *cred_info); - static inline void set_htc_pkt_info(struct htc_packet *packet, void *context, u8 *buf, unsigned int len, enum htc_endpoint_id eid, u16 tag) @@ -612,4 +671,7 @@ static inline int get_queue_depth(struct list_head *queue) return depth; } +void ath6kl_htc_pipe_attach(struct ath6kl *ar); +void ath6kl_htc_mbox_attach(struct ath6kl *ar); + #endif diff --git a/drivers/net/wireless/ath/ath6kl/htc.c b/drivers/net/wireless/ath/ath6kl/htc_mbox.c index f3b63ca25c7..e481f14b987 100644 --- a/drivers/net/wireless/ath/ath6kl/htc.c +++ b/drivers/net/wireless/ath/ath6kl/htc_mbox.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,10 +19,23 @@ #include "hif.h" #include "debug.h" #include "hif-ops.h" +#include "trace.h" + #include <asm/unaligned.h> #define CALC_TXRX_PADDED_LEN(dev, len) (__ALIGN_MASK((len), (dev)->block_mask)) +static void ath6kl_htc_mbox_cleanup(struct htc_target *target); +static void ath6kl_htc_mbox_stop(struct htc_target *target); +static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pkt_queue); +static void ath6kl_htc_set_credit_dist(struct htc_target *target, + struct ath6kl_htc_credit_info *cred_info, + u16 svc_pri_order[], int len); + +/* threshold to re-enable Tx bundling for an AC*/ +#define TX_RESUME_BUNDLE_THRESHOLD 1500 + /* Functions for Tx credit handling */ static void ath6kl_credit_deposit(struct ath6kl_htc_credit_info *cred_info, struct htc_endpoint_credit_dist *ep_dist, @@ -71,10 +85,7 @@ static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info, * never goes inactive EVER. */ cur_ep_dist->dist_flags |= HTC_EP_ACTIVE; - } else if (cur_ep_dist->svc_id == WMI_DATA_BK_SVC) - /* this is the lowest priority data endpoint */ - /* FIXME: this looks fishy, check */ - cred_info->lowestpri_ep_dist = cur_ep_dist->list; + } /* * Streams have to be created (explicit | implicit) for all @@ -88,15 +99,22 @@ static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info, */ } + /* + * For ath6kl_credit_seek function, + * it use list_for_each_entry_reverse to walk around the whole ep list. + * Therefore assign this lowestpri_ep_dist after walk around the ep_list + */ + cred_info->lowestpri_ep_dist = cur_ep_dist->list; + WARN_ON(cred_info->cur_free_credits <= 0); list_for_each_entry(cur_ep_dist, ep_list, list) { if (cur_ep_dist->endpoint == ENDPOINT_0) continue; - if (cur_ep_dist->svc_id == WMI_CONTROL_SVC) + if (cur_ep_dist->svc_id == WMI_CONTROL_SVC) { cur_ep_dist->cred_norm = cur_ep_dist->cred_per_msg; - else { + } else { /* * For the remaining data endpoints, we assume that * each cred_per_msg are the same. We use a simple @@ -111,7 +129,6 @@ static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info, count = (count * 3) >> 2; count = max(count, cur_ep_dist->cred_per_msg); cur_ep_dist->cred_norm = count; - } ath6kl_dbg(ATH6KL_DBG_CREDIT, @@ -126,8 +143,8 @@ static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info, } /* initialize and setup credit distribution */ -int ath6kl_credit_setup(void *htc_handle, - struct ath6kl_htc_credit_info *cred_info) +static int ath6kl_htc_mbox_credit_setup(struct htc_target *htc_target, + struct ath6kl_htc_credit_info *cred_info) { u16 servicepriority[5]; @@ -140,7 +157,7 @@ int ath6kl_credit_setup(void *htc_handle, servicepriority[4] = WMI_DATA_BK_SVC; /* lowest */ /* set priority list */ - ath6kl_htc_set_credit_dist(htc_handle, cred_info, servicepriority, 5); + ath6kl_htc_set_credit_dist(htc_target, cred_info, servicepriority, 5); return 0; } @@ -168,31 +185,29 @@ static void ath6kl_credit_reduce(struct ath6kl_htc_credit_info *cred_info, static void ath6kl_credit_update(struct ath6kl_htc_credit_info *cred_info, struct list_head *epdist_list) { - struct htc_endpoint_credit_dist *cur_dist_list; + struct htc_endpoint_credit_dist *cur_list; - list_for_each_entry(cur_dist_list, epdist_list, list) { - if (cur_dist_list->endpoint == ENDPOINT_0) + list_for_each_entry(cur_list, epdist_list, list) { + if (cur_list->endpoint == ENDPOINT_0) continue; - if (cur_dist_list->cred_to_dist > 0) { - cur_dist_list->credits += - cur_dist_list->cred_to_dist; - cur_dist_list->cred_to_dist = 0; - if (cur_dist_list->credits > - cur_dist_list->cred_assngd) + if (cur_list->cred_to_dist > 0) { + cur_list->credits += cur_list->cred_to_dist; + cur_list->cred_to_dist = 0; + + if (cur_list->credits > cur_list->cred_assngd) ath6kl_credit_reduce(cred_info, - cur_dist_list, - cur_dist_list->cred_assngd); + cur_list, + cur_list->cred_assngd); - if (cur_dist_list->credits > - cur_dist_list->cred_norm) - ath6kl_credit_reduce(cred_info, cur_dist_list, - cur_dist_list->cred_norm); + if (cur_list->credits > cur_list->cred_norm) + ath6kl_credit_reduce(cred_info, cur_list, + cur_list->cred_norm); - if (!(cur_dist_list->dist_flags & HTC_EP_ACTIVE)) { - if (cur_dist_list->txq_depth == 0) + if (!(cur_list->dist_flags & HTC_EP_ACTIVE)) { + if (cur_list->txq_depth == 0) ath6kl_credit_reduce(cred_info, - cur_dist_list, 0); + cur_list, 0); } } } @@ -430,7 +445,7 @@ static void htc_tx_complete(struct htc_endpoint *endpoint, "htc tx complete ep %d pkts %d\n", endpoint->eid, get_queue_depth(txq)); - ath6kl_tx_complete(endpoint->target->dev->ar, txq); + ath6kl_tx_complete(endpoint->target, txq); } static void htc_tx_comp_handler(struct htc_target *target, @@ -460,8 +475,8 @@ static void htc_async_tx_scat_complete(struct htc_target *target, INIT_LIST_HEAD(&tx_compq); ath6kl_dbg(ATH6KL_DBG_HTC, - "htc tx scat complete len %d entries %d\n", - scat_req->len, scat_req->scat_entries); + "htc tx scat complete len %d entries %d\n", + scat_req->len, scat_req->scat_entries); if (scat_req->status) ath6kl_err("send scatter req failed: %d\n", scat_req->status); @@ -523,6 +538,8 @@ static int ath6kl_htc_tx_issue(struct htc_target *target, packet->buf, padded_len, HIF_WR_ASYNC_BLOCK_INC, packet); + trace_ath6kl_htc_tx(status, packet->endpoint, packet->buf, send_len); + return status; } @@ -531,7 +548,6 @@ static int htc_check_credits(struct htc_target *target, enum htc_endpoint_id eid, unsigned int len, int *req_cred) { - *req_cred = (len > target->tgt_cred_sz) ? DIV_ROUND_UP(len, target->tgt_cred_sz) : 1; @@ -590,7 +606,6 @@ static void ath6kl_htc_tx_pkts_get(struct htc_target *target, unsigned int len; while (true) { - flags = 0; if (list_empty(&endpoint->txq)) @@ -599,8 +614,8 @@ static void ath6kl_htc_tx_pkts_get(struct htc_target *target, list); ath6kl_dbg(ATH6KL_DBG_HTC, - "htc tx got packet 0x%p queue depth %d\n", - packet, get_queue_depth(&endpoint->txq)); + "htc tx got packet 0x%p queue depth %d\n", + packet, get_queue_depth(&endpoint->txq)); len = CALC_TXRX_PADDED_LEN(target, packet->act_len + HTC_HDR_LENGTH); @@ -670,6 +685,7 @@ static int ath6kl_htc_tx_setup_scat_list(struct htc_target *target, struct htc_packet *packet; int i, len, rem_scat, cred_pad; int status = 0; + u8 flags; rem_scat = target->max_tx_bndl_sz; @@ -696,9 +712,9 @@ static int ath6kl_htc_tx_setup_scat_list(struct htc_target *target, scat_req->scat_list[i].packet = packet; /* prepare packet and flag message as part of a send bundle */ - ath6kl_htc_tx_prep_pkt(packet, - packet->info.tx.flags | HTC_FLAGS_SEND_BUNDLE, - cred_pad, packet->info.tx.seqno); + flags = packet->info.tx.flags | HTC_FLAGS_SEND_BUNDLE; + ath6kl_htc_tx_prep_pkt(packet, flags, + cred_pad, packet->info.tx.seqno); /* Make sure the buffer is 4-byte aligned */ ath6kl_htc_tx_buf_align(&packet->buf, packet->act_len + HTC_HDR_LENGTH); @@ -742,8 +758,15 @@ static void ath6kl_htc_tx_bundle(struct htc_endpoint *endpoint, { struct htc_target *target = endpoint->target; struct hif_scatter_req *scat_req = NULL; - int n_scat, n_sent_bundle = 0, tot_pkts_bundle = 0; + int n_scat, n_sent_bundle = 0, tot_pkts_bundle = 0, i; + struct htc_packet *packet; int status; + u32 txb_mask; + u8 ac = WMM_NUM_AC; + + if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) && + (WMI_CONTROL_SVC != endpoint->svc_id)) + ac = target->dev->ar->ep2ac_map[endpoint->eid]; while (true) { status = 0; @@ -759,10 +782,36 @@ static void ath6kl_htc_tx_bundle(struct htc_endpoint *endpoint, if (!scat_req) { /* no scatter resources */ ath6kl_dbg(ATH6KL_DBG_HTC, - "htc tx no more scatter resources\n"); + "htc tx no more scatter resources\n"); break; } + if ((ac < WMM_NUM_AC) && (ac != WMM_AC_BK)) { + if (WMM_AC_BE == ac) + /* + * BE, BK have priorities and bit + * positions reversed + */ + txb_mask = (1 << WMM_AC_BK); + else + /* + * any AC with priority lower than + * itself + */ + txb_mask = ((1 << ac) - 1); + + /* + * when the scatter request resources drop below a + * certain threshold, disable Tx bundling for all + * AC's with priority lower than the current requesting + * AC. Otherwise re-enable Tx bundling for them + */ + if (scat_req->scat_q_depth < ATH6KL_SCATTER_REQS) + target->tx_bndl_mask &= ~txb_mask; + else + target->tx_bndl_mask |= txb_mask; + } + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx pkts to scatter: %d\n", n_scat); @@ -785,6 +834,13 @@ static void ath6kl_htc_tx_bundle(struct htc_endpoint *endpoint, ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx scatter bytes %d entries %d\n", scat_req->len, scat_req->scat_entries); + + for (i = 0; i < scat_req->scat_entries; i++) { + packet = scat_req->scat_list[i].packet; + trace_ath6kl_htc_tx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + } + ath6kl_hif_submit_scat_req(target->dev, scat_req, false); if (status) @@ -806,6 +862,8 @@ static void ath6kl_htc_tx_from_queue(struct htc_target *target, struct htc_packet *packet; int bundle_sent; int n_pkts_bundle; + u8 ac = WMM_NUM_AC; + int status; spin_lock_bh(&target->tx_lock); @@ -823,8 +881,11 @@ static void ath6kl_htc_tx_from_queue(struct htc_target *target, */ INIT_LIST_HEAD(&txq); - while (true) { + if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) && + (WMI_CONTROL_SVC != endpoint->svc_id)) + ac = target->dev->ar->ep2ac_map[endpoint->eid]; + while (true) { if (list_empty(&endpoint->txq)) break; @@ -840,15 +901,18 @@ static void ath6kl_htc_tx_from_queue(struct htc_target *target, while (true) { /* try to send a bundle on each pass */ - if ((target->tx_bndl_enable) && + if ((target->tx_bndl_mask) && (get_queue_depth(&txq) >= HTC_MIN_HTC_MSGS_TO_BUNDLE)) { int temp1 = 0, temp2 = 0; - ath6kl_htc_tx_bundle(endpoint, &txq, - &temp1, &temp2); - bundle_sent += temp1; - n_pkts_bundle += temp2; + /* check if bundling is enabled for an AC */ + if (target->tx_bndl_mask & (1 << ac)) { + ath6kl_htc_tx_bundle(endpoint, &txq, + &temp1, &temp2); + bundle_sent += temp1; + n_pkts_bundle += temp2; + } } if (list_empty(&txq)) @@ -860,13 +924,38 @@ static void ath6kl_htc_tx_from_queue(struct htc_target *target, ath6kl_htc_tx_prep_pkt(packet, packet->info.tx.flags, 0, packet->info.tx.seqno); - ath6kl_htc_tx_issue(target, packet); + status = ath6kl_htc_tx_issue(target, packet); + + if (status) { + packet->status = status; + packet->completion(packet->context, packet); + } } spin_lock_bh(&target->tx_lock); endpoint->ep_st.tx_bundles += bundle_sent; endpoint->ep_st.tx_pkt_bundled += n_pkts_bundle; + + /* + * if an AC has bundling disabled and no tx bundling + * has occured continously for a certain number of TX, + * enable tx bundling for this AC + */ + if (!bundle_sent) { + if (!(target->tx_bndl_mask & (1 << ac)) && + (ac < WMM_NUM_AC)) { + if (++target->ac_tx_count[ac] >= + TX_RESUME_BUNDLE_THRESHOLD) { + target->ac_tx_count[ac] = 0; + target->tx_bndl_mask |= (1 << ac); + } + } + } else { + /* tx bundling will reset the counter */ + if (ac < WMM_NUM_AC) + target->ac_tx_count[ac] = 0; + } } endpoint->tx_proc_cnt = 0; @@ -979,8 +1068,8 @@ static int htc_setup_tx_complete(struct htc_target *target) memcpy(&setup_comp_ext->flags, &flags, sizeof(setup_comp_ext->flags)); set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp_ext, - sizeof(struct htc_setup_comp_ext_msg), - ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + sizeof(struct htc_setup_comp_ext_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); } else { struct htc_setup_comp_msg *setup_comp; @@ -988,8 +1077,8 @@ static int htc_setup_tx_complete(struct htc_target *target) memset(setup_comp, 0, sizeof(struct htc_setup_comp_msg)); setup_comp->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_ID); set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp, - sizeof(struct htc_setup_comp_msg), - ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + sizeof(struct htc_setup_comp_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); } /* we want synchronous operation */ @@ -1003,7 +1092,7 @@ static int htc_setup_tx_complete(struct htc_target *target) return status; } -void ath6kl_htc_set_credit_dist(struct htc_target *target, +static void ath6kl_htc_set_credit_dist(struct htc_target *target, struct ath6kl_htc_credit_info *credit_info, u16 srvc_pri_order[], int list_len) { @@ -1031,7 +1120,8 @@ void ath6kl_htc_set_credit_dist(struct htc_target *target, } } -int ath6kl_htc_tx(struct htc_target *target, struct htc_packet *packet) +static int ath6kl_htc_mbox_tx(struct htc_target *target, + struct htc_packet *packet) { struct htc_endpoint *endpoint; struct list_head queue; @@ -1059,7 +1149,7 @@ int ath6kl_htc_tx(struct htc_target *target, struct htc_packet *packet) } /* flush endpoint TX queue */ -void ath6kl_htc_flush_txep(struct htc_target *target, +static void ath6kl_htc_mbox_flush_txep(struct htc_target *target, enum htc_endpoint_id eid, u16 tag) { struct htc_packet *packet, *tmp_pkt; @@ -1088,15 +1178,14 @@ void ath6kl_htc_flush_txep(struct htc_target *target, packet->status = -ECANCELED; list_del(&packet->list); ath6kl_dbg(ATH6KL_DBG_HTC, - "htc tx flushing pkt 0x%p len %d ep %d tag 0x%x\n", - packet, packet->act_len, - packet->endpoint, packet->info.tx.tag); + "htc tx flushing pkt 0x%p len %d ep %d tag 0x%x\n", + packet, packet->act_len, + packet->endpoint, packet->info.tx.tag); INIT_LIST_HEAD(&container); list_add_tail(&packet->list, &container); htc_tx_complete(endpoint, &container); } - } static void ath6kl_htc_flush_txep_all(struct htc_target *target) @@ -1111,12 +1200,13 @@ static void ath6kl_htc_flush_txep_all(struct htc_target *target) if (endpoint->svc_id == 0) /* not in use.. */ continue; - ath6kl_htc_flush_txep(target, i, HTC_TX_PACKET_TAG_ALL); + ath6kl_htc_mbox_flush_txep(target, i, HTC_TX_PACKET_TAG_ALL); } } -void ath6kl_htc_indicate_activity_change(struct htc_target *target, - enum htc_endpoint_id eid, bool active) +static void ath6kl_htc_mbox_activity_changed(struct htc_target *target, + enum htc_endpoint_id eid, + bool active) { struct htc_endpoint *endpoint = &target->endpoint[eid]; bool dist = false; @@ -1184,7 +1274,7 @@ static int htc_add_rxbuf(struct htc_target *target, struct htc_packet *packet) INIT_LIST_HEAD(&queue); list_add_tail(&packet->list, &queue); - return ath6kl_htc_add_rxbuf_multiple(target, &queue); + return ath6kl_htc_mbox_add_rxbuf_multiple(target, &queue); } static void htc_reclaim_rxbuf(struct htc_target *target, @@ -1226,7 +1316,7 @@ static int ath6kl_htc_rx_packet(struct htc_target *target, } ath6kl_dbg(ATH6KL_DBG_HTC, - "htc rx 0x%p hdr x%x len %d mbox 0x%x\n", + "htc rx 0x%p hdr 0x%x len %d mbox 0x%x\n", packet, packet->info.rx.exp_hdr, padded_len, dev->ar->mbox_info.htc_addr); @@ -1291,13 +1381,14 @@ static int ath6kl_htc_rx_setup(struct htc_target *target, sizeof(*htc_hdr)); if (!htc_valid_rx_frame_len(target, ep->eid, full_len)) { - ath6kl_warn("Rx buffer requested with invalid length\n"); + ath6kl_warn("Rx buffer requested with invalid length htc_hdr:eid %d, flags 0x%x, len %d\n", + htc_hdr->eid, htc_hdr->flags, + le16_to_cpu(htc_hdr->payld_len)); return -EINVAL; } ep_cb = ep->ep_cb; for (j = 0; j < n_msg; j++) { - /* * Reset flag, any packets allocated using the * rx_alloc() API cannot be recycled on @@ -1327,9 +1418,9 @@ static int ath6kl_htc_rx_setup(struct htc_target *target, } } - if (list_empty(&ep->rx_bufq)) + if (list_empty(&ep->rx_bufq)) { packet = NULL; - else { + } else { packet = list_first_entry(&ep->rx_bufq, struct htc_packet, list); list_del(&packet->list); @@ -1390,7 +1481,6 @@ static int ath6kl_htc_rx_alloc(struct htc_target *target, spin_lock_bh(&target->rx_lock); for (i = 0; i < msg; i++) { - htc_hdr = (struct htc_frame_hdr *)&lk_ahds[i]; if (htc_hdr->eid >= ENDPOINT_MAX) { @@ -1490,7 +1580,7 @@ static void htc_ctrl_rx(struct htc_target *context, struct htc_packet *packets) if (packets->act_len > 0) { ath6kl_err("htc_ctrl_rx, got message with len:%zu\n", - packets->act_len + HTC_HDR_LENGTH); + packets->act_len + HTC_HDR_LENGTH); ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx unexpected endpoint 0 message", "", @@ -1609,9 +1699,8 @@ static int htc_parse_trailer(struct htc_target *target, } lk_ahd = (struct htc_lookahead_report *) record_buf; - if ((lk_ahd->pre_valid == ((~lk_ahd->post_valid) & 0xFF)) - && next_lk_ahds) { - + if ((lk_ahd->pre_valid == ((~lk_ahd->post_valid) & 0xFF)) && + next_lk_ahds) { ath6kl_dbg(ATH6KL_DBG_HTC, "htc rx lk_ahd found pre_valid 0x%x post_valid 0x%x\n", lk_ahd->pre_valid, lk_ahd->post_valid); @@ -1658,7 +1747,6 @@ static int htc_parse_trailer(struct htc_target *target, } return 0; - } static int htc_proc_trailer(struct htc_target *target, @@ -1679,7 +1767,6 @@ static int htc_proc_trailer(struct htc_target *target, status = 0; while (len > 0) { - if (len < sizeof(struct htc_record_hdr)) { status = -ENOMEM; break; @@ -1818,6 +1905,7 @@ static void ath6kl_htc_rx_complete(struct htc_endpoint *endpoint, ath6kl_dbg(ATH6KL_DBG_HTC, "htc rx complete ep %d packet 0x%p\n", endpoint->eid, packet); + endpoint->ep_cb.rx(endpoint->target, packet); } @@ -1926,6 +2014,9 @@ static int ath6kl_htc_rx_process_packets(struct htc_target *target, list_for_each_entry_safe(packet, tmp_pkt, comp_pktq, list) { ep = &target->endpoint[packet->endpoint]; + trace_ath6kl_htc_rx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + /* process header for each of the recv packet */ status = ath6kl_htc_rx_process_hdr(target, packet, lk_ahds, n_lk_ahd); @@ -1997,7 +2088,6 @@ static int ath6kl_htc_rx_fetch(struct htc_target *target, } if (!fetched_pkts) { - packet = list_first_entry(rx_pktq, struct htc_packet, list); @@ -2038,13 +2128,13 @@ fail_rx: list_for_each_entry_safe(packet, tmp_pkt, rx_pktq, list) { list_del(&packet->list); htc_reclaim_rxbuf(target, packet, - &target->endpoint[packet->endpoint]); + &target->endpoint[packet->endpoint]); } list_for_each_entry_safe(packet, tmp_pkt, &tmp_rxq, list) { list_del(&packet->list); htc_reclaim_rxbuf(target, packet, - &target->endpoint[packet->endpoint]); + &target->endpoint[packet->endpoint]); } return status; @@ -2062,6 +2152,7 @@ int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, enum htc_endpoint_id id; int n_fetched = 0; + INIT_LIST_HEAD(&comp_pktq); *num_pkts = 0; /* @@ -2071,7 +2162,6 @@ int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, look_aheads[0] = msg_look_ahead; while (true) { - /* * First lookahead sets the expected endpoint IDs for all * packets in a bundle. @@ -2175,11 +2265,11 @@ static struct htc_packet *htc_wait_for_ctrl_msg(struct htc_target *target) u32 look_ahead; if (ath6kl_hif_poll_mboxmsg_rx(target->dev, &look_ahead, - HTC_TARGET_RESPONSE_TIMEOUT)) + HTC_TARGET_RESPONSE_TIMEOUT)) return NULL; ath6kl_dbg(ATH6KL_DBG_HTC, - "htc rx wait ctrl look_ahead 0x%X\n", look_ahead); + "htc rx wait ctrl look_ahead 0x%X\n", look_ahead); htc_hdr = (struct htc_frame_hdr *)&look_ahead; @@ -2205,6 +2295,9 @@ static struct htc_packet *htc_wait_for_ctrl_msg(struct htc_target *target) if (ath6kl_htc_rx_packet(target, packet, packet->act_len)) goto fail_ctrl_rx; + trace_ath6kl_htc_rx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + /* process receive header */ packet->status = ath6kl_htc_rx_process_hdr(target, packet, NULL, NULL); @@ -2225,7 +2318,7 @@ fail_ctrl_rx: return NULL; } -int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, +static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target, struct list_head *pkt_queue) { struct htc_endpoint *endpoint; @@ -2244,7 +2337,7 @@ int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, depth = get_queue_depth(pkt_queue); ath6kl_dbg(ATH6KL_DBG_HTC, - "htc rx add multiple ep id %d cnt %d len %d\n", + "htc rx add multiple ep id %d cnt %d len %d\n", first_pkt->endpoint, depth, first_pkt->buf_len); endpoint = &target->endpoint[first_pkt->endpoint]; @@ -2270,8 +2363,8 @@ int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, if (target->rx_st_flags & HTC_RECV_WAIT_BUFFERS) { if (target->ep_waiting == first_pkt->endpoint) { ath6kl_dbg(ATH6KL_DBG_HTC, - "htc rx blocked on ep %d, unblocking\n", - target->ep_waiting); + "htc rx blocked on ep %d, unblocking\n", + target->ep_waiting); target->rx_st_flags &= ~HTC_RECV_WAIT_BUFFERS; target->ep_waiting = ENDPOINT_MAX; rx_unblock = true; @@ -2287,7 +2380,7 @@ int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, return status; } -void ath6kl_htc_flush_rx_buf(struct htc_target *target) +static void ath6kl_htc_mbox_flush_rx_buf(struct htc_target *target) { struct htc_endpoint *endpoint; struct htc_packet *packet, *tmp_pkt; @@ -2308,14 +2401,28 @@ void ath6kl_htc_flush_rx_buf(struct htc_target *target) "htc rx flush pkt 0x%p len %d ep %d\n", packet, packet->buf_len, packet->endpoint); - dev_kfree_skb(packet->pkt_cntxt); + /* + * packets in rx_bufq of endpoint 0 have originally + * been queued from target->free_ctrl_rxbuf where + * packet and packet->buf_start are allocated + * separately using kmalloc(). For other endpoint + * rx_bufq, it is allocated as skb where packet is + * skb->head. Take care of this difference while freeing + * the memory. + */ + if (packet->endpoint == ENDPOINT_0) { + kfree(packet->buf_start); + kfree(packet); + } else { + dev_kfree_skb(packet->pkt_cntxt); + } spin_lock_bh(&target->rx_lock); } spin_unlock_bh(&target->rx_lock); } } -int ath6kl_htc_conn_service(struct htc_target *target, +static int ath6kl_htc_mbox_conn_service(struct htc_target *target, struct htc_service_connect_req *conn_req, struct htc_service_connect_resp *conn_resp) { @@ -2327,6 +2434,7 @@ int ath6kl_htc_conn_service(struct htc_target *target, enum htc_endpoint_id assigned_ep = ENDPOINT_MAX; unsigned int max_msg_sz = 0; int status = 0; + u16 msg_id; ath6kl_dbg(ATH6KL_DBG_HTC, "htc connect service target 0x%p service id 0x%x\n", @@ -2370,9 +2478,10 @@ int ath6kl_htc_conn_service(struct htc_target *target, } resp_msg = (struct htc_conn_service_resp *)rx_pkt->buf; + msg_id = le16_to_cpu(resp_msg->msg_id); - if ((le16_to_cpu(resp_msg->msg_id) != HTC_MSG_CONN_SVC_RESP_ID) - || (rx_pkt->act_len < sizeof(*resp_msg))) { + if ((msg_id != HTC_MSG_CONN_SVC_RESP_ID) || + (rx_pkt->act_len < sizeof(*resp_msg))) { status = -ENOMEM; goto fail_tx; } @@ -2390,7 +2499,8 @@ int ath6kl_htc_conn_service(struct htc_target *target, max_msg_sz = le16_to_cpu(resp_msg->max_msg_sz); } - if (assigned_ep >= ENDPOINT_MAX || !max_msg_sz) { + if (WARN_ON_ONCE(assigned_ep == ENDPOINT_UNUSED || + assigned_ep >= ENDPOINT_MAX || !max_msg_sz)) { status = -ENOMEM; goto fail_tx; } @@ -2419,6 +2529,15 @@ int ath6kl_htc_conn_service(struct htc_target *target, endpoint->cred_dist.endpoint = assigned_ep; endpoint->cred_dist.cred_sz = target->tgt_cred_sz; + switch (endpoint->svc_id) { + case WMI_DATA_BK_SVC: + endpoint->tx_drop_packet_threshold = MAX_DEF_COOKIE_NUM / 3; + break; + default: + endpoint->tx_drop_packet_threshold = MAX_HI_COOKIE_NUM; + break; + } + if (conn_req->max_rxmsg_sz) { /* * Override cred_per_msg calculation, this optimizes @@ -2476,7 +2595,7 @@ static void reset_ep_state(struct htc_target *target) INIT_LIST_HEAD(&target->cred_dist_list); } -int ath6kl_htc_get_rxbuf_num(struct htc_target *target, +static int ath6kl_htc_mbox_get_rxbuf_num(struct htc_target *target, enum htc_endpoint_id endpoint) { int num; @@ -2516,7 +2635,8 @@ static void htc_setup_msg_bndl(struct htc_target *target) target->max_rx_bndl_sz, target->max_tx_bndl_sz); if (target->max_tx_bndl_sz) - target->tx_bndl_enable = true; + /* tx_bndl_mask is enabled per AC, each has 1 bit */ + target->tx_bndl_mask = (1 << WMM_NUM_AC) - 1; if (target->max_rx_bndl_sz) target->rx_bndl_enable = true; @@ -2531,11 +2651,11 @@ static void htc_setup_msg_bndl(struct htc_target *target) * padding will spill into the next credit buffer * which is fatal. */ - target->tx_bndl_enable = false; + target->tx_bndl_mask = 0; } } -int ath6kl_htc_wait_target(struct htc_target *target) +static int ath6kl_htc_mbox_wait_target(struct htc_target *target) { struct htc_packet *packet = NULL; struct htc_ready_ext_msg *rdy_msg; @@ -2582,8 +2702,8 @@ int ath6kl_htc_wait_target(struct htc_target *target) } ath6kl_dbg(ATH6KL_DBG_BOOT, "htc using protocol %s (%d)\n", - (target->htc_tgt_ver == HTC_VERSION_2P0) ? "2.0" : ">= 2.1", - target->htc_tgt_ver); + (target->htc_tgt_ver == HTC_VERSION_2P0) ? "2.0" : ">= 2.1", + target->htc_tgt_ver); if (target->msg_per_bndl_max > 0) htc_setup_msg_bndl(target); @@ -2598,12 +2718,12 @@ int ath6kl_htc_wait_target(struct htc_target *target) connect.svc_id = HTC_CTRL_RSVD_SVC; /* connect fake service */ - status = ath6kl_htc_conn_service((void *)target, &connect, &resp); + status = ath6kl_htc_mbox_conn_service((void *)target, &connect, &resp); if (status) /* * FIXME: this call doesn't make sense, the caller should - * call ath6kl_htc_cleanup() when it wants remove htc + * call ath6kl_htc_mbox_cleanup() when it wants remove htc */ ath6kl_hif_cleanup_scatter(target->dev->ar); @@ -2620,7 +2740,7 @@ fail_wait_target: * Start HTC, enable interrupts and let the target know * host has finished setup. */ -int ath6kl_htc_start(struct htc_target *target) +static int ath6kl_htc_mbox_start(struct htc_target *target) { struct htc_packet *packet; int status; @@ -2657,7 +2777,7 @@ int ath6kl_htc_start(struct htc_target *target) status = ath6kl_hif_unmask_intrs(target->dev); if (status) - ath6kl_htc_stop(target); + ath6kl_htc_mbox_stop(target); return status; } @@ -2693,15 +2813,16 @@ static int ath6kl_htc_reset(struct htc_target *target) packet->buf = packet->buf_start; packet->endpoint = ENDPOINT_0; list_add_tail(&packet->list, &target->free_ctrl_rxbuf); - } else + } else { list_add_tail(&packet->list, &target->free_ctrl_txbuf); + } } return 0; } /* htc_stop: stop interrupt reception, and flush all queued buffers */ -void ath6kl_htc_stop(struct htc_target *target) +static void ath6kl_htc_mbox_stop(struct htc_target *target) { spin_lock_bh(&target->htc_lock); target->htc_flags |= HTC_OP_STATE_STOPPING; @@ -2716,12 +2837,12 @@ void ath6kl_htc_stop(struct htc_target *target) ath6kl_htc_flush_txep_all(target); - ath6kl_htc_flush_rx_buf(target); + ath6kl_htc_mbox_flush_rx_buf(target); ath6kl_htc_reset(target); } -void *ath6kl_htc_create(struct ath6kl *ar) +static void *ath6kl_htc_mbox_create(struct ath6kl *ar) { struct htc_target *target = NULL; int status = 0; @@ -2762,27 +2883,27 @@ void *ath6kl_htc_create(struct ath6kl *ar) return target; err_htc_cleanup: - ath6kl_htc_cleanup(target); + ath6kl_htc_mbox_cleanup(target); return NULL; } /* cleanup the HTC instance */ -void ath6kl_htc_cleanup(struct htc_target *target) +static void ath6kl_htc_mbox_cleanup(struct htc_target *target) { struct htc_packet *packet, *tmp_packet; ath6kl_hif_cleanup_scatter(target->dev->ar); list_for_each_entry_safe(packet, tmp_packet, - &target->free_ctrl_txbuf, list) { + &target->free_ctrl_txbuf, list) { list_del(&packet->list); kfree(packet->buf_start); kfree(packet); } list_for_each_entry_safe(packet, tmp_packet, - &target->free_ctrl_rxbuf, list) { + &target->free_ctrl_rxbuf, list) { list_del(&packet->list); kfree(packet->buf_start); kfree(packet); @@ -2791,3 +2912,24 @@ void ath6kl_htc_cleanup(struct htc_target *target) kfree(target->dev); kfree(target); } + +static const struct ath6kl_htc_ops ath6kl_htc_mbox_ops = { + .create = ath6kl_htc_mbox_create, + .wait_target = ath6kl_htc_mbox_wait_target, + .start = ath6kl_htc_mbox_start, + .conn_service = ath6kl_htc_mbox_conn_service, + .tx = ath6kl_htc_mbox_tx, + .stop = ath6kl_htc_mbox_stop, + .cleanup = ath6kl_htc_mbox_cleanup, + .flush_txep = ath6kl_htc_mbox_flush_txep, + .flush_rx_buf = ath6kl_htc_mbox_flush_rx_buf, + .activity_changed = ath6kl_htc_mbox_activity_changed, + .get_rxbuf_num = ath6kl_htc_mbox_get_rxbuf_num, + .add_rxbuf_multiple = ath6kl_htc_mbox_add_rxbuf_multiple, + .credit_setup = ath6kl_htc_mbox_credit_setup, +}; + +void ath6kl_htc_mbox_attach(struct ath6kl *ar) +{ + ar->htc_ops = &ath6kl_htc_mbox_ops; +} diff --git a/drivers/net/wireless/ath/ath6kl/htc_pipe.c b/drivers/net/wireless/ath/ath6kl/htc_pipe.c new file mode 100644 index 00000000000..756fe52a12c --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc_pipe.c @@ -0,0 +1,1711 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "debug.h" +#include "hif-ops.h" + +#define HTC_PACKET_CONTAINER_ALLOCATION 32 +#define HTC_CONTROL_BUFFER_SIZE (HTC_MAX_CTRL_MSG_LEN + HTC_HDR_LENGTH) + +static int ath6kl_htc_pipe_tx(struct htc_target *handle, + struct htc_packet *packet); +static void ath6kl_htc_pipe_cleanup(struct htc_target *handle); + +/* htc pipe tx path */ +static inline void restore_tx_packet(struct htc_packet *packet) +{ + if (packet->info.tx.flags & HTC_FLAGS_TX_FIXUP_NETBUF) { + skb_pull(packet->skb, sizeof(struct htc_frame_hdr)); + packet->info.tx.flags &= ~HTC_FLAGS_TX_FIXUP_NETBUF; + } +} + +static void do_send_completion(struct htc_endpoint *ep, + struct list_head *queue_to_indicate) +{ + struct htc_packet *packet; + + if (list_empty(queue_to_indicate)) { + /* nothing to indicate */ + return; + } + + if (ep->ep_cb.tx_comp_multi != NULL) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: calling ep %d, send complete multiple callback (%d pkts)\n", + __func__, ep->eid, + get_queue_depth(queue_to_indicate)); + /* + * a multiple send complete handler is being used, + * pass the queue to the handler + */ + ep->ep_cb.tx_comp_multi(ep->target, queue_to_indicate); + /* + * all packets are now owned by the callback, + * reset queue to be safe + */ + INIT_LIST_HEAD(queue_to_indicate); + } else { + /* using legacy EpTxComplete */ + do { + packet = list_first_entry(queue_to_indicate, + struct htc_packet, list); + + list_del(&packet->list); + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: calling ep %d send complete callback on packet 0x%p\n", + __func__, ep->eid, packet); + ep->ep_cb.tx_complete(ep->target, packet); + } while (!list_empty(queue_to_indicate)); + } +} + +static void send_packet_completion(struct htc_target *target, + struct htc_packet *packet) +{ + struct htc_endpoint *ep = &target->endpoint[packet->endpoint]; + struct list_head container; + + restore_tx_packet(packet); + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* do completion */ + do_send_completion(ep, &container); +} + +static void get_htc_packet_credit_based(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *queue) +{ + int credits_required; + int remainder; + u8 send_flags; + struct htc_packet *packet; + unsigned int transfer_len; + + /* NOTE : the TX lock is held when this function is called */ + + /* loop until we can grab as many packets out of the queue as we can */ + while (true) { + send_flags = 0; + if (list_empty(&ep->txq)) + break; + + /* get packet at head, but don't remove it */ + packet = list_first_entry(&ep->txq, struct htc_packet, list); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: got head packet:0x%p , queue depth: %d\n", + __func__, packet, get_queue_depth(&ep->txq)); + + transfer_len = packet->act_len + HTC_HDR_LENGTH; + + if (transfer_len <= target->tgt_cred_sz) { + credits_required = 1; + } else { + /* figure out how many credits this message requires */ + credits_required = transfer_len / target->tgt_cred_sz; + remainder = transfer_len % target->tgt_cred_sz; + + if (remainder) + credits_required++; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: creds required:%d got:%d\n", + __func__, credits_required, ep->cred_dist.credits); + + if (ep->eid == ENDPOINT_0) { + /* + * endpoint 0 is special, it always has a credit and + * does not require credit based flow control + */ + credits_required = 0; + + } else { + if (ep->cred_dist.credits < credits_required) + break; + + ep->cred_dist.credits -= credits_required; + ep->ep_st.cred_cosumd += credits_required; + + /* check if we need credits back from the target */ + if (ep->cred_dist.credits < + ep->cred_dist.cred_per_msg) { + /* tell the target we need credits ASAP! */ + send_flags |= HTC_FLAGS_NEED_CREDIT_UPDATE; + ep->ep_st.cred_low_indicate += 1; + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: host needs credits\n", + __func__); + } + } + + /* now we can fully dequeue */ + packet = list_first_entry(&ep->txq, struct htc_packet, list); + + list_del(&packet->list); + /* save the number of credits this packet consumed */ + packet->info.tx.cred_used = credits_required; + /* save send flags */ + packet->info.tx.flags = send_flags; + packet->info.tx.seqno = ep->seqno; + ep->seqno++; + /* queue this packet into the caller's queue */ + list_add_tail(&packet->list, queue); + } +} + +static void get_htc_packet(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *queue, int resources) +{ + struct htc_packet *packet; + + /* NOTE : the TX lock is held when this function is called */ + + /* loop until we can grab as many packets out of the queue as we can */ + while (resources) { + if (list_empty(&ep->txq)) + break; + + packet = list_first_entry(&ep->txq, struct htc_packet, list); + list_del(&packet->list); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: got packet:0x%p , new queue depth: %d\n", + __func__, packet, get_queue_depth(&ep->txq)); + packet->info.tx.seqno = ep->seqno; + packet->info.tx.flags = 0; + packet->info.tx.cred_used = 0; + ep->seqno++; + + /* queue this packet into the caller's queue */ + list_add_tail(&packet->list, queue); + resources--; + } +} + +static int htc_issue_packets(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *pkt_queue) +{ + int status = 0; + u16 payload_len; + struct sk_buff *skb; + struct htc_frame_hdr *htc_hdr; + struct htc_packet *packet; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: queue: 0x%p, pkts %d\n", __func__, + pkt_queue, get_queue_depth(pkt_queue)); + + while (!list_empty(pkt_queue)) { + packet = list_first_entry(pkt_queue, struct htc_packet, list); + list_del(&packet->list); + + skb = packet->skb; + if (!skb) { + WARN_ON_ONCE(1); + status = -EINVAL; + break; + } + + payload_len = packet->act_len; + + /* setup HTC frame header */ + htc_hdr = (struct htc_frame_hdr *) skb_push(skb, + sizeof(*htc_hdr)); + if (!htc_hdr) { + WARN_ON_ONCE(1); + status = -EINVAL; + break; + } + + packet->info.tx.flags |= HTC_FLAGS_TX_FIXUP_NETBUF; + + /* Endianess? */ + put_unaligned((u16) payload_len, &htc_hdr->payld_len); + htc_hdr->flags = packet->info.tx.flags; + htc_hdr->eid = (u8) packet->endpoint; + htc_hdr->ctrl[0] = 0; + htc_hdr->ctrl[1] = (u8) packet->info.tx.seqno; + + spin_lock_bh(&target->tx_lock); + + /* store in look up queue to match completions */ + list_add_tail(&packet->list, &ep->pipe.tx_lookup_queue); + ep->ep_st.tx_issued += 1; + spin_unlock_bh(&target->tx_lock); + + status = ath6kl_hif_pipe_send(target->dev->ar, + ep->pipe.pipeid_ul, NULL, skb); + + if (status != 0) { + if (status != -ENOMEM) { + /* TODO: if more than 1 endpoint maps to the + * same PipeID, it is possible to run out of + * resources in the HIF layer. + * Don't emit the error + */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: failed status:%d\n", + __func__, status); + } + spin_lock_bh(&target->tx_lock); + list_del(&packet->list); + + /* reclaim credits */ + ep->cred_dist.credits += packet->info.tx.cred_used; + spin_unlock_bh(&target->tx_lock); + + /* put it back into the callers queue */ + list_add(&packet->list, pkt_queue); + break; + } + } + + if (status != 0) { + while (!list_empty(pkt_queue)) { + if (status != -ENOMEM) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: failed pkt:0x%p status:%d\n", + __func__, packet, status); + } + + packet = list_first_entry(pkt_queue, + struct htc_packet, list); + list_del(&packet->list); + packet->status = status; + send_packet_completion(target, packet); + } + } + + return status; +} + +static enum htc_send_queue_result htc_try_send(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *txq) +{ + struct list_head send_queue; /* temp queue to hold packets */ + struct htc_packet *packet, *tmp_pkt; + struct ath6kl *ar = target->dev->ar; + enum htc_send_full_action action; + int tx_resources, overflow, txqueue_depth, i, good_pkts; + u8 pipeid; + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: (queue:0x%p depth:%d)\n", + __func__, txq, + (txq == NULL) ? 0 : get_queue_depth(txq)); + + /* init the local send queue */ + INIT_LIST_HEAD(&send_queue); + + /* + * txq equals to NULL means + * caller didn't provide a queue, just wants us to + * check queues and send + */ + if (txq != NULL) { + if (list_empty(txq)) { + /* empty queue */ + return HTC_SEND_QUEUE_DROP; + } + + spin_lock_bh(&target->tx_lock); + txqueue_depth = get_queue_depth(&ep->txq); + spin_unlock_bh(&target->tx_lock); + + if (txqueue_depth >= ep->max_txq_depth) { + /* we've already overflowed */ + overflow = get_queue_depth(txq); + } else { + /* get how much we will overflow by */ + overflow = txqueue_depth; + overflow += get_queue_depth(txq); + /* get how much we will overflow the TX queue by */ + overflow -= ep->max_txq_depth; + } + + /* if overflow is negative or zero, we are okay */ + if (overflow > 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: Endpoint %d, TX queue will overflow :%d, Tx Depth:%d, Max:%d\n", + __func__, ep->eid, overflow, txqueue_depth, + ep->max_txq_depth); + } + if ((overflow <= 0) || + (ep->ep_cb.tx_full == NULL)) { + /* + * all packets will fit or caller did not provide send + * full indication handler -- just move all of them + * to the local send_queue object + */ + list_splice_tail_init(txq, &send_queue); + } else { + good_pkts = get_queue_depth(txq) - overflow; + if (good_pkts < 0) { + WARN_ON_ONCE(1); + return HTC_SEND_QUEUE_DROP; + } + + /* we have overflowed, and a callback is provided */ + /* dequeue all non-overflow packets to the sendqueue */ + for (i = 0; i < good_pkts; i++) { + /* pop off caller's queue */ + packet = list_first_entry(txq, + struct htc_packet, + list); + /* move to local queue */ + list_move_tail(&packet->list, &send_queue); + } + + /* + * the caller's queue has all the packets that won't fit + * walk through the caller's queue and indicate each to + * the send full handler + */ + list_for_each_entry_safe(packet, tmp_pkt, + txq, list) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: Indicat overflowed TX pkts: %p\n", + __func__, packet); + action = ep->ep_cb.tx_full(ep->target, packet); + if (action == HTC_SEND_FULL_DROP) { + /* callback wants the packet dropped */ + ep->ep_st.tx_dropped += 1; + + /* leave this one in the caller's queue + * for cleanup */ + } else { + /* callback wants to keep this packet, + * move from caller's queue to the send + * queue */ + list_move_tail(&packet->list, + &send_queue); + } + } + + if (list_empty(&send_queue)) { + /* no packets made it in, caller will cleanup */ + return HTC_SEND_QUEUE_DROP; + } + } + } + + if (!ep->pipe.tx_credit_flow_enabled) { + tx_resources = + ath6kl_hif_pipe_get_free_queue_number(ar, + ep->pipe.pipeid_ul); + } else { + tx_resources = 0; + } + + spin_lock_bh(&target->tx_lock); + if (!list_empty(&send_queue)) { + /* transfer packets to tail */ + list_splice_tail_init(&send_queue, &ep->txq); + if (!list_empty(&send_queue)) { + WARN_ON_ONCE(1); + spin_unlock_bh(&target->tx_lock); + return HTC_SEND_QUEUE_DROP; + } + INIT_LIST_HEAD(&send_queue); + } + + /* increment tx processing count on entry */ + ep->tx_proc_cnt++; + + if (ep->tx_proc_cnt > 1) { + /* + * Another thread or task is draining the TX queues on this + * endpoint that thread will reset the tx processing count + * when the queue is drained. + */ + ep->tx_proc_cnt--; + spin_unlock_bh(&target->tx_lock); + return HTC_SEND_QUEUE_OK; + } + + /***** beyond this point only 1 thread may enter ******/ + + /* + * Now drain the endpoint TX queue for transmission as long as we have + * enough transmit resources. + */ + while (true) { + if (get_queue_depth(&ep->txq) == 0) + break; + + if (ep->pipe.tx_credit_flow_enabled) { + /* + * Credit based mechanism provides flow control + * based on target transmit resource availability, + * we assume that the HIF layer will always have + * bus resources greater than target transmit + * resources. + */ + get_htc_packet_credit_based(target, ep, &send_queue); + } else { + /* + * Get all packets for this endpoint that we can + * for this pass. + */ + get_htc_packet(target, ep, &send_queue, tx_resources); + } + + if (get_queue_depth(&send_queue) == 0) { + /* + * Didn't get packets due to out of resources or TX + * queue was drained. + */ + break; + } + + spin_unlock_bh(&target->tx_lock); + + /* send what we can */ + htc_issue_packets(target, ep, &send_queue); + + if (!ep->pipe.tx_credit_flow_enabled) { + pipeid = ep->pipe.pipeid_ul; + tx_resources = + ath6kl_hif_pipe_get_free_queue_number(ar, pipeid); + } + + spin_lock_bh(&target->tx_lock); + } + + /* done with this endpoint, we can clear the count */ + ep->tx_proc_cnt = 0; + spin_unlock_bh(&target->tx_lock); + + return HTC_SEND_QUEUE_OK; +} + +/* htc control packet manipulation */ +static void destroy_htc_txctrl_packet(struct htc_packet *packet) +{ + struct sk_buff *skb; + skb = packet->skb; + dev_kfree_skb(skb); + kfree(packet); +} + +static struct htc_packet *build_htc_txctrl_packet(void) +{ + struct htc_packet *packet = NULL; + struct sk_buff *skb; + + packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); + if (packet == NULL) + return NULL; + + skb = __dev_alloc_skb(HTC_CONTROL_BUFFER_SIZE, GFP_KERNEL); + + if (skb == NULL) { + kfree(packet); + return NULL; + } + packet->skb = skb; + + return packet; +} + +static void htc_free_txctrl_packet(struct htc_target *target, + struct htc_packet *packet) +{ + destroy_htc_txctrl_packet(packet); +} + +static struct htc_packet *htc_alloc_txctrl_packet(struct htc_target *target) +{ + return build_htc_txctrl_packet(); +} + +static void htc_txctrl_complete(struct htc_target *target, + struct htc_packet *packet) +{ + htc_free_txctrl_packet(target, packet); +} + +#define MAX_MESSAGE_SIZE 1536 + +static int htc_setup_target_buffer_assignments(struct htc_target *target) +{ + int status, credits, credit_per_maxmsg, i; + struct htc_pipe_txcredit_alloc *entry; + unsigned int hif_usbaudioclass = 0; + + credit_per_maxmsg = MAX_MESSAGE_SIZE / target->tgt_cred_sz; + if (MAX_MESSAGE_SIZE % target->tgt_cred_sz) + credit_per_maxmsg++; + + /* TODO, this should be configured by the caller! */ + + credits = target->tgt_creds; + entry = &target->pipe.txcredit_alloc[0]; + + status = -ENOMEM; + + /* FIXME: hif_usbaudioclass is always zero */ + if (hif_usbaudioclass) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: For USB Audio Class- Total:%d\n", + __func__, credits); + entry++; + entry++; + /* Setup VO Service To have Max Credits */ + entry->service_id = WMI_DATA_VO_SVC; + entry->credit_alloc = (credits - 6); + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_CONTROL_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + /* leftovers go to best effort */ + entry++; + entry++; + entry->service_id = WMI_DATA_BE_SVC; + entry->credit_alloc = (u8) credits; + status = 0; + } else { + entry++; + entry->service_id = WMI_DATA_VI_SVC; + entry->credit_alloc = credits / 4; + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_DATA_VO_SVC; + entry->credit_alloc = credits / 4; + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_CONTROL_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_DATA_BK_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + /* leftovers go to best effort */ + entry++; + entry->service_id = WMI_DATA_BE_SVC; + entry->credit_alloc = (u8) credits; + status = 0; + } + + if (status == 0) { + for (i = 0; i < ENDPOINT_MAX; i++) { + if (target->pipe.txcredit_alloc[i].service_id != 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Service Index : %d TX : 0x%2.2X : alloc:%d\n", + i, + target->pipe.txcredit_alloc[i]. + service_id, + target->pipe.txcredit_alloc[i]. + credit_alloc); + } + } + } + return status; +} + +/* process credit reports and call distribution function */ +static void htc_process_credit_report(struct htc_target *target, + struct htc_credit_report *rpt, + int num_entries, + enum htc_endpoint_id from_ep) +{ + int total_credits = 0, i; + struct htc_endpoint *ep; + + /* lock out TX while we update credits */ + spin_lock_bh(&target->tx_lock); + + for (i = 0; i < num_entries; i++, rpt++) { + if (rpt->eid >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + spin_unlock_bh(&target->tx_lock); + return; + } + + ep = &target->endpoint[rpt->eid]; + ep->cred_dist.credits += rpt->credits; + + if (ep->cred_dist.credits && get_queue_depth(&ep->txq)) { + spin_unlock_bh(&target->tx_lock); + htc_try_send(target, ep, NULL); + spin_lock_bh(&target->tx_lock); + } + + total_credits += rpt->credits; + } + ath6kl_dbg(ATH6KL_DBG_HTC, + "Report indicated %d credits to distribute\n", + total_credits); + + spin_unlock_bh(&target->tx_lock); +} + +/* flush endpoint TX queue */ +static void htc_flush_tx_endpoint(struct htc_target *target, + struct htc_endpoint *ep, u16 tag) +{ + struct htc_packet *packet; + + spin_lock_bh(&target->tx_lock); + while (get_queue_depth(&ep->txq)) { + packet = list_first_entry(&ep->txq, struct htc_packet, list); + list_del(&packet->list); + packet->status = 0; + send_packet_completion(target, packet); + } + spin_unlock_bh(&target->tx_lock); +} + +/* + * In the adapted HIF layer, struct sk_buff * are passed between HIF and HTC, + * since upper layers expects struct htc_packet containers we use the completed + * skb and lookup it's corresponding HTC packet buffer from a lookup list. + * This is extra overhead that can be fixed by re-aligning HIF interfaces with + * HTC. + */ +static struct htc_packet *htc_lookup_tx_packet(struct htc_target *target, + struct htc_endpoint *ep, + struct sk_buff *skb) +{ + struct htc_packet *packet, *tmp_pkt, *found_packet = NULL; + + spin_lock_bh(&target->tx_lock); + + /* + * interate from the front of tx lookup queue + * this lookup should be fast since lower layers completes in-order and + * so the completed packet should be at the head of the list generally + */ + list_for_each_entry_safe(packet, tmp_pkt, &ep->pipe.tx_lookup_queue, + list) { + /* check for removal */ + if (skb == packet->skb) { + /* found it */ + list_del(&packet->list); + found_packet = packet; + break; + } + } + + spin_unlock_bh(&target->tx_lock); + + return found_packet; +} + +static int ath6kl_htc_pipe_tx_complete(struct ath6kl *ar, struct sk_buff *skb) +{ + struct htc_target *target = ar->htc_target; + struct htc_frame_hdr *htc_hdr; + struct htc_endpoint *ep; + struct htc_packet *packet; + u8 ep_id, *netdata; + u32 netlen; + + netdata = skb->data; + netlen = skb->len; + + htc_hdr = (struct htc_frame_hdr *) netdata; + + ep_id = htc_hdr->eid; + ep = &target->endpoint[ep_id]; + + packet = htc_lookup_tx_packet(target, ep, skb); + if (packet == NULL) { + /* may have already been flushed and freed */ + ath6kl_err("HTC TX lookup failed!\n"); + } else { + /* will be giving this buffer back to upper layers */ + packet->status = 0; + send_packet_completion(target, packet); + } + skb = NULL; + + if (!ep->pipe.tx_credit_flow_enabled) { + /* + * note: when using TX credit flow, the re-checking of queues + * happens when credits flow back from the target. in the + * non-TX credit case, we recheck after the packet completes + */ + htc_try_send(target, ep, NULL); + } + + return 0; +} + +static int htc_send_packets_multiple(struct htc_target *target, + struct list_head *pkt_queue) +{ + struct htc_endpoint *ep; + struct htc_packet *packet, *tmp_pkt; + + if (list_empty(pkt_queue)) + return -EINVAL; + + /* get first packet to find out which ep the packets will go into */ + packet = list_first_entry(pkt_queue, struct htc_packet, list); + + if (packet->endpoint >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + return -EINVAL; + } + ep = &target->endpoint[packet->endpoint]; + + htc_try_send(target, ep, pkt_queue); + + /* do completion on any packets that couldn't get in */ + if (!list_empty(pkt_queue)) { + list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { + packet->status = -ENOMEM; + } + + do_send_completion(ep, pkt_queue); + } + + return 0; +} + +/* htc pipe rx path */ +static struct htc_packet *alloc_htc_packet_container(struct htc_target *target) +{ + struct htc_packet *packet; + spin_lock_bh(&target->rx_lock); + + if (target->pipe.htc_packet_pool == NULL) { + spin_unlock_bh(&target->rx_lock); + return NULL; + } + + packet = target->pipe.htc_packet_pool; + target->pipe.htc_packet_pool = (struct htc_packet *) packet->list.next; + + spin_unlock_bh(&target->rx_lock); + + packet->list.next = NULL; + return packet; +} + +static void free_htc_packet_container(struct htc_target *target, + struct htc_packet *packet) +{ + struct list_head *lh; + + spin_lock_bh(&target->rx_lock); + + if (target->pipe.htc_packet_pool == NULL) { + target->pipe.htc_packet_pool = packet; + packet->list.next = NULL; + } else { + lh = (struct list_head *) target->pipe.htc_packet_pool; + packet->list.next = lh; + target->pipe.htc_packet_pool = packet; + } + + spin_unlock_bh(&target->rx_lock); +} + +static int htc_process_trailer(struct htc_target *target, u8 *buffer, + int len, enum htc_endpoint_id from_ep) +{ + struct htc_credit_report *report; + struct htc_record_hdr *record; + u8 *record_buf, *orig_buf; + int orig_len, status; + + orig_buf = buffer; + orig_len = len; + status = 0; + + while (len > 0) { + if (len < sizeof(struct htc_record_hdr)) { + status = -EINVAL; + break; + } + + /* these are byte aligned structs */ + record = (struct htc_record_hdr *) buffer; + len -= sizeof(struct htc_record_hdr); + buffer += sizeof(struct htc_record_hdr); + + if (record->len > len) { + /* no room left in buffer for record */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "invalid length: %d (id:%d) buffer has: %d bytes left\n", + record->len, record->rec_id, len); + status = -EINVAL; + break; + } + + /* start of record follows the header */ + record_buf = buffer; + + switch (record->rec_id) { + case HTC_RECORD_CREDITS: + if (record->len < sizeof(struct htc_credit_report)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + report = (struct htc_credit_report *) record_buf; + htc_process_credit_report(target, report, + record->len / sizeof(*report), + from_ep); + break; + default: + ath6kl_dbg(ATH6KL_DBG_HTC, + "unhandled record: id:%d length:%d\n", + record->rec_id, record->len); + break; + } + + if (status != 0) + break; + + /* advance buffer past this record for next time around */ + buffer += record->len; + len -= record->len; + } + + return status; +} + +static void do_recv_completion(struct htc_endpoint *ep, + struct list_head *queue_to_indicate) +{ + struct htc_packet *packet; + + if (list_empty(queue_to_indicate)) { + /* nothing to indicate */ + return; + } + + /* using legacy EpRecv */ + while (!list_empty(queue_to_indicate)) { + packet = list_first_entry(queue_to_indicate, + struct htc_packet, list); + list_del(&packet->list); + ep->ep_cb.rx(ep->target, packet); + } + + return; +} + +static void recv_packet_completion(struct htc_target *target, + struct htc_endpoint *ep, + struct htc_packet *packet) +{ + struct list_head container; + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* do completion */ + do_recv_completion(ep, &container); +} + +static int ath6kl_htc_pipe_rx_complete(struct ath6kl *ar, struct sk_buff *skb, + u8 pipeid) +{ + struct htc_target *target = ar->htc_target; + u8 *netdata, *trailer, hdr_info; + struct htc_frame_hdr *htc_hdr; + u32 netlen, trailerlen = 0; + struct htc_packet *packet; + struct htc_endpoint *ep; + u16 payload_len; + int status = 0; + + /* + * ar->htc_target can be NULL due to a race condition that can occur + * during driver initialization(we do 'ath6kl_hif_power_on' before + * initializing 'ar->htc_target' via 'ath6kl_htc_create'). + * 'ath6kl_hif_power_on' assigns 'ath6kl_recv_complete' as + * usb_complete_t/callback function for 'usb_fill_bulk_urb'. + * Thus the possibility of ar->htc_target being NULL + * via ath6kl_recv_complete -> ath6kl_usb_io_comp_work. + */ + if (WARN_ON_ONCE(!target)) { + ath6kl_err("Target not yet initialized\n"); + status = -EINVAL; + goto free_skb; + } + + + netdata = skb->data; + netlen = skb->len; + + htc_hdr = (struct htc_frame_hdr *) netdata; + + if (htc_hdr->eid >= ENDPOINT_MAX) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Rx: invalid EndpointID=%d\n", + htc_hdr->eid); + status = -EINVAL; + goto free_skb; + } + ep = &target->endpoint[htc_hdr->eid]; + + payload_len = le16_to_cpu(get_unaligned(&htc_hdr->payld_len)); + + if (netlen < (payload_len + HTC_HDR_LENGTH)) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Rx: insufficient length, got:%d expected =%u\n", + netlen, payload_len + HTC_HDR_LENGTH); + status = -EINVAL; + goto free_skb; + } + + /* get flags to check for trailer */ + hdr_info = htc_hdr->flags; + if (hdr_info & HTC_FLG_RX_TRAILER) { + /* extract the trailer length */ + hdr_info = htc_hdr->ctrl[0]; + if ((hdr_info < sizeof(struct htc_record_hdr)) || + (hdr_info > payload_len)) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "invalid header: payloadlen should be %d, CB[0]: %d\n", + payload_len, hdr_info); + status = -EINVAL; + goto free_skb; + } + + trailerlen = hdr_info; + /* process trailer after hdr/apps payload */ + trailer = (u8 *) htc_hdr + HTC_HDR_LENGTH + + payload_len - hdr_info; + status = htc_process_trailer(target, trailer, hdr_info, + htc_hdr->eid); + if (status != 0) + goto free_skb; + } + + if (((int) payload_len - (int) trailerlen) <= 0) { + /* zero length packet with trailer, just drop these */ + goto free_skb; + } + + if (htc_hdr->eid == ENDPOINT_0) { + /* handle HTC control message */ + if (target->htc_flags & HTC_OP_STATE_SETUP_COMPLETE) { + /* + * fatal: target should not send unsolicited + * messageson the endpoint 0 + */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC ignores Rx Ctrl after setup complete\n"); + status = -EINVAL; + goto free_skb; + } + + /* remove HTC header */ + skb_pull(skb, HTC_HDR_LENGTH); + + netdata = skb->data; + netlen = skb->len; + + spin_lock_bh(&target->rx_lock); + + target->pipe.ctrl_response_valid = true; + target->pipe.ctrl_response_len = min_t(int, netlen, + HTC_MAX_CTRL_MSG_LEN); + memcpy(target->pipe.ctrl_response_buf, netdata, + target->pipe.ctrl_response_len); + + spin_unlock_bh(&target->rx_lock); + + dev_kfree_skb(skb); + skb = NULL; + + goto free_skb; + } + + /* + * TODO: the message based HIF architecture allocates net bufs + * for recv packets since it bridges that HIF to upper layers, + * which expects HTC packets, we form the packets here + */ + packet = alloc_htc_packet_container(target); + if (packet == NULL) { + status = -ENOMEM; + goto free_skb; + } + + packet->status = 0; + packet->endpoint = htc_hdr->eid; + packet->pkt_cntxt = skb; + + /* TODO: for backwards compatibility */ + packet->buf = skb_push(skb, 0) + HTC_HDR_LENGTH; + packet->act_len = netlen - HTC_HDR_LENGTH - trailerlen; + + /* + * TODO: this is a hack because the driver layer will set the + * actual len of the skb again which will just double the len + */ + skb_trim(skb, 0); + + recv_packet_completion(target, ep, packet); + + /* recover the packet container */ + free_htc_packet_container(target, packet); + skb = NULL; + +free_skb: + dev_kfree_skb(skb); + + return status; +} + +static void htc_flush_rx_queue(struct htc_target *target, + struct htc_endpoint *ep) +{ + struct list_head container; + struct htc_packet *packet; + + spin_lock_bh(&target->rx_lock); + + while (1) { + if (list_empty(&ep->rx_bufq)) + break; + + packet = list_first_entry(&ep->rx_bufq, + struct htc_packet, list); + list_del(&packet->list); + + spin_unlock_bh(&target->rx_lock); + packet->status = -ECANCELED; + packet->act_len = 0; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "Flushing RX packet:0x%p, length:%d, ep:%d\n", + packet, packet->buf_len, + packet->endpoint); + + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* give the packet back */ + do_recv_completion(ep, &container); + spin_lock_bh(&target->rx_lock); + } + + spin_unlock_bh(&target->rx_lock); +} + +/* polling routine to wait for a control packet to be received */ +static int htc_wait_recv_ctrl_message(struct htc_target *target) +{ + int count = HTC_TARGET_RESPONSE_POLL_COUNT; + + while (count > 0) { + spin_lock_bh(&target->rx_lock); + + if (target->pipe.ctrl_response_valid) { + target->pipe.ctrl_response_valid = false; + spin_unlock_bh(&target->rx_lock); + break; + } + + spin_unlock_bh(&target->rx_lock); + + count--; + + msleep_interruptible(HTC_TARGET_RESPONSE_POLL_WAIT); + } + + if (count <= 0) { + ath6kl_warn("htc pipe control receive timeout!\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void htc_rxctrl_complete(struct htc_target *context, + struct htc_packet *packet) +{ + /* TODO, can't really receive HTC control messages yet.... */ + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: invalid call function\n", __func__); +} + +/* htc pipe initialization */ +static void reset_endpoint_states(struct htc_target *target) +{ + struct htc_endpoint *ep; + int i; + + for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { + ep = &target->endpoint[i]; + ep->svc_id = 0; + ep->len_max = 0; + ep->max_txq_depth = 0; + ep->eid = i; + INIT_LIST_HEAD(&ep->txq); + INIT_LIST_HEAD(&ep->pipe.tx_lookup_queue); + INIT_LIST_HEAD(&ep->rx_bufq); + ep->target = target; + ep->pipe.tx_credit_flow_enabled = true; + } +} + +/* start HTC, this is called after all services are connected */ +static int htc_config_target_hif_pipe(struct htc_target *target) +{ + return 0; +} + +/* htc service functions */ +static u8 htc_get_credit_alloc(struct htc_target *target, u16 service_id) +{ + u8 allocation = 0; + int i; + + for (i = 0; i < ENDPOINT_MAX; i++) { + if (target->pipe.txcredit_alloc[i].service_id == service_id) + allocation = + target->pipe.txcredit_alloc[i].credit_alloc; + } + + if (allocation == 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Service TX : 0x%2.2X : allocation is zero!\n", + service_id); + } + + return allocation; +} + +static int ath6kl_htc_pipe_conn_service(struct htc_target *target, + struct htc_service_connect_req *conn_req, + struct htc_service_connect_resp *conn_resp) +{ + struct ath6kl *ar = target->dev->ar; + struct htc_packet *packet = NULL; + struct htc_conn_service_resp *resp_msg; + struct htc_conn_service_msg *conn_msg; + enum htc_endpoint_id assigned_epid = ENDPOINT_MAX; + bool disable_credit_flowctrl = false; + unsigned int max_msg_size = 0; + struct htc_endpoint *ep; + int length, status = 0; + struct sk_buff *skb; + u8 tx_alloc; + u16 flags; + + if (conn_req->svc_id == 0) { + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + if (conn_req->svc_id == HTC_CTRL_RSVD_SVC) { + /* special case for pseudo control service */ + assigned_epid = ENDPOINT_0; + max_msg_size = HTC_MAX_CTRL_MSG_LEN; + tx_alloc = 0; + + } else { + tx_alloc = htc_get_credit_alloc(target, conn_req->svc_id); + if (tx_alloc == 0) { + status = -ENOMEM; + goto free_packet; + } + + /* allocate a packet to send to the target */ + packet = htc_alloc_txctrl_packet(target); + + if (packet == NULL) { + WARN_ON_ONCE(1); + status = -ENOMEM; + goto free_packet; + } + + skb = packet->skb; + length = sizeof(struct htc_conn_service_msg); + + /* assemble connect service message */ + conn_msg = (struct htc_conn_service_msg *) skb_put(skb, + length); + if (conn_msg == NULL) { + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + memset(conn_msg, 0, + sizeof(struct htc_conn_service_msg)); + conn_msg->msg_id = cpu_to_le16(HTC_MSG_CONN_SVC_ID); + conn_msg->svc_id = cpu_to_le16(conn_req->svc_id); + conn_msg->conn_flags = cpu_to_le16(conn_req->conn_flags & + ~HTC_CONN_FLGS_SET_RECV_ALLOC_MASK); + + /* tell target desired recv alloc for this ep */ + flags = tx_alloc << HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT; + conn_msg->conn_flags |= cpu_to_le16(flags); + + if (conn_req->conn_flags & + HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL) { + disable_credit_flowctrl = true; + } + + set_htc_pkt_info(packet, NULL, (u8 *) conn_msg, + length, + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + status = ath6kl_htc_pipe_tx(target, packet); + + /* we don't own it anymore */ + packet = NULL; + if (status != 0) + goto free_packet; + + /* wait for response */ + status = htc_wait_recv_ctrl_message(target); + if (status != 0) + goto free_packet; + + /* we controlled the buffer creation so it has to be + * properly aligned + */ + resp_msg = (struct htc_conn_service_resp *) + target->pipe.ctrl_response_buf; + + if (resp_msg->msg_id != cpu_to_le16(HTC_MSG_CONN_SVC_RESP_ID) || + (target->pipe.ctrl_response_len < sizeof(*resp_msg))) { + /* this message is not valid */ + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, + "%s: service 0x%X conn resp: status: %d ep: %d\n", + __func__, resp_msg->svc_id, resp_msg->status, + resp_msg->eid); + + conn_resp->resp_code = resp_msg->status; + /* check response status */ + if (resp_msg->status != HTC_SERVICE_SUCCESS) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "Target failed service 0x%X connect request (status:%d)\n", + resp_msg->svc_id, resp_msg->status); + status = -EINVAL; + goto free_packet; + } + + assigned_epid = (enum htc_endpoint_id) resp_msg->eid; + max_msg_size = le16_to_cpu(resp_msg->max_msg_sz); + } + + /* the rest are parameter checks so set the error status */ + status = -EINVAL; + + if (assigned_epid >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + goto free_packet; + } + + if (max_msg_size == 0) { + WARN_ON_ONCE(1); + goto free_packet; + } + + ep = &target->endpoint[assigned_epid]; + ep->eid = assigned_epid; + if (ep->svc_id != 0) { + /* endpoint already in use! */ + WARN_ON_ONCE(1); + goto free_packet; + } + + /* return assigned endpoint to caller */ + conn_resp->endpoint = assigned_epid; + conn_resp->len_max = max_msg_size; + + /* setup the endpoint */ + ep->svc_id = conn_req->svc_id; /* this marks ep in use */ + ep->max_txq_depth = conn_req->max_txq_depth; + ep->len_max = max_msg_size; + ep->cred_dist.credits = tx_alloc; + ep->cred_dist.cred_sz = target->tgt_cred_sz; + ep->cred_dist.cred_per_msg = max_msg_size / target->tgt_cred_sz; + if (max_msg_size % target->tgt_cred_sz) + ep->cred_dist.cred_per_msg++; + + /* copy all the callbacks */ + ep->ep_cb = conn_req->ep_cb; + + /* initialize tx_drop_packet_threshold */ + ep->tx_drop_packet_threshold = MAX_HI_COOKIE_NUM; + + status = ath6kl_hif_pipe_map_service(ar, ep->svc_id, + &ep->pipe.pipeid_ul, + &ep->pipe.pipeid_dl); + if (status != 0) + goto free_packet; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "SVC Ready: 0x%4.4X: ULpipe:%d DLpipe:%d id:%d\n", + ep->svc_id, ep->pipe.pipeid_ul, + ep->pipe.pipeid_dl, ep->eid); + + if (disable_credit_flowctrl && ep->pipe.tx_credit_flow_enabled) { + ep->pipe.tx_credit_flow_enabled = false; + ath6kl_dbg(ATH6KL_DBG_HTC, + "SVC: 0x%4.4X ep:%d TX flow control off\n", + ep->svc_id, assigned_epid); + } + +free_packet: + if (packet != NULL) + htc_free_txctrl_packet(target, packet); + return status; +} + +/* htc export functions */ +static void *ath6kl_htc_pipe_create(struct ath6kl *ar) +{ + int status = 0; + struct htc_endpoint *ep = NULL; + struct htc_target *target = NULL; + struct htc_packet *packet; + int i; + + target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); + if (target == NULL) { + ath6kl_err("htc create unable to allocate memory\n"); + status = -ENOMEM; + goto fail_htc_create; + } + + spin_lock_init(&target->htc_lock); + spin_lock_init(&target->rx_lock); + spin_lock_init(&target->tx_lock); + + reset_endpoint_states(target); + + for (i = 0; i < HTC_PACKET_CONTAINER_ALLOCATION; i++) { + packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); + + if (packet != NULL) + free_htc_packet_container(target, packet); + } + + target->dev = kzalloc(sizeof(*target->dev), GFP_KERNEL); + if (!target->dev) { + ath6kl_err("unable to allocate memory\n"); + status = -ENOMEM; + goto fail_htc_create; + } + target->dev->ar = ar; + target->dev->htc_cnxt = target; + + /* Get HIF default pipe for HTC message exchange */ + ep = &target->endpoint[ENDPOINT_0]; + + ath6kl_hif_pipe_get_default(ar, &ep->pipe.pipeid_ul, + &ep->pipe.pipeid_dl); + + return target; + +fail_htc_create: + if (status != 0) { + if (target != NULL) + ath6kl_htc_pipe_cleanup(target); + + target = NULL; + } + return target; +} + +/* cleanup the HTC instance */ +static void ath6kl_htc_pipe_cleanup(struct htc_target *target) +{ + struct htc_packet *packet; + + while (true) { + packet = alloc_htc_packet_container(target); + if (packet == NULL) + break; + kfree(packet); + } + + kfree(target->dev); + + /* kfree our instance */ + kfree(target); +} + +static int ath6kl_htc_pipe_start(struct htc_target *target) +{ + struct sk_buff *skb; + struct htc_setup_comp_ext_msg *setup; + struct htc_packet *packet; + + htc_config_target_hif_pipe(target); + + /* allocate a buffer to send */ + packet = htc_alloc_txctrl_packet(target); + if (packet == NULL) { + WARN_ON_ONCE(1); + return -ENOMEM; + } + + skb = packet->skb; + + /* assemble setup complete message */ + setup = (struct htc_setup_comp_ext_msg *) skb_put(skb, + sizeof(*setup)); + memset(setup, 0, sizeof(struct htc_setup_comp_ext_msg)); + setup->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_EX_ID); + + ath6kl_dbg(ATH6KL_DBG_HTC, "HTC using TX credit flow control\n"); + + set_htc_pkt_info(packet, NULL, (u8 *) setup, + sizeof(struct htc_setup_comp_ext_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + target->htc_flags |= HTC_OP_STATE_SETUP_COMPLETE; + + return ath6kl_htc_pipe_tx(target, packet); +} + +static void ath6kl_htc_pipe_stop(struct htc_target *target) +{ + int i; + struct htc_endpoint *ep; + + /* cleanup endpoints */ + for (i = 0; i < ENDPOINT_MAX; i++) { + ep = &target->endpoint[i]; + htc_flush_rx_queue(target, ep); + htc_flush_tx_endpoint(target, ep, HTC_TX_PACKET_TAG_ALL); + } + + reset_endpoint_states(target); + target->htc_flags &= ~HTC_OP_STATE_SETUP_COMPLETE; +} + +static int ath6kl_htc_pipe_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + int num; + + spin_lock_bh(&target->rx_lock); + num = get_queue_depth(&(target->endpoint[endpoint].rx_bufq)); + spin_unlock_bh(&target->rx_lock); + + return num; +} + +static int ath6kl_htc_pipe_tx(struct htc_target *target, + struct htc_packet *packet) +{ + struct list_head queue; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: endPointId: %d, buffer: 0x%p, length: %d\n", + __func__, packet->endpoint, packet->buf, + packet->act_len); + + INIT_LIST_HEAD(&queue); + list_add_tail(&packet->list, &queue); + + return htc_send_packets_multiple(target, &queue); +} + +static int ath6kl_htc_pipe_wait_target(struct htc_target *target) +{ + struct htc_ready_ext_msg *ready_msg; + struct htc_service_connect_req connect; + struct htc_service_connect_resp resp; + int status = 0; + + status = htc_wait_recv_ctrl_message(target); + + if (status != 0) + return status; + + if (target->pipe.ctrl_response_len < sizeof(*ready_msg)) { + ath6kl_warn("invalid htc pipe ready msg len: %d\n", + target->pipe.ctrl_response_len); + return -ECOMM; + } + + ready_msg = (struct htc_ready_ext_msg *) target->pipe.ctrl_response_buf; + + if (ready_msg->ver2_0_info.msg_id != cpu_to_le16(HTC_MSG_READY_ID)) { + ath6kl_warn("invalid htc pipe ready msg: 0x%x\n", + ready_msg->ver2_0_info.msg_id); + return -ECOMM; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, + "Target Ready! : transmit resources : %d size:%d\n", + ready_msg->ver2_0_info.cred_cnt, + ready_msg->ver2_0_info.cred_sz); + + target->tgt_creds = le16_to_cpu(ready_msg->ver2_0_info.cred_cnt); + target->tgt_cred_sz = le16_to_cpu(ready_msg->ver2_0_info.cred_sz); + + if ((target->tgt_creds == 0) || (target->tgt_cred_sz == 0)) + return -ECOMM; + + htc_setup_target_buffer_assignments(target); + + /* setup our pseudo HTC control endpoint connection */ + memset(&connect, 0, sizeof(connect)); + memset(&resp, 0, sizeof(resp)); + connect.ep_cb.tx_complete = htc_txctrl_complete; + connect.ep_cb.rx = htc_rxctrl_complete; + connect.max_txq_depth = NUM_CONTROL_TX_BUFFERS; + connect.svc_id = HTC_CTRL_RSVD_SVC; + + /* connect fake service */ + status = ath6kl_htc_pipe_conn_service(target, &connect, &resp); + + return status; +} + +static void ath6kl_htc_pipe_flush_txep(struct htc_target *target, + enum htc_endpoint_id endpoint, u16 tag) +{ + struct htc_endpoint *ep = &target->endpoint[endpoint]; + + if (ep->svc_id == 0) { + WARN_ON_ONCE(1); + /* not in use.. */ + return; + } + + htc_flush_tx_endpoint(target, ep, tag); +} + +static int ath6kl_htc_pipe_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pkt_queue) +{ + struct htc_packet *packet, *tmp_pkt, *first; + struct htc_endpoint *ep; + int status = 0; + + if (list_empty(pkt_queue)) + return -EINVAL; + + first = list_first_entry(pkt_queue, struct htc_packet, list); + + if (first->endpoint >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: epid: %d, cnt:%d, len: %d\n", + __func__, first->endpoint, get_queue_depth(pkt_queue), + first->buf_len); + + ep = &target->endpoint[first->endpoint]; + + spin_lock_bh(&target->rx_lock); + + /* store receive packets */ + list_splice_tail_init(pkt_queue, &ep->rx_bufq); + + spin_unlock_bh(&target->rx_lock); + + if (status != 0) { + /* walk through queue and mark each one canceled */ + list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { + packet->status = -ECANCELED; + } + + do_recv_completion(ep, pkt_queue); + } + + return status; +} + +static void ath6kl_htc_pipe_activity_changed(struct htc_target *target, + enum htc_endpoint_id ep, + bool active) +{ + /* TODO */ +} + +static void ath6kl_htc_pipe_flush_rx_buf(struct htc_target *target) +{ + /* TODO */ +} + +static int ath6kl_htc_pipe_credit_setup(struct htc_target *target, + struct ath6kl_htc_credit_info *info) +{ + return 0; +} + +static const struct ath6kl_htc_ops ath6kl_htc_pipe_ops = { + .create = ath6kl_htc_pipe_create, + .wait_target = ath6kl_htc_pipe_wait_target, + .start = ath6kl_htc_pipe_start, + .conn_service = ath6kl_htc_pipe_conn_service, + .tx = ath6kl_htc_pipe_tx, + .stop = ath6kl_htc_pipe_stop, + .cleanup = ath6kl_htc_pipe_cleanup, + .flush_txep = ath6kl_htc_pipe_flush_txep, + .flush_rx_buf = ath6kl_htc_pipe_flush_rx_buf, + .activity_changed = ath6kl_htc_pipe_activity_changed, + .get_rxbuf_num = ath6kl_htc_pipe_get_rxbuf_num, + .add_rxbuf_multiple = ath6kl_htc_pipe_add_rxbuf_multiple, + .credit_setup = ath6kl_htc_pipe_credit_setup, + .tx_complete = ath6kl_htc_pipe_tx_complete, + .rx_complete = ath6kl_htc_pipe_rx_complete, +}; + +void ath6kl_htc_pipe_attach(struct ath6kl *ar) +{ + ar->htc_ops = &ath6kl_htc_pipe_ops; +} diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c index 7f55be3092d..d5ef211f261 100644 --- a/drivers/net/wireless/ath/ath6kl/init.c +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,23 +16,21 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/moduleparam.h> #include <linux/errno.h> +#include <linux/export.h> #include <linux/of.h> #include <linux/mmc/sdio_func.h> +#include <linux/vmalloc.h> + #include "core.h" #include "cfg80211.h" #include "target.h" #include "debug.h" #include "hif-ops.h" - -unsigned int debug_mask; -static unsigned int testmode; -static bool suspend_cutpower; - -module_param(debug_mask, uint, 0644); -module_param(testmode, uint, 0644); -module_param(suspend_cutpower, bool, 0444); +#include "htc-ops.h" static const struct ath6kl_hw hw_list[] = { { @@ -43,15 +42,19 @@ static const struct ath6kl_hw hw_list[] = { .reserved_ram_size = 6912, .refclk_hz = 26000000, .uarttx_pin = 8, + .flags = ATH6KL_HW_SDIO_CRC_ERROR_WAR, /* hw2.0 needs override address hardcoded */ .app_start_override_addr = 0x944C00, - .fw_otp = AR6003_HW_2_0_OTP_FILE, - .fw = AR6003_HW_2_0_FIRMWARE_FILE, - .fw_tcmd = AR6003_HW_2_0_TCMD_FIRMWARE_FILE, - .fw_patch = AR6003_HW_2_0_PATCH_FILE, - .fw_api2 = AR6003_HW_2_0_FIRMWARE_2_FILE, + .fw = { + .dir = AR6003_HW_2_0_FW_DIR, + .otp = AR6003_HW_2_0_OTP_FILE, + .fw = AR6003_HW_2_0_FIRMWARE_FILE, + .tcmd = AR6003_HW_2_0_TCMD_FIRMWARE_FILE, + .patch = AR6003_HW_2_0_PATCH_FILE, + }, + .fw_board = AR6003_HW_2_0_BOARD_DATA_FILE, .fw_default_board = AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE, }, @@ -64,14 +67,21 @@ static const struct ath6kl_hw hw_list[] = { .reserved_ram_size = 512, .refclk_hz = 26000000, .uarttx_pin = 8, + .testscript_addr = 0x57ef74, + .flags = ATH6KL_HW_SDIO_CRC_ERROR_WAR, + + .fw = { + .dir = AR6003_HW_2_1_1_FW_DIR, + .otp = AR6003_HW_2_1_1_OTP_FILE, + .fw = AR6003_HW_2_1_1_FIRMWARE_FILE, + .tcmd = AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE, + .patch = AR6003_HW_2_1_1_PATCH_FILE, + .utf = AR6003_HW_2_1_1_UTF_FIRMWARE_FILE, + .testscript = AR6003_HW_2_1_1_TESTSCRIPT_FILE, + }, - .fw_otp = AR6003_HW_2_1_1_OTP_FILE, - .fw = AR6003_HW_2_1_1_FIRMWARE_FILE, - .fw_tcmd = AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE, - .fw_patch = AR6003_HW_2_1_1_PATCH_FILE, - .fw_api2 = AR6003_HW_2_1_1_FIRMWARE_2_FILE, .fw_board = AR6003_HW_2_1_1_BOARD_DATA_FILE, - .fw_default_board = AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE, + .fw_default_board = AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE, }, { .id = AR6004_HW_1_0_VERSION, @@ -83,9 +93,14 @@ static const struct ath6kl_hw hw_list[] = { .board_addr = 0x433900, .refclk_hz = 26000000, .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + + .fw = { + .dir = AR6004_HW_1_0_FW_DIR, + .fw = AR6004_HW_1_0_FIRMWARE_FILE, + }, - .fw = AR6004_HW_1_0_FIRMWARE_FILE, - .fw_api2 = AR6004_HW_1_0_FIRMWARE_2_FILE, .fw_board = AR6004_HW_1_0_BOARD_DATA_FILE, .fw_default_board = AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE, }, @@ -99,12 +114,58 @@ static const struct ath6kl_hw hw_list[] = { .board_addr = 0x43d400, .refclk_hz = 40000000, .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + .fw = { + .dir = AR6004_HW_1_1_FW_DIR, + .fw = AR6004_HW_1_1_FIRMWARE_FILE, + }, - .fw = AR6004_HW_1_1_FIRMWARE_FILE, - .fw_api2 = AR6004_HW_1_1_FIRMWARE_2_FILE, .fw_board = AR6004_HW_1_1_BOARD_DATA_FILE, .fw_default_board = AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE, }, + { + .id = AR6004_HW_1_2_VERSION, + .name = "ar6004 hw 1.2", + .dataset_patch_addr = 0x436ecc, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 9216, + .board_addr = 0x435c00, + .refclk_hz = 40000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + + .fw = { + .dir = AR6004_HW_1_2_FW_DIR, + .fw = AR6004_HW_1_2_FIRMWARE_FILE, + }, + .fw_board = AR6004_HW_1_2_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6004_HW_1_3_VERSION, + .name = "ar6004 hw 1.3", + .dataset_patch_addr = 0x437860, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 7168, + .board_addr = 0x436400, + .refclk_hz = 40000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS | + ATH6KL_HW_MAP_LP_ENDPOINT, + + .fw = { + .dir = AR6004_HW_1_3_FW_DIR, + .fw = AR6004_HW_1_3_FIRMWARE_FILE, + }, + + .fw_board = AR6004_HW_1_3_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE, + }, }; /* @@ -140,8 +201,8 @@ struct sk_buff *ath6kl_buf_alloc(int size) u16 reserved; /* Add chacheline space at front and back of buffer */ - reserved = (2 * L1_CACHE_BYTES) + ATH6KL_DATA_OFFSET + - sizeof(struct htc_packet) + ATH6KL_HTC_ALIGN_BYTES; + reserved = roundup((2 * L1_CACHE_BYTES) + ATH6KL_DATA_OFFSET + + sizeof(struct htc_packet) + ATH6KL_HTC_ALIGN_BYTES, 4); skb = dev_alloc_skb(size + reserved); if (skb) @@ -246,6 +307,7 @@ static int ath6kl_init_service_ep(struct ath6kl *ar) memset(&connect, 0, sizeof(connect)); /* these fields are the same for all service endpoints */ + connect.ep_cb.tx_comp_multi = ath6kl_tx_complete; connect.ep_cb.rx = ath6kl_rx; connect.ep_cb.rx_refill = ath6kl_rx_refill; connect.ep_cb.tx_full = ath6kl_tx_queue_full; @@ -299,7 +361,7 @@ static int ath6kl_init_service_ep(struct ath6kl *ar) if (ath6kl_connectservice(ar, &connect, "WMI DATA BK")) return -EIO; - /* connect to Video service, map this to to HI PRI */ + /* connect to Video service, map this to HI PRI */ connect.svc_id = WMI_DATA_VI_SVC; if (ath6kl_connectservice(ar, &connect, "WMI DATA VI")) return -EIO; @@ -342,11 +404,7 @@ static int ath6kl_set_htc_params(struct ath6kl *ar, u32 mbox_isr_yield_val, blk_size |= ((u32)htc_ctrl_buf) << 16; /* set the host interest area for the block size */ - status = ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_mbox_io_block_sz)), - (u8 *)&blk_size, - 4); + status = ath6kl_bmi_write_hi32(ar, hi_mbox_io_block_sz, blk_size); if (status) { ath6kl_err("bmi_write_memory for IO block size failed\n"); goto out; @@ -358,11 +416,8 @@ static int ath6kl_set_htc_params(struct ath6kl *ar, u32 mbox_isr_yield_val, if (mbox_isr_yield_val) { /* set the host interest area for the mbox ISR yield limit */ - status = ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_mbox_isr_yield_limit)), - (u8 *)&mbox_isr_yield_val, - 4); + status = ath6kl_bmi_write_hi32(ar, hi_mbox_isr_yield_limit, + mbox_isr_yield_val); if (status) { ath6kl_err("bmi_write_memory for yield limit failed\n"); goto out; @@ -375,7 +430,6 @@ out: static int ath6kl_target_config_wlan_params(struct ath6kl *ar, int idx) { - int status = 0; int ret; /* @@ -383,43 +437,54 @@ static int ath6kl_target_config_wlan_params(struct ath6kl *ar, int idx) * default values. Required if checksum offload is needed. Set * RxMetaVersion to 2. */ - if (ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, idx, - ar->rx_meta_ver, 0, 0)) { - ath6kl_err("unable to set the rx frame format\n"); - status = -EIO; + ret = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, idx, + ar->rx_meta_ver, 0, 0); + if (ret) { + ath6kl_err("unable to set the rx frame format: %d\n", ret); + return ret; } - if (ar->conf_flags & ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN) - if ((ath6kl_wmi_pmparams_cmd(ar->wmi, idx, 0, 1, 0, 0, 1, - IGNORE_POWER_SAVE_FAIL_EVENT_DURING_SCAN)) != 0) { - ath6kl_err("unable to set power save fail event policy\n"); - status = -EIO; + if (ar->conf_flags & ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN) { + ret = ath6kl_wmi_pmparams_cmd(ar->wmi, idx, 0, 1, 0, 0, 1, + IGNORE_PS_FAIL_DURING_SCAN); + if (ret) { + ath6kl_err("unable to set power save fail event policy: %d\n", + ret); + return ret; } + } - if (!(ar->conf_flags & ATH6KL_CONF_IGNORE_ERP_BARKER)) - if ((ath6kl_wmi_set_lpreamble_cmd(ar->wmi, idx, 0, - WMI_DONOT_IGNORE_BARKER_IN_ERP)) != 0) { - ath6kl_err("unable to set barker preamble policy\n"); - status = -EIO; + if (!(ar->conf_flags & ATH6KL_CONF_IGNORE_ERP_BARKER)) { + ret = ath6kl_wmi_set_lpreamble_cmd(ar->wmi, idx, 0, + WMI_FOLLOW_BARKER_IN_ERP); + if (ret) { + ath6kl_err("unable to set barker preamble policy: %d\n", + ret); + return ret; } + } - if (ath6kl_wmi_set_keepalive_cmd(ar->wmi, idx, - WLAN_CONFIG_KEEP_ALIVE_INTERVAL)) { - ath6kl_err("unable to set keep alive interval\n"); - status = -EIO; + ret = ath6kl_wmi_set_keepalive_cmd(ar->wmi, idx, + WLAN_CONFIG_KEEP_ALIVE_INTERVAL); + if (ret) { + ath6kl_err("unable to set keep alive interval: %d\n", ret); + return ret; } - if (ath6kl_wmi_disctimeout_cmd(ar->wmi, idx, - WLAN_CONFIG_DISCONNECT_TIMEOUT)) { - ath6kl_err("unable to set disconnect timeout\n"); - status = -EIO; + ret = ath6kl_wmi_disctimeout_cmd(ar->wmi, idx, + WLAN_CONFIG_DISCONNECT_TIMEOUT); + if (ret) { + ath6kl_err("unable to set disconnect timeout: %d\n", ret); + return ret; } - if (!(ar->conf_flags & ATH6KL_CONF_ENABLE_TX_BURST)) - if (ath6kl_wmi_set_wmm_txop(ar->wmi, idx, WMI_TXOP_DISABLED)) { - ath6kl_err("unable to set txop bursting\n"); - status = -EIO; + if (!(ar->conf_flags & ATH6KL_CONF_ENABLE_TX_BURST)) { + ret = ath6kl_wmi_set_wmm_txop(ar->wmi, idx, WMI_TXOP_DISABLED); + if (ret) { + ath6kl_err("unable to set txop bursting: %d\n", ret); + return ret; } + } if (ar->p2p && (ar->vif_max == 1 || idx)) { ret = ath6kl_wmi_info_req_cmd(ar->wmi, idx, @@ -427,9 +492,9 @@ static int ath6kl_target_config_wlan_params(struct ath6kl *ar, int idx) P2P_FLAG_MACADDR_REQ | P2P_FLAG_HMODEL_REQ); if (ret) { - ath6kl_dbg(ATH6KL_DBG_TRC, "failed to request P2P " - "capabilities (%d) - assuming P2P not " - "supported\n", ret); + ath6kl_dbg(ATH6KL_DBG_TRC, + "failed to request P2P capabilities (%d) - assuming P2P not supported\n", + ret); ar->p2p = false; } } @@ -438,12 +503,13 @@ static int ath6kl_target_config_wlan_params(struct ath6kl *ar, int idx) /* Enable Probe Request reporting for P2P */ ret = ath6kl_wmi_probe_report_req_cmd(ar->wmi, idx, true); if (ret) { - ath6kl_dbg(ATH6KL_DBG_TRC, "failed to enable Probe " - "Request reporting (%d)\n", ret); + ath6kl_dbg(ATH6KL_DBG_TRC, + "failed to enable Probe Request reporting (%d)\n", + ret); } } - return status; + return ret; } int ath6kl_configure_target(struct ath6kl *ar) @@ -452,6 +518,12 @@ int ath6kl_configure_target(struct ath6kl *ar) u8 fw_iftype, fw_mode = 0, fw_submode = 0; int i, status; + param = !!(ar->conf_flags & ATH6KL_CONF_UART_DEBUG); + if (ath6kl_bmi_write_hi32(ar, hi_serial_enable, param)) { + ath6kl_err("bmi_write_memory for uart debug failed\n"); + return -EIO; + } + /* * Note: Even though the firmware interface type is * chosen as BSS_STA for all three interfaces, can @@ -466,28 +538,34 @@ int ath6kl_configure_target(struct ath6kl *ar) fw_mode |= fw_iftype << (i * HI_OPTION_FW_MODE_BITS); /* - * By default, submodes : + * Submodes when fw does not support dynamic interface + * switching: * vif[0] - AP/STA/IBSS * vif[1] - "P2P dev"/"P2P GO"/"P2P Client" * vif[2] - "P2P dev"/"P2P GO"/"P2P Client" + * Otherwise, All the interface are initialized to p2p dev. */ - for (i = 0; i < ar->max_norm_iface; i++) - fw_submode |= HI_OPTION_FW_SUBMODE_NONE << - (i * HI_OPTION_FW_SUBMODE_BITS); + if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + for (i = 0; i < ar->vif_max; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_P2PDEV << + (i * HI_OPTION_FW_SUBMODE_BITS); + } else { + for (i = 0; i < ar->max_norm_iface; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_NONE << + (i * HI_OPTION_FW_SUBMODE_BITS); - for (i = ar->max_norm_iface; i < ar->vif_max; i++) - fw_submode |= HI_OPTION_FW_SUBMODE_P2PDEV << - (i * HI_OPTION_FW_SUBMODE_BITS); + for (i = ar->max_norm_iface; i < ar->vif_max; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_P2PDEV << + (i * HI_OPTION_FW_SUBMODE_BITS); - if (ar->p2p && ar->vif_max == 1) - fw_submode = HI_OPTION_FW_SUBMODE_P2PDEV; + if (ar->p2p && ar->vif_max == 1) + fw_submode = HI_OPTION_FW_SUBMODE_P2PDEV; + } - param = HTC_PROTOCOL_VERSION; - if (ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_app_host_interest)), - (u8 *)¶m, 4) != 0) { + if (ath6kl_bmi_write_hi32(ar, hi_app_host_interest, + HTC_PROTOCOL_VERSION) != 0) { ath6kl_err("bmi_write_memory for htc version failed\n"); return -EIO; } @@ -495,10 +573,7 @@ int ath6kl_configure_target(struct ath6kl *ar) /* set the firmware mode to STA/IBSS/AP */ param = 0; - if (ath6kl_bmi_read(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_option_flag)), - (u8 *)¶m, 4) != 0) { + if (ath6kl_bmi_read_hi32(ar, hi_option_flag, ¶m) != 0) { ath6kl_err("bmi_read_memory for setting fwmode failed\n"); return -EIO; } @@ -510,11 +585,7 @@ int ath6kl_configure_target(struct ath6kl *ar) param |= (0 << HI_OPTION_MAC_ADDR_METHOD_SHIFT); param |= (0 << HI_OPTION_FW_BRIDGE_SHIFT); - if (ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_option_flag)), - (u8 *)¶m, - 4) != 0) { + if (ath6kl_bmi_write_hi32(ar, hi_option_flag, param) != 0) { ath6kl_err("bmi_write_memory for setting fwmode failed\n"); return -EIO; } @@ -530,21 +601,20 @@ int ath6kl_configure_target(struct ath6kl *ar) * but possible in theory. */ - param = ar->hw.board_ext_data_addr; - ram_reserved_size = ar->hw.reserved_ram_size; + if (ar->target_type == TARGET_TYPE_AR6003) { + param = ar->hw.board_ext_data_addr; + ram_reserved_size = ar->hw.reserved_ram_size; - if (ath6kl_bmi_write(ar, ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_ext_data)), - (u8 *)¶m, 4) != 0) { - ath6kl_err("bmi_write_memory for hi_board_ext_data failed\n"); - return -EIO; - } + if (ath6kl_bmi_write_hi32(ar, hi_board_ext_data, param) != 0) { + ath6kl_err("bmi_write_memory for hi_board_ext_data failed\n"); + return -EIO; + } - if (ath6kl_bmi_write(ar, ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_end_ram_reserve_sz)), - (u8 *)&ram_reserved_size, 4) != 0) { - ath6kl_err("bmi_write_memory for hi_end_ram_reserve_sz failed\n"); - return -EIO; + if (ath6kl_bmi_write_hi32(ar, hi_end_ram_reserve_sz, + ram_reserved_size) != 0) { + ath6kl_err("bmi_write_memory for hi_end_ram_reserve_sz failed\n"); + return -EIO; + } } /* set the block size for the target */ @@ -553,56 +623,19 @@ int ath6kl_configure_target(struct ath6kl *ar) return -EIO; /* Configure GPIO AR600x UART */ - param = ar->hw.uarttx_pin; - status = ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_dbg_uart_txpin)), - (u8 *)¶m, 4); + status = ath6kl_bmi_write_hi32(ar, hi_dbg_uart_txpin, + ar->hw.uarttx_pin); if (status) return status; /* Configure target refclk_hz */ - param = ar->hw.refclk_hz; - status = ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_refclk_hz)), - (u8 *)¶m, 4); + status = ath6kl_bmi_write_hi32(ar, hi_refclk_hz, ar->hw.refclk_hz); if (status) return status; return 0; } -void ath6kl_core_free(struct ath6kl *ar) -{ - wiphy_free(ar->wiphy); -} - -void ath6kl_core_cleanup(struct ath6kl *ar) -{ - ath6kl_hif_power_off(ar); - - destroy_workqueue(ar->ath6kl_wq); - - if (ar->htc_target) - ath6kl_htc_cleanup(ar->htc_target); - - ath6kl_cookie_cleanup(ar); - - ath6kl_cleanup_amsdu_rxbufs(ar); - - ath6kl_bmi_cleanup(ar); - - ath6kl_debug_cleanup(ar); - - kfree(ar->fw_board); - kfree(ar->fw_otp); - kfree(ar->fw); - kfree(ar->fw_patch); - - ath6kl_deinit_ieee80211_hw(ar); -} - /* firmware upload */ static int ath6kl_get_fw(struct ath6kl *ar, const char *filename, u8 **fw, size_t *fw_len) @@ -626,21 +659,6 @@ static int ath6kl_get_fw(struct ath6kl *ar, const char *filename, } #ifdef CONFIG_OF -static const char *get_target_ver_dir(const struct ath6kl *ar) -{ - switch (ar->version.target_ver) { - case AR6003_HW_1_0_VERSION: - return "ath6k/AR6003/hw1.0"; - case AR6003_HW_2_0_VERSION: - return "ath6k/AR6003/hw2.0"; - case AR6003_HW_2_1_1_VERSION: - return "ath6k/AR6003/hw2.1.1"; - } - ath6kl_warn("%s: unsupported target version 0x%x.\n", __func__, - ar->version.target_ver); - return NULL; -} - /* * Check the device tree for a board-id and use it to construct * the pathname to the firmware file. Used (for now) to find a @@ -663,7 +681,7 @@ static bool check_device_tree(struct ath6kl *ar) continue; } snprintf(board_filename, sizeof(board_filename), - "%s/bdata.%s.bin", get_target_ver_dir(ar), board_id); + "%s/bdata.%s.bin", ar->hw.fw.dir, board_id); ret = ath6kl_get_fw(ar, board_filename, &ar->fw_board, &ar->fw_board_len); @@ -730,19 +748,20 @@ static int ath6kl_fetch_board_file(struct ath6kl *ar) static int ath6kl_fetch_otp_file(struct ath6kl *ar) { - const char *filename; + char filename[100]; int ret; if (ar->fw_otp != NULL) return 0; - if (ar->hw.fw_otp == NULL) { + if (ar->hw.fw.otp == NULL) { ath6kl_dbg(ATH6KL_DBG_BOOT, "no OTP file configured for this hw\n"); return 0; } - filename = ar->hw.fw_otp; + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.otp); ret = ath6kl_get_fw(ar, filename, &ar->fw_otp, &ar->fw_otp_len); @@ -755,33 +774,61 @@ static int ath6kl_fetch_otp_file(struct ath6kl *ar) return 0; } -static int ath6kl_fetch_fw_file(struct ath6kl *ar) +static int ath6kl_fetch_testmode_file(struct ath6kl *ar) { - const char *filename; + char filename[100]; int ret; - if (ar->fw != NULL) + if (ar->testmode == 0) return 0; - if (testmode) { - if (ar->hw.fw_tcmd == NULL) { - ath6kl_warn("testmode not supported\n"); + ath6kl_dbg(ATH6KL_DBG_BOOT, "testmode %d\n", ar->testmode); + + if (ar->testmode == 2) { + if (ar->hw.fw.utf == NULL) { + ath6kl_warn("testmode 2 not supported\n"); return -EOPNOTSUPP; } - filename = ar->hw.fw_tcmd; + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.utf); + } else { + if (ar->hw.fw.tcmd == NULL) { + ath6kl_warn("testmode 1 not supported\n"); + return -EOPNOTSUPP; + } - set_bit(TESTMODE, &ar->flag); + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.tcmd); + } - goto get_fw; + set_bit(TESTMODE, &ar->flag); + + ret = ath6kl_get_fw(ar, filename, &ar->fw, &ar->fw_len); + if (ret) { + ath6kl_err("Failed to get testmode %d firmware file %s: %d\n", + ar->testmode, filename, ret); + return ret; } - if (WARN_ON(ar->hw.fw == NULL)) + return 0; +} + +static int ath6kl_fetch_fw_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->fw != NULL) + return 0; + + /* FIXME: remove WARN_ON() as we won't support FW API 1 for long */ + if (WARN_ON(ar->hw.fw.fw == NULL)) return -EINVAL; - filename = ar->hw.fw; + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.fw); -get_fw: ret = ath6kl_get_fw(ar, filename, &ar->fw, &ar->fw_len); if (ret) { ath6kl_err("Failed to get firmware file %s: %d\n", @@ -794,16 +841,17 @@ get_fw: static int ath6kl_fetch_patch_file(struct ath6kl *ar) { - const char *filename; + char filename[100]; int ret; if (ar->fw_patch != NULL) return 0; - if (ar->hw.fw_patch == NULL) + if (ar->hw.fw.patch == NULL) return 0; - filename = ar->hw.fw_patch; + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.patch); ret = ath6kl_get_fw(ar, filename, &ar->fw_patch, &ar->fw_patch_len); @@ -816,6 +864,34 @@ static int ath6kl_fetch_patch_file(struct ath6kl *ar) return 0; } +static int ath6kl_fetch_testscript_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->testmode != 2) + return 0; + + if (ar->fw_testscript != NULL) + return 0; + + if (ar->hw.fw.testscript == NULL) + return 0; + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.testscript); + + ret = ath6kl_get_fw(ar, filename, &ar->fw_testscript, + &ar->fw_testscript_len); + if (ret) { + ath6kl_err("Failed to get testscript file %s: %d\n", + filename, ret); + return ret; + } + + return 0; +} + static int ath6kl_fetch_fw_api1(struct ath6kl *ar) { int ret; @@ -832,23 +908,24 @@ static int ath6kl_fetch_fw_api1(struct ath6kl *ar) if (ret) return ret; + ret = ath6kl_fetch_testscript_file(ar); + if (ret) + return ret; + return 0; } -static int ath6kl_fetch_fw_api2(struct ath6kl *ar) +static int ath6kl_fetch_fw_apin(struct ath6kl *ar, const char *name) { size_t magic_len, len, ie_len; const struct firmware *fw; struct ath6kl_fw_ie *hdr; - const char *filename; + char filename[100]; const u8 *data; int ret, ie_id, i, index, bit; __le32 *val; - if (ar->hw.fw_api2 == NULL) - return -EOPNOTSUPP; - - filename = ar->hw.fw_api2; + snprintf(filename, sizeof(filename), "%s/%s", ar->hw.fw.dir, name); ret = request_firmware(&fw, filename, ar->dev); if (ret) @@ -890,9 +967,17 @@ static int ath6kl_fetch_fw_api2(struct ath6kl *ar) } switch (ie_id) { + case ATH6KL_FW_IE_FW_VERSION: + strlcpy(ar->wiphy->fw_version, data, + sizeof(ar->wiphy->fw_version)); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found fw version %s\n", + ar->wiphy->fw_version); + break; case ATH6KL_FW_IE_OTP_IMAGE: ath6kl_dbg(ATH6KL_DBG_BOOT, "found otp image ie (%zd B)\n", - ie_len); + ie_len); ar->fw_otp = kmemdup(data, ie_len, GFP_KERNEL); @@ -905,20 +990,25 @@ static int ath6kl_fetch_fw_api2(struct ath6kl *ar) break; case ATH6KL_FW_IE_FW_IMAGE: ath6kl_dbg(ATH6KL_DBG_BOOT, "found fw image ie (%zd B)\n", - ie_len); + ie_len); - ar->fw = kmemdup(data, ie_len, GFP_KERNEL); + /* in testmode we already might have a fw file */ + if (ar->fw != NULL) + break; + + ar->fw = vmalloc(ie_len); if (ar->fw == NULL) { ret = -ENOMEM; goto out; } + memcpy(ar->fw, data, ie_len); ar->fw_len = ie_len; break; case ATH6KL_FW_IE_PATCH_IMAGE: ath6kl_dbg(ATH6KL_DBG_BOOT, "found patch image ie (%zd B)\n", - ie_len); + ie_len); ar->fw_patch = kmemdup(data, ie_len, GFP_KERNEL); @@ -938,9 +1028,6 @@ static int ath6kl_fetch_fw_api2(struct ath6kl *ar) ar->hw.reserved_ram_size); break; case ATH6KL_FW_IE_CAPABILITIES: - if (ie_len < DIV_ROUND_UP(ATH6KL_FW_CAPABILITY_MAX, 8)) - break; - ath6kl_dbg(ATH6KL_DBG_BOOT, "found firmware capabilities ie (%zd B)\n", ie_len); @@ -949,6 +1036,9 @@ static int ath6kl_fetch_fw_api2(struct ath6kl *ar) index = i / 8; bit = i % 8; + if (index == ie_len) + break; + if (data[index] & (1 << bit)) __set_bit(i, ar->fw_capabilities); } @@ -1010,7 +1100,7 @@ out: return ret; } -static int ath6kl_fetch_firmwares(struct ath6kl *ar) +int ath6kl_init_fetch_firmwares(struct ath6kl *ar) { int ret; @@ -1018,17 +1108,36 @@ static int ath6kl_fetch_firmwares(struct ath6kl *ar) if (ret) return ret; - ret = ath6kl_fetch_fw_api2(ar); + ret = ath6kl_fetch_testmode_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API4_FILE); if (ret == 0) { - ath6kl_dbg(ATH6KL_DBG_BOOT, "using fw api 2\n"); - return 0; + ar->fw_api = 4; + goto out; + } + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API3_FILE); + if (ret == 0) { + ar->fw_api = 3; + goto out; + } + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API2_FILE); + if (ret == 0) { + ar->fw_api = 2; + goto out; } ret = ath6kl_fetch_fw_api1(ar); if (ret) return ret; - ath6kl_dbg(ATH6KL_DBG_BOOT, "using fw api 1\n"); + ar->fw_api = 1; + +out: + ath6kl_dbg(ATH6KL_DBG_BOOT, "using fw api %d\n", ar->fw_api); return 0; } @@ -1049,22 +1158,14 @@ static int ath6kl_upload_board_file(struct ath6kl *ar) */ if (ar->hw.board_addr != 0) { board_address = ar->hw.board_addr; - ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_data)), - (u8 *) &board_address, 4); + ath6kl_bmi_write_hi32(ar, hi_board_data, + board_address); } else { - ath6kl_bmi_read(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_data)), - (u8 *) &board_address, 4); + ath6kl_bmi_read_hi32(ar, hi_board_data, &board_address); } /* determine where in target ram to write extended board data */ - ath6kl_bmi_read(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_ext_data)), - (u8 *) &board_ext_address, 4); + ath6kl_bmi_read_hi32(ar, hi_board_ext_data, &board_ext_address); if (ar->target_type == TARGET_TYPE_AR6003 && board_ext_address == 0) { @@ -1076,6 +1177,8 @@ static int ath6kl_upload_board_file(struct ath6kl *ar) case TARGET_TYPE_AR6003: board_data_size = AR6003_BOARD_DATA_SZ; board_ext_data_size = AR6003_BOARD_EXT_DATA_SZ; + if (ar->fw_board_len > (board_data_size + board_ext_data_size)) + board_ext_data_size = AR6003_BOARD_EXT_DATA_SZ_V2; break; case TARGET_TYPE_AR6004: board_data_size = AR6004_BOARD_DATA_SZ; @@ -1089,7 +1192,6 @@ static int ath6kl_upload_board_file(struct ath6kl *ar) if (board_ext_address && ar->fw_board_len == (board_data_size + board_ext_data_size)) { - /* write extended board data */ ath6kl_dbg(ATH6KL_DBG_BOOT, "writing extended board data to 0x%x (%d B)\n", @@ -1107,10 +1209,7 @@ static int ath6kl_upload_board_file(struct ath6kl *ar) /* record that extended board data is initialized */ param = (board_ext_data_size << 16) | 1; - ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_ext_data_config)), - (unsigned char *) ¶m, 4); + ath6kl_bmi_write_hi32(ar, hi_board_ext_data_config, param); } if (ar->fw_board_len < board_data_size) { @@ -1131,11 +1230,7 @@ static int ath6kl_upload_board_file(struct ath6kl *ar) } /* record the fact that Board Data IS initialized */ - param = 1; - ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_board_data_initialized)), - (u8 *)¶m, 4); + ath6kl_bmi_write_hi32(ar, hi_board_data_initialized, 1); return ret; } @@ -1162,10 +1257,7 @@ static int ath6kl_upload_otp(struct ath6kl *ar) } /* read firmware start address */ - ret = ath6kl_bmi_read(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_app_start)), - (u8 *) &address, sizeof(address)); + ret = ath6kl_bmi_read_hi32(ar, hi_app_start, &address); if (ret) { ath6kl_err("Failed to read hi_app_start: %d\n", ret); @@ -1223,7 +1315,7 @@ static int ath6kl_upload_firmware(struct ath6kl *ar) static int ath6kl_upload_patch(struct ath6kl *ar) { - u32 address, param; + u32 address; int ret; if (ar->fw_patch == NULL) @@ -1240,11 +1332,37 @@ static int ath6kl_upload_patch(struct ath6kl *ar) return ret; } - param = address; - ath6kl_bmi_write(ar, - ath6kl_get_hi_item_addr(ar, - HI_ITEM(hi_dset_list_head)), - (unsigned char *) ¶m, 4); + ath6kl_bmi_write_hi32(ar, hi_dset_list_head, address); + + return 0; +} + +static int ath6kl_upload_testscript(struct ath6kl *ar) +{ + u32 address; + int ret; + + if (ar->testmode != 2) + return 0; + + if (ar->fw_testscript == NULL) + return 0; + + address = ar->hw.testscript_addr; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing testscript to 0x%x (%zd B)\n", + address, ar->fw_testscript_len); + + ret = ath6kl_bmi_write(ar, address, ar->fw_testscript, + ar->fw_testscript_len); + if (ret) { + ath6kl_err("Failed to write testscript file: %d\n", ret); + return ret; + } + + ath6kl_bmi_write_hi32(ar, hi_ota_testscript, address); + ath6kl_bmi_write_hi32(ar, hi_end_ram_reserve_sz, 4096); + ath6kl_bmi_write_hi32(ar, hi_test_apps_related, 1); return 0; } @@ -1255,7 +1373,7 @@ static int ath6kl_init_upload(struct ath6kl *ar) int status = 0; if (ar->target_type != TARGET_TYPE_AR6003 && - ar->target_type != TARGET_TYPE_AR6004) + ar->target_type != TARGET_TYPE_AR6004) return -EINVAL; /* temporarily disable system sleep */ @@ -1312,9 +1430,15 @@ static int ath6kl_init_upload(struct ath6kl *ar) return status; /* WAR to avoid SDIO CRC err */ - if (ar->version.target_ver == AR6003_HW_2_0_VERSION) { + if (ar->hw.flags & ATH6KL_HW_SDIO_CRC_ERROR_WAR) { ath6kl_err("temporary war to avoid sdio crc error\n"); + param = 0x28; + address = GPIO_BASE_ADDRESS + GPIO_PIN9_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + param = 0x20; address = GPIO_BASE_ADDRESS + GPIO_PIN10_ADDRESS; @@ -1357,6 +1481,11 @@ static int ath6kl_init_upload(struct ath6kl *ar) if (status) return status; + /* Download the test script */ + status = ath6kl_upload_testscript(ar); + if (status) + return status; + /* Restore system sleep */ address = RTC_BASE_ADDRESS + SYSTEM_SLEEP_ADDRESS; status = ath6kl_bmi_reg_write(ar, address, sleep); @@ -1372,9 +1501,9 @@ static int ath6kl_init_upload(struct ath6kl *ar) return status; } -static int ath6kl_init_hw_params(struct ath6kl *ar) +int ath6kl_init_hw_params(struct ath6kl *ar) { - const struct ath6kl_hw *hw; + const struct ath6kl_hw *uninitialized_var(hw); int i; for (i = 0; i < ARRAY_SIZE(hw_list); i++) { @@ -1419,10 +1548,89 @@ static const char *ath6kl_init_get_hif_name(enum ath6kl_hif_type type) return NULL; } -int ath6kl_init_hw_start(struct ath6kl *ar) + +static const struct fw_capa_str_map { + int id; + const char *name; +} fw_capa_map[] = { + { ATH6KL_FW_CAPABILITY_HOST_P2P, "host-p2p" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN, "sched-scan" }, + { ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, "sta-p2pdev-duplex" }, + { ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, "inactivity-timeout" }, + { ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, "rsn-cap-override" }, + { ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, "wow-mc-filter" }, + { ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, "bmiss-enhance" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, "sscan-match-list" }, + { ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, "rssi-scan-thold" }, + { ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, "custom-mac-addr" }, + { ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, "tx-err-notify" }, + { ATH6KL_FW_CAPABILITY_REGDOMAIN, "regdomain" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, "sched-scan-v2" }, + { ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, "hb-poll" }, +}; + +static const char *ath6kl_init_get_fw_capa_name(unsigned int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fw_capa_map); i++) { + if (fw_capa_map[i].id == id) + return fw_capa_map[i].name; + } + + return "<unknown>"; +} + +static void ath6kl_init_get_fwcaps(struct ath6kl *ar, char *buf, size_t buf_len) +{ + u8 *data = (u8 *) ar->fw_capabilities; + size_t trunc_len, len = 0; + int i, index, bit; + char *trunc = "..."; + + for (i = 0; i < ATH6KL_FW_CAPABILITY_MAX; i++) { + index = i / 8; + bit = i % 8; + + if (index >= sizeof(ar->fw_capabilities) * 4) + break; + + if (buf_len - len < 4) { + ath6kl_warn("firmware capability buffer too small!\n"); + + /* add "..." to the end of string */ + trunc_len = strlen(trunc) + 1; + strncpy(buf + buf_len - trunc_len, trunc, trunc_len); + + return; + } + + if (data[index] & (1 << bit)) { + len += scnprintf(buf + len, buf_len - len, "%s,", + ath6kl_init_get_fw_capa_name(i)); + } + } + + /* overwrite the last comma */ + if (len > 0) + len--; + + buf[len] = '\0'; +} + +static int ath6kl_init_hw_reset(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_BOOT, "cold resetting the device"); + + return ath6kl_diag_write32(ar, RESET_CONTROL_ADDRESS, + cpu_to_le32(RESET_CONTROL_COLD_RST)); +} + +static int __ath6kl_init_hw_start(struct ath6kl *ar) { long timeleft; int ret, i; + char buf[200]; ath6kl_dbg(ATH6KL_DBG_BOOT, "hw start\n"); @@ -1439,29 +1647,40 @@ int ath6kl_init_hw_start(struct ath6kl *ar) goto err_power_off; /* Do we need to finish the BMI phase */ - /* FIXME: return error from ath6kl_bmi_done() */ - if (ath6kl_bmi_done(ar)) { - ret = -EIO; + ret = ath6kl_bmi_done(ar); + if (ret) goto err_power_off; - } /* * The reason we have to wait for the target here is that the * driver layer has to init BMI in order to set the host block * size. */ - if (ath6kl_htc_wait_target(ar->htc_target)) { - ret = -EIO; + ret = ath6kl_htc_wait_target(ar->htc_target); + + if (ret == -ETIMEDOUT) { + /* + * Most likely USB target is in odd state after reboot and + * needs a reset. A cold reset makes the whole device + * disappear from USB bus and initialisation starts from + * beginning. + */ + ath6kl_warn("htc wait target timed out, resetting device\n"); + ath6kl_init_hw_reset(ar); + goto err_power_off; + } else if (ret) { + ath6kl_err("htc wait target failed: %d\n", ret); goto err_power_off; } - if (ath6kl_init_service_ep(ar)) { - ret = -EIO; + ret = ath6kl_init_service_ep(ar); + if (ret) { + ath6kl_err("Endpoint service initilisation failed: %d\n", ret); goto err_cleanup_scatter; } /* setup credit distribution */ - ath6kl_credit_setup(ar->htc_target, &ar->credit_state_info); + ath6kl_htc_credit_setup(ar->htc_target, &ar->credit_state_info); /* start HTC */ ret = ath6kl_htc_start(ar->htc_target); @@ -1476,16 +1695,25 @@ int ath6kl_init_hw_start(struct ath6kl *ar) test_bit(WMI_READY, &ar->flag), WMI_TIMEOUT); + if (timeleft <= 0) { + clear_bit(WMI_READY, &ar->flag); + ath6kl_err("wmi is not ready or wait was interrupted: %ld\n", + timeleft); + ret = -EIO; + goto err_htc_stop; + } ath6kl_dbg(ATH6KL_DBG_BOOT, "firmware booted\n"); - if (test_and_clear_bit(FIRST_BOOT, &ar->flag)) { - ath6kl_info("%s %s fw %s%s\n", + ath6kl_info("%s %s fw %s api %d%s\n", ar->hw.name, ath6kl_init_get_hif_name(ar->hif_type), ar->wiphy->fw_version, + ar->fw_api, test_bit(TESTMODE, &ar->flag) ? " testmode" : ""); + ath6kl_init_get_fwcaps(ar, buf, sizeof(buf)); + ath6kl_info("firmware supports: %s\n", buf); } if (ar->version.abi_ver != ATH6KL_ABI_VERSION) { @@ -1495,12 +1723,6 @@ int ath6kl_init_hw_start(struct ath6kl *ar) goto err_htc_stop; } - if (!timeleft || signal_pending(current)) { - ath6kl_err("wmi is not ready or wait was interrupted\n"); - ret = -EIO; - goto err_htc_stop; - } - ath6kl_dbg(ATH6KL_DBG_TRC, "%s: wmi is ready\n", __func__); /* communicate the wmi protocol verision to the target */ @@ -1514,8 +1736,6 @@ int ath6kl_init_hw_start(struct ath6kl *ar) goto err_htc_stop; } - ar->state = ATH6KL_STATE_ON; - return 0; err_htc_stop: @@ -1528,7 +1748,18 @@ err_power_off: return ret; } -int ath6kl_init_hw_stop(struct ath6kl *ar) +int ath6kl_init_hw_start(struct ath6kl *ar) +{ + int err; + + err = __ath6kl_init_hw_start(ar); + if (err) + return err; + ar->state = ATH6KL_STATE_ON; + return 0; +} + +static int __ath6kl_init_hw_stop(struct ath6kl *ar) { int ret; @@ -1544,209 +1775,41 @@ int ath6kl_init_hw_stop(struct ath6kl *ar) if (ret) ath6kl_warn("failed to power off hif: %d\n", ret); - ar->state = ATH6KL_STATE_OFF; - return 0; } -int ath6kl_core_init(struct ath6kl *ar) +int ath6kl_init_hw_stop(struct ath6kl *ar) { - struct ath6kl_bmi_target_info targ_info; - struct net_device *ndev; - int ret = 0, i; - - ar->ath6kl_wq = create_singlethread_workqueue("ath6kl"); - if (!ar->ath6kl_wq) - return -ENOMEM; + int err; - ret = ath6kl_bmi_init(ar); - if (ret) - goto err_wq; - - /* - * Turn on power to get hardware (target) version and leave power - * on delibrately as we will boot the hardware anyway within few - * seconds. - */ - ret = ath6kl_hif_power_on(ar); - if (ret) - goto err_bmi_cleanup; - - ret = ath6kl_bmi_get_target_info(ar, &targ_info); - if (ret) - goto err_power_off; - - ar->version.target_ver = le32_to_cpu(targ_info.version); - ar->target_type = le32_to_cpu(targ_info.type); - ar->wiphy->hw_version = le32_to_cpu(targ_info.version); - - ret = ath6kl_init_hw_params(ar); - if (ret) - goto err_power_off; - - ar->htc_target = ath6kl_htc_create(ar); - - if (!ar->htc_target) { - ret = -ENOMEM; - goto err_power_off; - } - - ret = ath6kl_fetch_firmwares(ar); - if (ret) - goto err_htc_cleanup; - - /* FIXME: we should free all firmwares in the error cases below */ - - /* Indicate that WMI is enabled (although not ready yet) */ - set_bit(WMI_ENABLED, &ar->flag); - ar->wmi = ath6kl_wmi_init(ar); - if (!ar->wmi) { - ath6kl_err("failed to initialize wmi\n"); - ret = -EIO; - goto err_htc_cleanup; - } - - ath6kl_dbg(ATH6KL_DBG_TRC, "%s: got wmi @ 0x%p.\n", __func__, ar->wmi); - - ret = ath6kl_register_ieee80211_hw(ar); - if (ret) - goto err_node_cleanup; - - ret = ath6kl_debug_init(ar); - if (ret) { - wiphy_unregister(ar->wiphy); - goto err_node_cleanup; - } - - for (i = 0; i < ar->vif_max; i++) - ar->avail_idx_map |= BIT(i); - - rtnl_lock(); - - /* Add an initial station interface */ - ndev = ath6kl_interface_add(ar, "wlan%d", NL80211_IFTYPE_STATION, 0, - INFRA_NETWORK); - - rtnl_unlock(); - - if (!ndev) { - ath6kl_err("Failed to instantiate a network device\n"); - ret = -ENOMEM; - wiphy_unregister(ar->wiphy); - goto err_debug_init; - } - - - ath6kl_dbg(ATH6KL_DBG_TRC, "%s: name=%s dev=0x%p, ar=0x%p\n", - __func__, ndev->name, ndev, ar); - - /* setup access class priority mappings */ - ar->ac_stream_pri_map[WMM_AC_BK] = 0; /* lowest */ - ar->ac_stream_pri_map[WMM_AC_BE] = 1; - ar->ac_stream_pri_map[WMM_AC_VI] = 2; - ar->ac_stream_pri_map[WMM_AC_VO] = 3; /* highest */ - - /* give our connected endpoints some buffers */ - ath6kl_rx_refill(ar->htc_target, ar->ctrl_ep); - ath6kl_rx_refill(ar->htc_target, ar->ac2ep_map[WMM_AC_BE]); - - /* allocate some buffers that handle larger AMSDU frames */ - ath6kl_refill_amsdu_rxbufs(ar, ATH6KL_MAX_AMSDU_RX_BUFFERS); - - ath6kl_cookie_init(ar); - - ar->conf_flags = ATH6KL_CONF_IGNORE_ERP_BARKER | - ATH6KL_CONF_ENABLE_11N | ATH6KL_CONF_ENABLE_TX_BURST; - - if (suspend_cutpower) - ar->conf_flags |= ATH6KL_CONF_SUSPEND_CUTPOWER; - - ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM | - WIPHY_FLAG_HAVE_AP_SME | - WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | - WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; - - if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN, ar->fw_capabilities)) - ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; - - ar->wiphy->probe_resp_offload = - NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | - NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | - NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P | - NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U; - - set_bit(FIRST_BOOT, &ar->flag); - - ret = ath6kl_init_hw_start(ar); - if (ret) { - ath6kl_err("Failed to start hardware: %d\n", ret); - goto err_rxbuf_cleanup; - } - - /* - * Set mac address which is received in ready event - * FIXME: Move to ath6kl_interface_add() - */ - memcpy(ndev->dev_addr, ar->mac_addr, ETH_ALEN); - - return ret; - -err_rxbuf_cleanup: - ath6kl_htc_flush_rx_buf(ar->htc_target); - ath6kl_cleanup_amsdu_rxbufs(ar); - rtnl_lock(); - ath6kl_deinit_if_data(netdev_priv(ndev)); - rtnl_unlock(); - wiphy_unregister(ar->wiphy); -err_debug_init: - ath6kl_debug_cleanup(ar); -err_node_cleanup: - ath6kl_wmi_shutdown(ar->wmi); - clear_bit(WMI_ENABLED, &ar->flag); - ar->wmi = NULL; -err_htc_cleanup: - ath6kl_htc_cleanup(ar->htc_target); -err_power_off: - ath6kl_hif_power_off(ar); -err_bmi_cleanup: - ath6kl_bmi_cleanup(ar); -err_wq: - destroy_workqueue(ar->ath6kl_wq); - - return ret; + err = __ath6kl_init_hw_stop(ar); + if (err) + return err; + ar->state = ATH6KL_STATE_OFF; + return 0; } -void ath6kl_cleanup_vif(struct ath6kl_vif *vif, bool wmi_ready) +void ath6kl_init_hw_restart(struct ath6kl *ar) { - static u8 bcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - bool discon_issued; - - netif_stop_queue(vif->ndev); - - clear_bit(WLAN_ENABLED, &vif->flags); + clear_bit(WMI_READY, &ar->flag); - if (wmi_ready) { - discon_issued = test_bit(CONNECTED, &vif->flags) || - test_bit(CONNECT_PEND, &vif->flags); - ath6kl_disconnect(vif); - del_timer(&vif->disconnect_timer); + ath6kl_cfg80211_stop_all(ar); - if (discon_issued) - ath6kl_disconnect_event(vif, DISCONNECT_CMD, - (vif->nw_type & AP_NETWORK) ? - bcast_mac : vif->bssid, - 0, NULL, 0); + if (__ath6kl_init_hw_stop(ar)) { + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Failed to stop during fw error recovery\n"); + return; } - if (vif->scan_req) { - cfg80211_scan_done(vif->scan_req, true); - vif->scan_req = NULL; + if (__ath6kl_init_hw_start(ar)) { + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Failed to restart during fw error recovery\n"); + return; } } void ath6kl_stop_txrx(struct ath6kl *ar) { struct ath6kl_vif *vif, *tmp_vif; + int i; set_bit(DESTROY_IN_PROGRESS, &ar->flag); @@ -1755,13 +1818,16 @@ void ath6kl_stop_txrx(struct ath6kl *ar) return; } + for (i = 0; i < AP_MAX_NUM_STA; i++) + aggr_reset_state(ar->sta_list[i].aggr_conn); + spin_lock_bh(&ar->list_lock); list_for_each_entry_safe(vif, tmp_vif, &ar->vif_list, list) { list_del(&vif->list); spin_unlock_bh(&ar->list_lock); - ath6kl_cleanup_vif(vif, test_bit(WMI_READY, &ar->flag)); + ath6kl_cfg80211_vif_stop(vif, test_bit(WMI_READY, &ar->flag)); rtnl_lock(); - ath6kl_deinit_if_data(vif); + ath6kl_cfg80211_vif_cleanup(vif); rtnl_unlock(); spin_lock_bh(&ar->list_lock); } @@ -1769,6 +1835,9 @@ void ath6kl_stop_txrx(struct ath6kl *ar) clear_bit(WMI_READY, &ar->flag); + if (ar->fw_recovery.enable) + del_timer_sync(&ar->fw_recovery.hb_timer); + /* * After wmi_shudown all WMI events will be dropped. We * need to cleanup the buffers allocated in AP mode and @@ -1790,9 +1859,8 @@ void ath6kl_stop_txrx(struct ath6kl *ar) * Try to reset the device if we can. The driver may have been * configure NOT to reset the target during a debug session. */ - ath6kl_dbg(ATH6KL_DBG_TRC, - "attempting to reset target on instance destroy\n"); - ath6kl_reset_device(ar, ar->target_type, true, true); + ath6kl_init_hw_reset(ar); - clear_bit(WLAN_ENABLED, &ar->flag); + up(&ar->sem); } +EXPORT_SYMBOL(ath6kl_stop_txrx); diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c index eea3c747653..d56554674da 100644 --- a/drivers/net/wireless/ath/ath6kl/main.c +++ b/drivers/net/wireless/ath/ath6kl/main.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,6 +15,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include "core.h" #include "hif-ops.h" #include "cfg80211.h" @@ -26,6 +29,9 @@ struct ath6kl_sta *ath6kl_find_sta(struct ath6kl_vif *vif, u8 *node_addr) struct ath6kl_sta *conn = NULL; u8 i, max_conn; + if (is_zero_ether_addr(node_addr)) + return NULL; + max_conn = (vif->nw_type == AP_NETWORK) ? AP_MAX_NUM_STA : 0; for (i = 0; i < max_conn; i++) { @@ -52,9 +58,11 @@ struct ath6kl_sta *ath6kl_find_sta_by_aid(struct ath6kl *ar, u8 aid) return conn; } -static void ath6kl_add_new_sta(struct ath6kl *ar, u8 *mac, u16 aid, u8 *wpaie, - u8 ielen, u8 keymgmt, u8 ucipher, u8 auth) +static void ath6kl_add_new_sta(struct ath6kl_vif *vif, u8 *mac, u16 aid, + u8 *wpaie, size_t ielen, u8 keymgmt, + u8 ucipher, u8 auth, u8 apsd_info) { + struct ath6kl *ar = vif->ar; struct ath6kl_sta *sta; u8 free_slot; @@ -68,18 +76,31 @@ static void ath6kl_add_new_sta(struct ath6kl *ar, u8 *mac, u16 aid, u8 *wpaie, sta->keymgmt = keymgmt; sta->ucipher = ucipher; sta->auth = auth; + sta->apsd_info = apsd_info; ar->sta_list_index = ar->sta_list_index | (1 << free_slot); ar->ap_stats.sta[free_slot].aid = cpu_to_le32(aid); + aggr_conn_init(vif, vif->aggr_cntxt, sta->aggr_conn); } static void ath6kl_sta_cleanup(struct ath6kl *ar, u8 i) { struct ath6kl_sta *sta = &ar->sta_list[i]; + struct ath6kl_mgmt_buff *entry, *tmp; /* empty the queued pkts in the PS queue if any */ spin_lock_bh(&sta->psq_lock); skb_queue_purge(&sta->psq); + skb_queue_purge(&sta->apsdq); + + if (sta->mgmt_psq_len != 0) { + list_for_each_entry_safe(entry, tmp, &sta->mgmt_psq, list) { + kfree(entry); + } + INIT_LIST_HEAD(&sta->mgmt_psq); + sta->mgmt_psq_len = 0; + } + spin_unlock_bh(&sta->psq_lock); memset(&ar->ap_stats.sta[sta->aid - 1], 0, @@ -90,7 +111,7 @@ static void ath6kl_sta_cleanup(struct ath6kl *ar, u8 i) sta->sta_flags = 0; ar->sta_list_index = ar->sta_list_index & ~(1 << i); - + aggr_reset_state(sta->aggr_conn); } static u8 ath6kl_remove_sta(struct ath6kl *ar, u8 *mac, u16 reason) @@ -252,7 +273,7 @@ int ath6kl_read_fwlogs(struct ath6kl *ar) struct ath6kl_dbglog_hdr debug_hdr; struct ath6kl_dbglog_buf debug_buf; u32 address, length, dropped, firstbuf, debug_hdr_addr; - int ret = 0, loop; + int ret, loop; u8 *buf; buf = kmalloc(ATH6KL_FWLOG_PAYLOAD_SIZE, GFP_KERNEL); @@ -275,13 +296,17 @@ int ath6kl_read_fwlogs(struct ath6kl *ar) } address = TARG_VTOP(ar->target_type, debug_hdr_addr); - ath6kl_diag_read(ar, address, &debug_hdr, sizeof(debug_hdr)); + ret = ath6kl_diag_read(ar, address, &debug_hdr, sizeof(debug_hdr)); + if (ret) + goto out; address = TARG_VTOP(ar->target_type, le32_to_cpu(debug_hdr.dbuf_addr)); firstbuf = address; dropped = le32_to_cpu(debug_hdr.dropped); - ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf)); + ret = ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf)); + if (ret) + goto out; loop = 100; @@ -304,7 +329,8 @@ int ath6kl_read_fwlogs(struct ath6kl *ar) address = TARG_VTOP(ar->target_type, le32_to_cpu(debug_buf.next)); - ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf)); + ret = ath6kl_diag_read(ar, address, &debug_buf, + sizeof(debug_buf)); if (ret) goto out; @@ -322,48 +348,12 @@ out: return ret; } -/* FIXME: move to a better place, target.h? */ -#define AR6003_RESET_CONTROL_ADDRESS 0x00004000 -#define AR6004_RESET_CONTROL_ADDRESS 0x00004000 - -void ath6kl_reset_device(struct ath6kl *ar, u32 target_type, - bool wait_fot_compltn, bool cold_reset) -{ - int status = 0; - u32 address; - __le32 data; - - if (target_type != TARGET_TYPE_AR6003 && - target_type != TARGET_TYPE_AR6004) - return; - - data = cold_reset ? cpu_to_le32(RESET_CONTROL_COLD_RST) : - cpu_to_le32(RESET_CONTROL_MBOX_RST); - - switch (target_type) { - case TARGET_TYPE_AR6003: - address = AR6003_RESET_CONTROL_ADDRESS; - break; - case TARGET_TYPE_AR6004: - address = AR6004_RESET_CONTROL_ADDRESS; - break; - default: - address = AR6003_RESET_CONTROL_ADDRESS; - break; - } - - status = ath6kl_diag_write32(ar, address, data); - - if (status) - ath6kl_err("failed to reset target\n"); -} - static void ath6kl_install_static_wep_keys(struct ath6kl_vif *vif) { u8 index; u8 keyusage; - for (index = WMI_MIN_KEY_INDEX; index <= WMI_MAX_KEY_INDEX; index++) { + for (index = 0; index <= WMI_MAX_KEY_INDEX; index++) { if (vif->wep_key_list[index].key_len) { keyusage = GROUP_USAGE; if (index == vif->def_txkey_index) @@ -406,8 +396,8 @@ void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel) if (!ik->valid) break; - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delayed addkey for " - "the initial group key for AP mode\n"); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delayed addkey for the initial group key for AP mode\n"); memset(key_rsc, 0, sizeof(key_rsc)); res = ath6kl_wmi_addkey_cmd( ar->wmi, vif->fw_vif_idx, ik->key_index, ik->key_type, @@ -415,12 +405,16 @@ void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel) ik->key, KEY_OP_INIT_VAL, NULL, SYNC_BOTH_WMIFLAG); if (res) { - ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "Delayed " - "addkey failed: %d\n", res); + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delayed addkey failed: %d\n", res); } break; } + if (ar->last_ch != channel) + /* we actually don't know the phymode, default to HT20 */ + ath6kl_cfg80211_ch_switch_notify(vif, channel, WMI_11G_HT20); + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, NONE_BSS_FILTER, 0); set_bit(CONNECTED, &vif->flags); netif_carrier_on(vif->ndev); @@ -428,9 +422,8 @@ void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel) void ath6kl_connect_ap_mode_sta(struct ath6kl_vif *vif, u16 aid, u8 *mac_addr, u8 keymgmt, u8 ucipher, u8 auth, - u8 assoc_req_len, u8 *assoc_info) + u8 assoc_req_len, u8 *assoc_info, u8 apsd_info) { - struct ath6kl *ar = vif->ar; u8 *ies = NULL, *wpa_ie = NULL, *pos; size_t ies_len = 0; struct station_info sinfo; @@ -484,9 +477,9 @@ void ath6kl_connect_ap_mode_sta(struct ath6kl_vif *vif, u16 aid, u8 *mac_addr, pos += 2 + pos[1]; } - ath6kl_add_new_sta(ar, mac_addr, aid, wpa_ie, + ath6kl_add_new_sta(vif, mac_addr, aid, wpa_ie, wpa_ie ? 2 + wpa_ie[1] : 0, - keymgmt, ucipher, auth); + keymgmt, ucipher, auth, apsd_info); /* send event to application */ memset(&sinfo, 0, sizeof(sinfo)); @@ -527,24 +520,30 @@ void ath6kl_disconnect(struct ath6kl_vif *vif) /* WMI Event handlers */ -void ath6kl_ready_event(void *devt, u8 *datap, u32 sw_ver, u32 abi_ver) +void ath6kl_ready_event(void *devt, u8 *datap, u32 sw_ver, u32 abi_ver, + enum wmi_phy_cap cap) { struct ath6kl *ar = devt; memcpy(ar->mac_addr, datap, ETH_ALEN); - ath6kl_dbg(ATH6KL_DBG_TRC, "%s: mac addr = %pM\n", - __func__, ar->mac_addr); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "ready event mac addr %pM sw_ver 0x%x abi_ver 0x%x cap 0x%x\n", + ar->mac_addr, sw_ver, abi_ver, cap); ar->version.wlan_ver = sw_ver; ar->version.abi_ver = abi_ver; - - snprintf(ar->wiphy->fw_version, - sizeof(ar->wiphy->fw_version), - "%u.%u.%u.%u", - (ar->version.wlan_ver & 0xf0000000) >> 28, - (ar->version.wlan_ver & 0x0f000000) >> 24, - (ar->version.wlan_ver & 0x00ff0000) >> 16, - (ar->version.wlan_ver & 0x0000ffff)); + ar->hw.cap = cap; + + if (strlen(ar->wiphy->fw_version) == 0) { + snprintf(ar->wiphy->fw_version, + sizeof(ar->wiphy->fw_version), + "%u.%u.%u.%u", + (ar->version.wlan_ver & 0xf0000000) >> 28, + (ar->version.wlan_ver & 0x0f000000) >> 24, + (ar->version.wlan_ver & 0x00ff0000) >> 16, + (ar->version.wlan_ver & 0x0000ffff)); + } /* indicate to the waiting thread that the ready event was received */ set_bit(WMI_READY, &ar->flag); @@ -570,6 +569,57 @@ void ath6kl_scan_complete_evt(struct ath6kl_vif *vif, int status) ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "scan complete: %d\n", status); } +static int ath6kl_commit_ch_switch(struct ath6kl_vif *vif, u16 channel) +{ + struct ath6kl *ar = vif->ar; + + vif->profile.ch = cpu_to_le16(channel); + + switch (vif->nw_type) { + case AP_NETWORK: + /* + * reconfigure any saved RSN IE capabilites in the beacon / + * probe response to stay in sync with the supplicant. + */ + if (vif->rsn_capab && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities)) + ath6kl_wmi_set_ie_cmd(ar->wmi, vif->fw_vif_idx, + WLAN_EID_RSN, WMI_RSN_IE_CAPB, + (const u8 *) &vif->rsn_capab, + sizeof(vif->rsn_capab)); + + return ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, + &vif->profile); + default: + ath6kl_err("won't switch channels nw_type=%d\n", vif->nw_type); + return -ENOTSUPP; + } +} + +static void ath6kl_check_ch_switch(struct ath6kl *ar, u16 channel) +{ + struct ath6kl_vif *vif; + int res = 0; + + if (!ar->want_ch_switch) + return; + + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (ar->want_ch_switch & (1 << vif->fw_vif_idx)) + res = ath6kl_commit_ch_switch(vif, channel); + + /* if channel switch failed, oh well we tried */ + ar->want_ch_switch &= ~(1 << vif->fw_vif_idx); + + if (res) + ath6kl_err("channel switch failed nw_type %d res %d\n", + vif->nw_type, res); + } + spin_unlock_bh(&ar->list_lock); +} + void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *bssid, u16 listen_int, u16 beacon_int, enum network_type net_type, u8 beacon_ie_len, @@ -587,10 +637,11 @@ void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *bssid, memcpy(vif->bssid, bssid, sizeof(vif->bssid)); vif->bss_ch = channel; - if ((vif->nw_type == INFRA_NETWORK)) + if ((vif->nw_type == INFRA_NETWORK)) { ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, - ar->listen_intvl_t, - ar->listen_intvl_b); + vif->listen_intvl_t, 0); + ath6kl_check_ch_switch(ar, channel); + } netif_wake_queue(vif->ndev); @@ -601,7 +652,7 @@ void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *bssid, netif_carrier_on(vif->ndev); spin_unlock_bh(&vif->if_lock); - aggr_reset_state(vif->aggr_cntxt); + aggr_reset_state(vif->aggr_cntxt->aggr_conn); vif->reconnect_flag = 0; if ((vif->nw_type == ADHOC_NETWORK) && ar->ibss_ps_enable) { @@ -639,9 +690,9 @@ void ath6kl_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, bool ismcast) cfg80211_michael_mic_failure(vif->ndev, sta->mac, NL80211_KEYTYPE_PAIRWISE, keyid, tsc, GFP_KERNEL); - } else + } else { ath6kl_cfg80211_tkip_micerr_event(vif, keyid, ismcast); - + } } static void ath6kl_update_target_stats(struct ath6kl_vif *vif, u8 *ptr, u32 len) @@ -745,6 +796,10 @@ static void ath6kl_update_target_stats(struct ath6kl_vif *vif, u8 *ptr, u32 len) stats->wow_evt_discarded += le16_to_cpu(tgt_stats->wow_stats.wow_evt_discarded); + stats->arp_received = le32_to_cpu(tgt_stats->arp_stats.arp_received); + stats->arp_replied = le32_to_cpu(tgt_stats->arp_stats.arp_replied); + stats->arp_matched = le32_to_cpu(tgt_stats->arp_stats.arp_matched); + if (test_bit(STATS_UPDATE_PEND, &vif->flags)) { clear_bit(STATS_UPDATE_PEND, &vif->flags); wake_up(&ar->event_wq); @@ -808,6 +863,7 @@ void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) struct sk_buff *skb; bool psq_empty = false; struct ath6kl *ar = vif->ar; + struct ath6kl_mgmt_buff *mgmt_buf; conn = ath6kl_find_sta_by_aid(ar, aid); @@ -818,7 +874,7 @@ void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) * becomes empty update the PVB for this station. */ spin_lock_bh(&conn->psq_lock); - psq_empty = skb_queue_empty(&conn->psq); + psq_empty = skb_queue_empty(&conn->psq) && (conn->mgmt_psq_len == 0); spin_unlock_bh(&conn->psq_lock); if (psq_empty) @@ -826,15 +882,31 @@ void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) return; spin_lock_bh(&conn->psq_lock); - skb = skb_dequeue(&conn->psq); - spin_unlock_bh(&conn->psq_lock); + if (conn->mgmt_psq_len > 0) { + mgmt_buf = list_first_entry(&conn->mgmt_psq, + struct ath6kl_mgmt_buff, list); + list_del(&mgmt_buf->list); + conn->mgmt_psq_len--; + spin_unlock_bh(&conn->psq_lock); + + conn->sta_flags |= STA_PS_POLLED; + ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, + mgmt_buf->id, mgmt_buf->freq, + mgmt_buf->wait, mgmt_buf->buf, + mgmt_buf->len, mgmt_buf->no_cck); + conn->sta_flags &= ~STA_PS_POLLED; + kfree(mgmt_buf); + } else { + skb = skb_dequeue(&conn->psq); + spin_unlock_bh(&conn->psq_lock); - conn->sta_flags |= STA_PS_POLLED; - ath6kl_data_tx(skb, vif->ndev); - conn->sta_flags &= ~STA_PS_POLLED; + conn->sta_flags |= STA_PS_POLLED; + ath6kl_data_tx(skb, vif->ndev); + conn->sta_flags &= ~STA_PS_POLLED; + } spin_lock_bh(&conn->psq_lock); - psq_empty = skb_queue_empty(&conn->psq); + psq_empty = skb_queue_empty(&conn->psq) && (conn->mgmt_psq_len == 0); spin_unlock_bh(&conn->psq_lock); if (psq_empty) @@ -892,6 +964,28 @@ void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, u8 *bssid, struct ath6kl *ar = vif->ar; if (vif->nw_type == AP_NETWORK) { + /* disconnect due to other STA vif switching channels */ + if (reason == BSS_DISCONNECTED && + prot_reason_status == WMI_AP_REASON_STA_ROAM) { + ar->want_ch_switch |= 1 << vif->fw_vif_idx; + /* bail back to this channel if STA vif fails connect */ + ar->last_ch = le16_to_cpu(vif->profile.ch); + } + + if (prot_reason_status == WMI_AP_REASON_MAX_STA) { + /* send max client reached notification to user space */ + cfg80211_conn_failed(vif->ndev, bssid, + NL80211_CONN_FAIL_MAX_CLIENTS, + GFP_KERNEL); + } + + if (prot_reason_status == WMI_AP_REASON_ACL) { + /* send blocked client notification to user space */ + cfg80211_conn_failed(vif->ndev, bssid, + NL80211_CONN_FAIL_BLOCKED_CLIENT, + GFP_KERNEL); + } + if (!ath6kl_remove_sta(ar, bssid, prot_reason_status)) return; @@ -920,10 +1014,10 @@ void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, u8 *bssid, } ath6kl_cfg80211_disconnect_event(vif, reason, bssid, - assoc_resp_len, assoc_info, - prot_reason_status); + assoc_resp_len, assoc_info, + prot_reason_status); - aggr_reset_state(vif->aggr_cntxt); + aggr_reset_state(vif->aggr_cntxt->aggr_conn); del_timer(&vif->disconnect_timer); @@ -941,14 +1035,17 @@ void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, u8 *bssid, } else { set_bit(CONNECT_PEND, &vif->flags); if (((reason == ASSOC_FAILED) && - (prot_reason_status == 0x11)) || - ((reason == ASSOC_FAILED) && (prot_reason_status == 0x0) - && (vif->reconnect_flag == 1))) { + (prot_reason_status == 0x11)) || + ((reason == ASSOC_FAILED) && (prot_reason_status == 0x0) && + (vif->reconnect_flag == 1))) { set_bit(CONNECTED, &vif->flags); return; } } + /* restart disconnected concurrent vifs waiting for new channel */ + ath6kl_check_ch_switch(ar, ar->last_ch); + /* update connect & link status atomically */ spin_lock_bh(&vif->if_lock); clear_bit(CONNECTED, &vif->flags); @@ -994,8 +1091,9 @@ static int ath6kl_open(struct net_device *dev) if (test_bit(CONNECTED, &vif->flags)) { netif_carrier_on(dev); netif_wake_queue(dev); - } else + } else { netif_carrier_off(dev); + } return 0; } @@ -1020,11 +1118,174 @@ static struct net_device_stats *ath6kl_get_stats(struct net_device *dev) return &vif->net_stats; } -static struct net_device_ops ath6kl_netdev_ops = { +static int ath6kl_set_features(struct net_device *dev, + netdev_features_t features) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl *ar = vif->ar; + int err = 0; + + if ((features & NETIF_F_RXCSUM) && + (ar->rx_meta_ver != WMI_META_VERSION_2)) { + ar->rx_meta_ver = WMI_META_VERSION_2; + err = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, + vif->fw_vif_idx, + ar->rx_meta_ver, 0, 0); + if (err) { + dev->features = features & ~NETIF_F_RXCSUM; + return err; + } + } else if (!(features & NETIF_F_RXCSUM) && + (ar->rx_meta_ver == WMI_META_VERSION_2)) { + ar->rx_meta_ver = 0; + err = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, + vif->fw_vif_idx, + ar->rx_meta_ver, 0, 0); + if (err) { + dev->features = features | NETIF_F_RXCSUM; + return err; + } + } + + return err; +} + +static void ath6kl_set_multicast_list(struct net_device *ndev) +{ + struct ath6kl_vif *vif = netdev_priv(ndev); + bool mc_all_on = false; + int mc_count = netdev_mc_count(ndev); + struct netdev_hw_addr *ha; + bool found; + struct ath6kl_mc_filter *mc_filter, *tmp; + struct list_head mc_filter_new; + int ret; + + if (!test_bit(WMI_READY, &vif->ar->flag) || + !test_bit(WLAN_ENABLED, &vif->flags)) + return; + + /* Enable multicast-all filter. */ + mc_all_on = !!(ndev->flags & IFF_PROMISC) || + !!(ndev->flags & IFF_ALLMULTI) || + !!(mc_count > ATH6K_MAX_MC_FILTERS_PER_LIST); + + if (mc_all_on) + set_bit(NETDEV_MCAST_ALL_ON, &vif->flags); + else + clear_bit(NETDEV_MCAST_ALL_ON, &vif->flags); + + if (test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + vif->ar->fw_capabilities)) { + mc_all_on = mc_all_on || (vif->ar->state == ATH6KL_STATE_ON); + } + + if (!(ndev->flags & IFF_MULTICAST)) { + mc_all_on = false; + set_bit(NETDEV_MCAST_ALL_OFF, &vif->flags); + } else { + clear_bit(NETDEV_MCAST_ALL_OFF, &vif->flags); + } + + /* Enable/disable "multicast-all" filter*/ + ath6kl_dbg(ATH6KL_DBG_TRC, "%s multicast-all filter\n", + mc_all_on ? "enabling" : "disabling"); + + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, vif->fw_vif_idx, + mc_all_on); + if (ret) { + ath6kl_warn("Failed to %s multicast-all receive\n", + mc_all_on ? "enable" : "disable"); + return; + } + + if (test_bit(NETDEV_MCAST_ALL_ON, &vif->flags)) + return; + + /* Keep the driver and firmware mcast list in sync. */ + list_for_each_entry_safe(mc_filter, tmp, &vif->mc_filter, list) { + found = false; + netdev_for_each_mc_addr(ha, ndev) { + if (memcmp(ha->addr, mc_filter->hw_addr, + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE) == 0) { + found = true; + break; + } + } + + if (!found) { + /* + * Delete the filter which was previously set + * but not in the new request. + */ + ath6kl_dbg(ATH6KL_DBG_TRC, + "Removing %pM from multicast filter\n", + mc_filter->hw_addr); + ret = ath6kl_wmi_add_del_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, mc_filter->hw_addr, + false); + if (ret) { + ath6kl_warn("Failed to remove multicast filter:%pM\n", + mc_filter->hw_addr); + return; + } + + list_del(&mc_filter->list); + kfree(mc_filter); + } + } + + INIT_LIST_HEAD(&mc_filter_new); + + netdev_for_each_mc_addr(ha, ndev) { + found = false; + list_for_each_entry(mc_filter, &vif->mc_filter, list) { + if (memcmp(ha->addr, mc_filter->hw_addr, + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE) == 0) { + found = true; + break; + } + } + + if (!found) { + mc_filter = kzalloc(sizeof(struct ath6kl_mc_filter), + GFP_ATOMIC); + if (!mc_filter) { + WARN_ON(1); + goto out; + } + + memcpy(mc_filter->hw_addr, ha->addr, + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE); + /* Set the multicast filter */ + ath6kl_dbg(ATH6KL_DBG_TRC, + "Adding %pM to multicast filter list\n", + mc_filter->hw_addr); + ret = ath6kl_wmi_add_del_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, mc_filter->hw_addr, + true); + if (ret) { + ath6kl_warn("Failed to add multicast filter :%pM\n", + mc_filter->hw_addr); + kfree(mc_filter); + goto out; + } + + list_add_tail(&mc_filter->list, &mc_filter_new); + } + } + +out: + list_splice_tail(&mc_filter_new, &vif->mc_filter); +} + +static const struct net_device_ops ath6kl_netdev_ops = { .ndo_open = ath6kl_open, .ndo_stop = ath6kl_close, .ndo_start_xmit = ath6kl_data_tx, .ndo_get_stats = ath6kl_get_stats, + .ndo_set_features = ath6kl_set_features, + .ndo_set_rx_mode = ath6kl_set_multicast_list, }; void init_netdev(struct net_device *dev) @@ -1034,9 +1295,13 @@ void init_netdev(struct net_device *dev) dev->watchdog_timeo = ATH6KL_TX_TIMEOUT; dev->needed_headroom = ETH_HLEN; - dev->needed_headroom += sizeof(struct ath6kl_llc_snap_hdr) + - sizeof(struct wmi_data_hdr) + HTC_HDR_LENGTH - + WMI_MAX_TX_META_SZ + ATH6KL_HTC_ALIGN_BYTES; + dev->needed_headroom += roundup(sizeof(struct ath6kl_llc_snap_hdr) + + sizeof(struct wmi_data_hdr) + + HTC_HDR_LENGTH + + WMI_MAX_TX_META_SZ + + ATH6KL_HTC_ALIGN_BYTES, 4); + + dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_RXCSUM; return; } diff --git a/drivers/net/wireless/ath/ath6kl/recovery.c b/drivers/net/wireless/ath/ath6kl/recovery.c new file mode 100644 index 00000000000..3a8d5e97dc8 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/recovery.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "cfg80211.h" +#include "debug.h" + +static void ath6kl_recovery_work(struct work_struct *work) +{ + struct ath6kl *ar = container_of(work, struct ath6kl, + fw_recovery.recovery_work); + + ar->state = ATH6KL_STATE_RECOVERY; + + del_timer_sync(&ar->fw_recovery.hb_timer); + + ath6kl_init_hw_restart(ar); + + ar->state = ATH6KL_STATE_ON; + clear_bit(WMI_CTRL_EP_FULL, &ar->flag); + + ar->fw_recovery.err_reason = 0; + + if (ar->fw_recovery.hb_poll) + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason) +{ + if (!ar->fw_recovery.enable) + return; + + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Fw error detected, reason:%d\n", + reason); + + set_bit(reason, &ar->fw_recovery.err_reason); + + if (!test_bit(RECOVERY_CLEANUP, &ar->flag) && + ar->state != ATH6KL_STATE_RECOVERY) + queue_work(ar->ath6kl_wq, &ar->fw_recovery.recovery_work); +} + +void ath6kl_recovery_hb_event(struct ath6kl *ar, u32 cookie) +{ + if (cookie == ar->fw_recovery.seq_num) + ar->fw_recovery.hb_pending = false; +} + +static void ath6kl_recovery_hb_timer(unsigned long data) +{ + struct ath6kl *ar = (struct ath6kl *) data; + int err; + + if (test_bit(RECOVERY_CLEANUP, &ar->flag) || + (ar->state == ATH6KL_STATE_RECOVERY)) + return; + + if (ar->fw_recovery.hb_pending) + ar->fw_recovery.hb_misscnt++; + else + ar->fw_recovery.hb_misscnt = 0; + + if (ar->fw_recovery.hb_misscnt > ATH6KL_HB_RESP_MISS_THRES) { + ar->fw_recovery.hb_misscnt = 0; + ar->fw_recovery.seq_num = 0; + ar->fw_recovery.hb_pending = false; + ath6kl_recovery_err_notify(ar, ATH6KL_FW_HB_RESP_FAILURE); + return; + } + + ar->fw_recovery.seq_num++; + ar->fw_recovery.hb_pending = true; + + err = ath6kl_wmi_get_challenge_resp_cmd(ar->wmi, + ar->fw_recovery.seq_num, 0); + if (err) + ath6kl_warn("Failed to send hb challenge request, err:%d\n", + err); + + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_init(struct ath6kl *ar) +{ + struct ath6kl_fw_recovery *recovery = &ar->fw_recovery; + + clear_bit(RECOVERY_CLEANUP, &ar->flag); + INIT_WORK(&recovery->recovery_work, ath6kl_recovery_work); + recovery->seq_num = 0; + recovery->hb_misscnt = 0; + ar->fw_recovery.hb_pending = false; + ar->fw_recovery.hb_timer.function = ath6kl_recovery_hb_timer; + ar->fw_recovery.hb_timer.data = (unsigned long) ar; + init_timer_deferrable(&ar->fw_recovery.hb_timer); + + if (ar->fw_recovery.hb_poll) + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_cleanup(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + set_bit(RECOVERY_CLEANUP, &ar->flag); + + del_timer_sync(&ar->fw_recovery.hb_timer); + cancel_work_sync(&ar->fw_recovery.recovery_work); +} + +void ath6kl_recovery_suspend(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + ath6kl_recovery_cleanup(ar); + + if (!ar->fw_recovery.err_reason) + return; + + /* Process pending fw error detection */ + ar->fw_recovery.err_reason = 0; + WARN_ON(ar->state != ATH6KL_STATE_ON); + ar->state = ATH6KL_STATE_RECOVERY; + ath6kl_init_hw_restart(ar); + ar->state = ATH6KL_STATE_ON; +} + +void ath6kl_recovery_resume(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + clear_bit(RECOVERY_CLEANUP, &ar->flag); + + if (!ar->fw_recovery.hb_poll) + return; + + ar->fw_recovery.hb_pending = false; + ar->fw_recovery.seq_num = 0; + ar->fw_recovery.hb_misscnt = 0; + mod_timer(&ar->fw_recovery.hb_timer, + jiffies + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} diff --git a/drivers/net/wireless/ath/ath6kl/sdio.c b/drivers/net/wireless/ath/ath6kl/sdio.c index 9475e2d0d0b..339d89f14d3 100644 --- a/drivers/net/wireless/ath/ath6kl/sdio.c +++ b/drivers/net/wireless/ath/ath6kl/sdio.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,10 +28,12 @@ #include "target.h" #include "debug.h" #include "cfg80211.h" +#include "trace.h" struct ath6kl_sdio { struct sdio_func *func; + /* protects access to bus_req_freeq */ spinlock_t lock; /* free list */ @@ -49,14 +52,20 @@ struct ath6kl_sdio { /* scatter request list head */ struct list_head scat_req; + atomic_t irq_handling; + wait_queue_head_t irq_wq; + + /* protects access to scat_req */ spinlock_t scat_lock; + bool scatter_enabled; bool is_disabled; - atomic_t irq_handling; const struct sdio_device_id *id; struct work_struct wr_async_work; struct list_head wr_asyncq; + + /* protects access to wr_asyncq */ spinlock_t wr_async_lock; }; @@ -171,6 +180,8 @@ static int ath6kl_sdio_io(struct sdio_func *func, u32 request, u32 addr, request & HIF_FIXED_ADDRESS ? " (fixed)" : "", buf, len); ath6kl_dbg_dump(ATH6KL_DBG_SDIO_DUMP, NULL, "sdio ", buf, len); + trace_ath6kl_sdio(addr, request, buf, len); + return ret; } @@ -301,6 +312,13 @@ static int ath6kl_sdio_scat_rw(struct ath6kl_sdio *ar_sdio, sdio_claim_host(ar_sdio->func); mmc_set_data_timeout(&data, ar_sdio->func->card); + + trace_ath6kl_sdio_scat(scat_req->addr, + scat_req->req, + scat_req->len, + scat_req->scat_entries, + scat_req->scat_list); + /* synchronous call to process request */ mmc_wait_for_req(ar_sdio->func->card->host, &mmc_req); @@ -327,17 +345,17 @@ static int ath6kl_sdio_alloc_prep_scat_req(struct ath6kl_sdio *ar_sdio, { struct hif_scatter_req *s_req; struct bus_request *bus_req; - int i, scat_req_sz, scat_list_sz, sg_sz, buf_sz; + int i, scat_req_sz, scat_list_sz, size; u8 *virt_buf; - scat_list_sz = (n_scat_entry - 1) * sizeof(struct hif_scatter_item); + scat_list_sz = n_scat_entry * sizeof(struct hif_scatter_item); scat_req_sz = sizeof(*s_req) + scat_list_sz; if (!virt_scat) - sg_sz = sizeof(struct scatterlist) * n_scat_entry; + size = sizeof(struct scatterlist) * n_scat_entry; else - buf_sz = 2 * L1_CACHE_BYTES + - ATH6KL_MAX_TRANSFER_SIZE_PER_SCATTER; + size = 2 * L1_CACHE_BYTES + + ATH6KL_MAX_TRANSFER_SIZE_PER_SCATTER; for (i = 0; i < n_scat_req; i++) { /* allocate the scatter request */ @@ -346,7 +364,7 @@ static int ath6kl_sdio_alloc_prep_scat_req(struct ath6kl_sdio *ar_sdio, return -ENOMEM; if (virt_scat) { - virt_buf = kzalloc(buf_sz, GFP_KERNEL); + virt_buf = kzalloc(size, GFP_KERNEL); if (!virt_buf) { kfree(s_req); return -ENOMEM; @@ -356,7 +374,7 @@ static int ath6kl_sdio_alloc_prep_scat_req(struct ath6kl_sdio *ar_sdio, (u8 *)L1_CACHE_ALIGN((unsigned long)virt_buf); } else { /* allocate sglist */ - s_req->sgentries = kzalloc(sg_sz, GFP_KERNEL); + s_req->sgentries = kzalloc(size, GFP_KERNEL); if (!s_req->sgentries) { kfree(s_req); @@ -402,10 +420,14 @@ static int ath6kl_sdio_read_write_sync(struct ath6kl *ar, u32 addr, u8 *buf, return -ENOMEM; mutex_lock(&ar_sdio->dma_buffer_mutex); tbuf = ar_sdio->dma_buffer; - memcpy(tbuf, buf, len); + + if (request & HIF_WRITE) + memcpy(tbuf, buf, len); + bounced = true; - } else + } else { tbuf = buf; + } ret = ath6kl_sdio_io(ar_sdio->func, request, addr, tbuf, len); if ((request & HIF_READ) && bounced) @@ -420,9 +442,9 @@ static int ath6kl_sdio_read_write_sync(struct ath6kl *ar, u32 addr, u8 *buf, static void __ath6kl_sdio_write_async(struct ath6kl_sdio *ar_sdio, struct bus_request *req) { - if (req->scat_req) + if (req->scat_req) { ath6kl_sdio_scat_rw(ar_sdio, req); - else { + } else { void *context; int status; @@ -461,7 +483,6 @@ static void ath6kl_sdio_irq_handler(struct sdio_func *func) ar_sdio = sdio_get_drvdata(func); atomic_set(&ar_sdio->irq_handling, 1); - /* * Release the host during interrups so we can pick it back up when * we process commands. @@ -470,7 +491,10 @@ static void ath6kl_sdio_irq_handler(struct sdio_func *func) status = ath6kl_hif_intr_bh_handler(ar_sdio->ar); sdio_claim_host(ar_sdio->func); + atomic_set(&ar_sdio->irq_handling, 0); + wake_up(&ar_sdio->irq_wq); + WARN_ON(status && status != -ECANCELED); } @@ -539,7 +563,7 @@ static int ath6kl_sdio_write_async(struct ath6kl *ar, u32 address, u8 *buffer, bus_req = ath6kl_sdio_alloc_busreq(ar_sdio); - if (!bus_req) + if (WARN_ON_ONCE(!bus_req)) return -ENOMEM; bus_req->address = address; @@ -571,6 +595,13 @@ static void ath6kl_sdio_irq_enable(struct ath6kl *ar) sdio_release_host(ar_sdio->func); } +static bool ath6kl_sdio_is_on_irq(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + + return !atomic_read(&ar_sdio->irq_handling); +} + static void ath6kl_sdio_irq_disable(struct ath6kl *ar) { struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); @@ -578,10 +609,14 @@ static void ath6kl_sdio_irq_disable(struct ath6kl *ar) sdio_claim_host(ar_sdio->func); - /* Mask our function IRQ */ - while (atomic_read(&ar_sdio->irq_handling)) { + if (atomic_read(&ar_sdio->irq_handling)) { sdio_release_host(ar_sdio->func); - schedule_timeout(HZ / 10); + + ret = wait_event_interruptible(ar_sdio->irq_wq, + ath6kl_sdio_is_on_irq(ar)); + if (ret) + return; + sdio_claim_host(ar_sdio->func); } @@ -603,6 +638,8 @@ static struct hif_scatter_req *ath6kl_sdio_scatter_req_get(struct ath6kl *ar) node = list_first_entry(&ar_sdio->scat_req, struct hif_scatter_req, list); list_del(&node->list); + + node->scat_q_depth = get_queue_depth(&ar_sdio->scat_req); } spin_unlock_bh(&ar_sdio->scat_lock); @@ -620,7 +657,6 @@ static void ath6kl_sdio_scatter_req_add(struct ath6kl *ar, list_add_tail(&s_req->list, &ar_sdio->scat_req); spin_unlock_bh(&ar_sdio->scat_lock); - } /* scatter gather read write request */ @@ -635,12 +671,12 @@ static int ath6kl_sdio_async_rw_scatter(struct ath6kl *ar, return -EINVAL; ath6kl_dbg(ATH6KL_DBG_SCATTER, - "hif-scatter: total len: %d scatter entries: %d\n", - scat_req->len, scat_req->scat_entries); + "hif-scatter: total len: %d scatter entries: %d\n", + scat_req->len, scat_req->scat_entries); - if (request & HIF_SYNCHRONOUS) + if (request & HIF_SYNCHRONOUS) { status = ath6kl_sdio_scat_rw(ar_sdio, scat_req->busrequest); - else { + } else { spin_lock_bh(&ar_sdio->wr_async_lock); list_add_tail(&scat_req->busrequest->list, &ar_sdio->wr_asyncq); spin_unlock_bh(&ar_sdio->wr_async_lock); @@ -683,7 +719,7 @@ static int ath6kl_sdio_enable_scatter(struct ath6kl *ar) { struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); struct htc_target *target = ar->htc_target; - int ret; + int ret = 0; bool virt_scat = false; if (ar_sdio->scatter_enabled) @@ -772,7 +808,6 @@ static int ath6kl_sdio_config(struct ath6kl *ar) if (ret) { ath6kl_err("Set sdio block size %d failed: %d)\n", HIF_MBOX_BLOCK_SIZE, ret); - sdio_release_host(func); goto out; } @@ -782,7 +817,7 @@ out: return ret; } -static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +static int ath6kl_set_sdio_pm_caps(struct ath6kl *ar) { struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); struct sdio_func *func = ar_sdio->func; @@ -793,60 +828,89 @@ static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) ath6kl_dbg(ATH6KL_DBG_SUSPEND, "sdio suspend pm_caps 0x%x\n", flags); - if (!(flags & MMC_PM_KEEP_POWER) || - (ar->conf_flags & ATH6KL_CONF_SUSPEND_CUTPOWER)) { - /* as host doesn't support keep power we need to cut power */ - return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_CUTPOWER, - NULL); - } + if (!(flags & MMC_PM_WAKE_SDIO_IRQ) || + !(flags & MMC_PM_KEEP_POWER)) + return -EINVAL; ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); if (ret) { - printk(KERN_ERR "ath6kl: set sdio pm flags failed: %d\n", - ret); + ath6kl_err("set sdio keep pwr flag failed: %d\n", ret); return ret; } - if (!(flags & MMC_PM_WAKE_SDIO_IRQ)) - goto deepsleep; - /* sdio irq wakes up host */ + ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); + if (ret) + ath6kl_err("set sdio wake irq flag failed: %d\n", ret); - if (ar->state == ATH6KL_STATE_SCHED_SCAN) { - ret = ath6kl_cfg80211_suspend(ar, - ATH6KL_CFG_SUSPEND_SCHED_SCAN, - NULL); - if (ret) { - ath6kl_warn("Schedule scan suspend failed: %d", ret); - return ret; - } + return ret; +} + +static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct sdio_func *func = ar_sdio->func; + mmc_pm_flag_t flags; + bool try_deepsleep = false; + int ret; - ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); + if (ar->suspend_mode == WLAN_POWER_STATE_WOW || + (!ar->suspend_mode && wow)) { + ret = ath6kl_set_sdio_pm_caps(ar); if (ret) - ath6kl_warn("set sdio wake irq flag failed: %d\n", ret); + goto cut_pwr; - return ret; + ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, wow); + if (ret && ret != -ENOTCONN) + ath6kl_err("wow suspend failed: %d\n", ret); + + if (ret && + (!ar->wow_suspend_mode || + ar->wow_suspend_mode == WLAN_POWER_STATE_DEEP_SLEEP)) + try_deepsleep = true; + else if (ret && + ar->wow_suspend_mode == WLAN_POWER_STATE_CUT_PWR) + goto cut_pwr; + if (!ret) + return 0; } - if (wow) { + if (ar->suspend_mode == WLAN_POWER_STATE_DEEP_SLEEP || + !ar->suspend_mode || try_deepsleep) { + flags = sdio_get_host_pm_caps(func); + if (!(flags & MMC_PM_KEEP_POWER)) + goto cut_pwr; + + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + goto cut_pwr; + /* - * The host sdio controller is capable of keep power and - * sdio irq wake up at this point. It's fine to continue - * wow suspend operation. + * Workaround to support Deep Sleep with MSM, set the host pm + * flag as MMC_PM_WAKE_SDIO_IRQ to allow SDCC deiver to disable + * the sdc2_clock and internally allows MSM to enter + * TCXO shutdown properly. */ - ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, wow); - if (ret) - return ret; + if ((flags & MMC_PM_WAKE_SDIO_IRQ)) { + ret = sdio_set_host_pm_flags(func, + MMC_PM_WAKE_SDIO_IRQ); + if (ret) + goto cut_pwr; + } - ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); + ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, + NULL); if (ret) - ath6kl_err("set sdio wake irq flag failed: %d\n", ret); + goto cut_pwr; - return ret; + return 0; } -deepsleep: - return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, NULL); +cut_pwr: + if (func->card && func->card->host) + func->card->host->pm_flags &= ~MMC_PM_KEEP_POWER; + + return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_CUTPOWER, NULL); } static int ath6kl_sdio_resume(struct ath6kl *ar) @@ -869,7 +933,14 @@ static int ath6kl_sdio_resume(struct ath6kl *ar) case ATH6KL_STATE_WOW: break; - case ATH6KL_STATE_SCHED_SCAN: + + case ATH6KL_STATE_SUSPENDING: + break; + + case ATH6KL_STATE_RESUMING: + break; + + case ATH6KL_STATE_RECOVERY: break; } @@ -909,9 +980,8 @@ static int ath6kl_set_addrwin_reg(struct ath6kl *ar, u32 reg_addr, u32 addr) } if (status) { - ath6kl_err("%s: failed to write initial bytes of 0x%x " - "to window reg: 0x%X\n", __func__, - addr, reg_addr); + ath6kl_err("%s: failed to write initial bytes of 0x%x to window reg: 0x%X\n", + __func__, addr, reg_addr); return status; } @@ -949,7 +1019,7 @@ static int ath6kl_sdio_diag_read32(struct ath6kl *ar, u32 address, u32 *data) (u8 *)data, sizeof(u32), HIF_RD_SYNC_BYTE_INC); if (status) { ath6kl_err("%s: failed to read from window data addr\n", - __func__); + __func__); return status; } @@ -989,7 +1059,6 @@ static int ath6kl_sdio_bmi_credits(struct ath6kl *ar) timeout = jiffies + msecs_to_jiffies(BMI_COMMUNICATION_TIMEOUT); while (time_before(jiffies, timeout) && !ar->bmi.cmd_credits) { - /* * Hit the credit counter with a 4-byte access, the first byte * read will hit the counter and cause a decrement, while the @@ -1000,8 +1069,8 @@ static int ath6kl_sdio_bmi_credits(struct ath6kl *ar) (u8 *)&ar->bmi.cmd_credits, 4, HIF_RD_SYNC_BYTE_INC); if (ret) { - ath6kl_err("Unable to decrement the command credit " - "count register: %d\n", ret); + ath6kl_err("Unable to decrement the command credit count register: %d\n", + ret); return ret; } @@ -1061,10 +1130,12 @@ static int ath6kl_sdio_bmi_write(struct ath6kl *ar, u8 *buf, u32 len) ret = ath6kl_sdio_read_write_sync(ar, addr, buf, len, HIF_WR_SYNC_BYTE_INC); - if (ret) + if (ret) { ath6kl_err("unable to send the bmi data to the device\n"); + return ret; + } - return ret; + return 0; } static int ath6kl_sdio_bmi_read(struct ath6kl *ar, u8 *buf, u32 len) @@ -1260,10 +1331,12 @@ static int ath6kl_sdio_probe(struct sdio_func *func, INIT_WORK(&ar_sdio->wr_async_work, ath6kl_sdio_write_async_work); + init_waitqueue_head(&ar_sdio->irq_wq); + for (count = 0; count < BUS_REQUEST_MAX_NUM; count++) ath6kl_sdio_free_bus_req(ar_sdio, &ar_sdio->bus_req[count]); - ar = ath6kl_core_alloc(&ar_sdio->func->dev); + ar = ath6kl_core_create(&ar_sdio->func->dev); if (!ar) { ath6kl_err("Failed to alloc ath6kl core\n"); ret = -ENOMEM; @@ -1284,7 +1357,7 @@ static int ath6kl_sdio_probe(struct sdio_func *func, goto err_core_alloc; } - ret = ath6kl_core_init(ar); + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_MBOX); if (ret) { ath6kl_err("Failed to init ath6kl core\n"); goto err_core_alloc; @@ -1293,7 +1366,7 @@ static int ath6kl_sdio_probe(struct sdio_func *func, return ret; err_core_alloc: - ath6kl_core_free(ar_sdio->ar); + ath6kl_core_destroy(ar_sdio->ar); err_dma: kfree(ar_sdio->dma_buffer); err_hif: @@ -1316,6 +1389,7 @@ static void ath6kl_sdio_remove(struct sdio_func *func) cancel_work_sync(&ar_sdio->wr_async_work); ath6kl_core_cleanup(ar_sdio->ar); + ath6kl_core_destroy(ar_sdio->ar); kfree(ar_sdio->dma_buffer); kfree(ar_sdio); @@ -1332,7 +1406,7 @@ static const struct sdio_device_id ath6kl_sdio_devices[] = { MODULE_DEVICE_TABLE(sdio, ath6kl_sdio_devices); static struct sdio_driver ath6kl_sdio_driver = { - .name = "ath6kl", + .name = "ath6kl_sdio", .id_table = ath6kl_sdio_devices, .probe = ath6kl_sdio_probe, .remove = ath6kl_sdio_remove, @@ -1362,19 +1436,25 @@ MODULE_AUTHOR("Atheros Communications, Inc."); MODULE_DESCRIPTION("Driver support for Atheros AR600x SDIO devices"); MODULE_LICENSE("Dual BSD/GPL"); -MODULE_FIRMWARE(AR6003_HW_2_0_OTP_FILE); -MODULE_FIRMWARE(AR6003_HW_2_0_FIRMWARE_FILE); -MODULE_FIRMWARE(AR6003_HW_2_0_PATCH_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_OTP_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_PATCH_FILE); MODULE_FIRMWARE(AR6003_HW_2_0_BOARD_DATA_FILE); MODULE_FIRMWARE(AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE); -MODULE_FIRMWARE(AR6003_HW_2_1_1_OTP_FILE); -MODULE_FIRMWARE(AR6003_HW_2_1_1_FIRMWARE_FILE); -MODULE_FIRMWARE(AR6003_HW_2_1_1_PATCH_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_OTP_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_PATCH_FILE); MODULE_FIRMWARE(AR6003_HW_2_1_1_BOARD_DATA_FILE); MODULE_FIRMWARE(AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE); -MODULE_FIRMWARE(AR6004_HW_1_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_FW_DIR "/" AR6004_HW_1_0_FIRMWARE_FILE); MODULE_FIRMWARE(AR6004_HW_1_0_BOARD_DATA_FILE); MODULE_FIRMWARE(AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE); -MODULE_FIRMWARE(AR6004_HW_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_FW_DIR "/" AR6004_HW_1_1_FIRMWARE_FILE); MODULE_FIRMWARE(AR6004_HW_1_1_BOARD_DATA_FILE); MODULE_FIRMWARE(AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_FW_DIR "/" AR6004_HW_1_2_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_FW_DIR "/" AR6004_HW_1_3_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE); diff --git a/drivers/net/wireless/ath/ath6kl/target.h b/drivers/net/wireless/ath/ath6kl/target.h index 108a723a108..d5eeeae7711 100644 --- a/drivers/net/wireless/ath/ath6kl/target.h +++ b/drivers/net/wireless/ath/ath6kl/target.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2010 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,11 +20,12 @@ #define AR6003_BOARD_DATA_SZ 1024 #define AR6003_BOARD_EXT_DATA_SZ 768 +#define AR6003_BOARD_EXT_DATA_SZ_V2 1024 #define AR6004_BOARD_DATA_SZ 6144 #define AR6004_BOARD_EXT_DATA_SZ 0 -#define RESET_CONTROL_ADDRESS 0x00000000 +#define RESET_CONTROL_ADDRESS 0x00004000 #define RESET_CONTROL_COLD_RST 0x00000100 #define RESET_CONTROL_MBOX_RST 0x00000004 @@ -43,6 +45,7 @@ #define LPO_CAL_ENABLE_S 20 #define LPO_CAL_ENABLE 0x00100000 +#define GPIO_PIN9_ADDRESS 0x0000004c #define GPIO_PIN10_ADDRESS 0x00000050 #define GPIO_PIN11_ADDRESS 0x00000054 #define GPIO_PIN12_ADDRESS 0x00000058 @@ -286,7 +289,7 @@ struct host_interest { u32 hi_hp_rx_traffic_ratio; /* 0xd8 */ /* test applications flags */ - u32 hi_test_apps_related ; /* 0xdc */ + u32 hi_test_apps_related; /* 0xdc */ /* location of test script */ u32 hi_ota_testscript; /* 0xe0 */ /* location of CAL data */ diff --git a/drivers/net/wireless/ath/ath6kl/testmode.c b/drivers/net/wireless/ath/ath6kl/testmode.c index 381eb66a605..d67170ea103 100644 --- a/drivers/net/wireless/ath/ath6kl/testmode.c +++ b/drivers/net/wireless/ath/ath6kl/testmode.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,6 +16,7 @@ */ #include "testmode.h" +#include "debug.h" #include <net/netlink.h> @@ -30,7 +32,7 @@ enum ath6kl_tm_attr { enum ath6kl_tm_cmd { ATH6KL_TM_CMD_TCMD = 0, - ATH6KL_TM_CMD_RX_REPORT = 1, + ATH6KL_TM_CMD_RX_REPORT = 1, /* not used anymore */ }; #define ATH6KL_TM_DATA_MAX_LEN 5000 @@ -41,84 +43,35 @@ static const struct nla_policy ath6kl_tm_policy[ATH6KL_TM_ATTR_MAX + 1] = { .len = ATH6KL_TM_DATA_MAX_LEN }, }; -void ath6kl_tm_rx_report_event(struct ath6kl *ar, void *buf, size_t buf_len) +void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, size_t buf_len) { - if (down_interruptible(&ar->sem)) - return; - - kfree(ar->tm.rx_report); - - ar->tm.rx_report = kmemdup(buf, buf_len, GFP_KERNEL); - ar->tm.rx_report_len = buf_len; - - up(&ar->sem); - - wake_up(&ar->event_wq); -} - -static int ath6kl_tm_rx_report(struct ath6kl *ar, void *buf, size_t buf_len, - struct sk_buff *skb) -{ - int ret = 0; - long left; - - if (down_interruptible(&ar->sem)) - return -ERESTARTSYS; - - if (!test_bit(WMI_READY, &ar->flag)) { - ret = -EIO; - goto out; - } - - if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) { - ret = -EBUSY; - goto out; - } - - if (ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len) < 0) { - up(&ar->sem); - return -EIO; - } - - left = wait_event_interruptible_timeout(ar->event_wq, - ar->tm.rx_report != NULL, - WMI_TIMEOUT); + struct sk_buff *skb; - if (left == 0) { - ret = -ETIMEDOUT; - goto out; - } else if (left < 0) { - ret = left; - goto out; - } + if (!buf || buf_len == 0) + return; - if (ar->tm.rx_report == NULL || ar->tm.rx_report_len == 0) { - ret = -EINVAL; - goto out; + skb = cfg80211_testmode_alloc_event_skb(ar->wiphy, buf_len, GFP_KERNEL); + if (!skb) { + ath6kl_warn("failed to allocate testmode rx skb!\n"); + return; } - - NLA_PUT(skb, ATH6KL_TM_ATTR_DATA, ar->tm.rx_report_len, - ar->tm.rx_report); - - kfree(ar->tm.rx_report); - ar->tm.rx_report = NULL; - -out: - up(&ar->sem); - - return ret; + if (nla_put_u32(skb, ATH6KL_TM_ATTR_CMD, ATH6KL_TM_CMD_TCMD) || + nla_put(skb, ATH6KL_TM_ATTR_DATA, buf_len, buf)) + goto nla_put_failure; + cfg80211_testmode_event(skb, GFP_KERNEL); + return; nla_put_failure: - ret = -ENOBUFS; - goto out; + kfree_skb(skb); + ath6kl_warn("nla_put failed on testmode rx skb!\n"); } -int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) +int ath6kl_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, + void *data, int len) { struct ath6kl *ar = wiphy_priv(wiphy); struct nlattr *tb[ATH6KL_TM_ATTR_MAX + 1]; - int err, buf_len, reply_len; - struct sk_buff *skb; + int err, buf_len; void *buf; err = nla_parse(tb, ATH6KL_TM_ATTR_MAX, data, len, @@ -143,24 +96,6 @@ int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) break; case ATH6KL_TM_CMD_RX_REPORT: - if (!tb[ATH6KL_TM_ATTR_DATA]) - return -EINVAL; - - buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); - buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); - - reply_len = nla_total_size(ATH6KL_TM_DATA_MAX_LEN); - skb = cfg80211_testmode_alloc_reply_skb(wiphy, reply_len); - if (!skb) - return -ENOMEM; - - err = ath6kl_tm_rx_report(ar, buf, buf_len, skb); - if (err < 0) { - kfree_skb(skb); - return err; - } - - return cfg80211_testmode_reply(skb); default: return -EOPNOTSUPP; } diff --git a/drivers/net/wireless/ath/ath6kl/testmode.h b/drivers/net/wireless/ath/ath6kl/testmode.h index 43dffcc11fb..9fbcdec3e20 100644 --- a/drivers/net/wireless/ath/ath6kl/testmode.h +++ b/drivers/net/wireless/ath/ath6kl/testmode.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,17 +19,20 @@ #ifdef CONFIG_NL80211_TESTMODE -void ath6kl_tm_rx_report_event(struct ath6kl *ar, void *buf, size_t buf_len); -int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len); +void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, size_t buf_len); +int ath6kl_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, + void *data, int len); #else -static inline void ath6kl_tm_rx_report_event(struct ath6kl *ar, void *buf, - size_t buf_len) +static inline void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, + size_t buf_len) { } -static inline int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) +static inline int ath6kl_tm_cmd(struct wiphy *wiphy, + struct wireless_dev *wdev, + void *data, int len) { return 0; } diff --git a/drivers/net/wireless/ath/ath6kl/trace.c b/drivers/net/wireless/ath/ath6kl/trace.c new file mode 100644 index 00000000000..e7d64b1285c --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/trace.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/module.h> + +#define CREATE_TRACE_POINTS +#include "trace.h" + +EXPORT_TRACEPOINT_SYMBOL(ath6kl_sdio); +EXPORT_TRACEPOINT_SYMBOL(ath6kl_sdio_scat); diff --git a/drivers/net/wireless/ath/ath6kl/trace.h b/drivers/net/wireless/ath/ath6kl/trace.h new file mode 100644 index 00000000000..1a1ea7881b4 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/trace.h @@ -0,0 +1,332 @@ +#if !defined(_ATH6KL_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) + +#include <net/cfg80211.h> +#include <linux/skbuff.h> +#include <linux/tracepoint.h> +#include "wmi.h" +#include "hif.h" + +#if !defined(_ATH6KL_TRACE_H) +static inline unsigned int ath6kl_get_wmi_id(void *buf, size_t buf_len) +{ + struct wmi_cmd_hdr *hdr = buf; + + if (buf_len < sizeof(*hdr)) + return 0; + + return le16_to_cpu(hdr->cmd_id); +} +#endif /* __ATH6KL_TRACE_H */ + +#define _ATH6KL_TRACE_H + +/* create empty functions when tracing is disabled */ +#if !defined(CONFIG_ATH6KL_TRACING) +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif /* !CONFIG_ATH6KL_TRACING || __CHECKER__ */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ath6kl + +TRACE_EVENT(ath6kl_wmi_cmd, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, id) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->id = ath6kl_get_wmi_id(buf, buf_len); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "id %d len %zd", + __entry->id, __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_wmi_event, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, id) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->id = ath6kl_get_wmi_id(buf, buf_len); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "id %d len %zd", + __entry->id, __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_sdio, + TP_PROTO(unsigned int addr, int flags, + void *buf, size_t buf_len), + + TP_ARGS(addr, flags, buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, tx) + __field(unsigned int, addr) + __field(int, flags) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->addr = addr; + __entry->flags = flags; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + + if (flags & HIF_WRITE) + __entry->tx = 1; + else + __entry->tx = 0; + ), + + TP_printk( + "%s addr 0x%x flags 0x%x len %zd\n", + __entry->tx ? "tx" : "rx", + __entry->addr, + __entry->flags, + __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_sdio_scat, + TP_PROTO(unsigned int addr, int flags, unsigned int total_len, + unsigned int entries, struct hif_scatter_item *list), + + TP_ARGS(addr, flags, total_len, entries, list), + + TP_STRUCT__entry( + __field(unsigned int, tx) + __field(unsigned int, addr) + __field(int, flags) + __field(unsigned int, entries) + __field(size_t, total_len) + __dynamic_array(unsigned int, len_array, entries) + __dynamic_array(u8, data, total_len) + ), + + TP_fast_assign( + unsigned int *len_array; + int i, offset = 0; + size_t len; + + __entry->addr = addr; + __entry->flags = flags; + __entry->entries = entries; + __entry->total_len = total_len; + + if (flags & HIF_WRITE) + __entry->tx = 1; + else + __entry->tx = 0; + + len_array = __get_dynamic_array(len_array); + + for (i = 0; i < entries; i++) { + len = list[i].len; + + memcpy((u8 *) __get_dynamic_array(data) + offset, + list[i].buf, len); + + len_array[i] = len; + offset += len; + } + ), + + TP_printk( + "%s addr 0x%x flags 0x%x entries %d total_len %zd\n", + __entry->tx ? "tx" : "rx", + __entry->addr, + __entry->flags, + __entry->entries, + __entry->total_len + ) +); + +TRACE_EVENT(ath6kl_sdio_irq, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "irq len %zd\n", __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_htc_rx, + TP_PROTO(int status, int endpoint, void *buf, + size_t buf_len), + + TP_ARGS(status, endpoint, buf, buf_len), + + TP_STRUCT__entry( + __field(int, status) + __field(int, endpoint) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->status = status; + __entry->endpoint = endpoint; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "status %d endpoint %d len %zd\n", + __entry->status, + __entry->endpoint, + __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_htc_tx, + TP_PROTO(int status, int endpoint, void *buf, + size_t buf_len), + + TP_ARGS(status, endpoint, buf, buf_len), + + TP_STRUCT__entry( + __field(int, status) + __field(int, endpoint) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->status = status; + __entry->endpoint = endpoint; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "status %d endpoint %d len %zd\n", + __entry->status, + __entry->endpoint, + __entry->buf_len + ) +); + +#define ATH6KL_MSG_MAX 200 + +DECLARE_EVENT_CLASS(ath6kl_log_event, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry( + __dynamic_array(char, msg, ATH6KL_MSG_MAX) + ), + TP_fast_assign( + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + ATH6KL_MSG_MAX, + vaf->fmt, + *vaf->va) >= ATH6KL_MSG_MAX); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_err, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_warn, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_info, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +TRACE_EVENT(ath6kl_log_dbg, + TP_PROTO(unsigned int level, struct va_format *vaf), + TP_ARGS(level, vaf), + TP_STRUCT__entry( + __field(unsigned int, level) + __dynamic_array(char, msg, ATH6KL_MSG_MAX) + ), + TP_fast_assign( + __entry->level = level; + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + ATH6KL_MSG_MAX, + vaf->fmt, + *vaf->va) >= ATH6KL_MSG_MAX); + ), + TP_printk("%s", __get_str(msg)) +); + +TRACE_EVENT(ath6kl_log_dbg_dump, + TP_PROTO(const char *msg, const char *prefix, + const void *buf, size_t buf_len), + + TP_ARGS(msg, prefix, buf, buf_len), + + TP_STRUCT__entry( + __string(msg, msg) + __string(prefix, prefix) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __assign_str(msg, msg); + __assign_str(prefix, prefix); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "%s/%s\n", __get_str(prefix), __get_str(msg) + ) +); + +#endif /* _ ATH6KL_TRACE_H || TRACE_HEADER_MULTI_READ*/ + +/* we don't want to use include/trace/events */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c index 506a3031a88..40432fe7a5d 100644 --- a/drivers/net/wireless/ath/ath6kl/txrx.c +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,8 +15,29 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include "core.h" #include "debug.h" +#include "htc-ops.h" +#include "trace.h" + +/* + * tid - tid_mux0..tid_mux3 + * aid - tid_mux4..tid_mux7 + */ +#define ATH6KL_TID_MASK 0xf +#define ATH6KL_AID_SHIFT 4 + +static inline u8 ath6kl_get_tid(u8 tid_mux) +{ + return tid_mux & ATH6KL_TID_MASK; +} + +static inline u8 ath6kl_get_aid(u8 tid_mux) +{ + return tid_mux >> ATH6KL_AID_SHIFT; +} static u8 ath6kl_ibss_map_epid(struct sk_buff *skb, struct net_device *dev, u32 *map_no) @@ -77,12 +99,119 @@ static u8 ath6kl_ibss_map_epid(struct sk_buff *skb, struct net_device *dev, return ar->node_map[ep_map].ep_id; } +static bool ath6kl_process_uapsdq(struct ath6kl_sta *conn, + struct ath6kl_vif *vif, + struct sk_buff *skb, + u32 *flags) +{ + struct ath6kl *ar = vif->ar; + bool is_apsdq_empty = false; + struct ethhdr *datap = (struct ethhdr *) skb->data; + u8 up = 0, traffic_class, *ip_hdr; + u16 ether_type; + struct ath6kl_llc_snap_hdr *llc_hdr; + + if (conn->sta_flags & STA_PS_APSD_TRIGGER) { + /* + * This tx is because of a uAPSD trigger, determine + * more and EOSP bit. Set EOSP if queue is empty + * or sufficient frames are delivered for this trigger. + */ + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->apsdq)) + *flags |= WMI_DATA_HDR_FLAGS_MORE; + else if (conn->sta_flags & STA_PS_APSD_EOSP) + *flags |= WMI_DATA_HDR_FLAGS_EOSP; + *flags |= WMI_DATA_HDR_FLAGS_UAPSD; + spin_unlock_bh(&conn->psq_lock); + return false; + } else if (!conn->apsd_info) { + return false; + } + + if (test_bit(WMM_ENABLED, &vif->flags)) { + ether_type = be16_to_cpu(datap->h_proto); + if (is_ethertype(ether_type)) { + /* packet is in DIX format */ + ip_hdr = (u8 *)(datap + 1); + } else { + /* packet is in 802.3 format */ + llc_hdr = (struct ath6kl_llc_snap_hdr *) + (datap + 1); + ether_type = be16_to_cpu(llc_hdr->eth_type); + ip_hdr = (u8 *)(llc_hdr + 1); + } + + if (ether_type == IP_ETHERTYPE) + up = ath6kl_wmi_determine_user_priority( + ip_hdr, 0); + } + + traffic_class = ath6kl_wmi_get_traffic_class(up); + + if ((conn->apsd_info & (1 << traffic_class)) == 0) + return false; + + /* Queue the frames if the STA is sleeping */ + spin_lock_bh(&conn->psq_lock); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + skb_queue_tail(&conn->apsdq, skb); + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this STA + */ + if (is_apsdq_empty) { + ath6kl_wmi_set_apsd_bfrd_traf(ar->wmi, + vif->fw_vif_idx, + conn->aid, 1, 0); + } + *flags |= WMI_DATA_HDR_FLAGS_UAPSD; + + return true; +} + +static bool ath6kl_process_psq(struct ath6kl_sta *conn, + struct ath6kl_vif *vif, + struct sk_buff *skb, + u32 *flags) +{ + bool is_psq_empty = false; + struct ath6kl *ar = vif->ar; + + if (conn->sta_flags & STA_PS_POLLED) { + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->psq)) + *flags |= WMI_DATA_HDR_FLAGS_MORE; + spin_unlock_bh(&conn->psq_lock); + return false; + } + + /* Queue the frames if the STA is sleeping */ + spin_lock_bh(&conn->psq_lock); + is_psq_empty = skb_queue_empty(&conn->psq); + skb_queue_tail(&conn->psq, skb); + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this + * STA. + */ + if (is_psq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, + vif->fw_vif_idx, + conn->aid, 1); + return true; +} + static bool ath6kl_powersave_ap(struct ath6kl_vif *vif, struct sk_buff *skb, - bool *more_data) + u32 *flags) { struct ethhdr *datap = (struct ethhdr *) skb->data; struct ath6kl_sta *conn = NULL; - bool ps_queued = false, is_psq_empty = false; + bool ps_queued = false; struct ath6kl *ar = vif->ar; if (is_multicast_ether_addr(datap->h_dest)) { @@ -128,7 +257,7 @@ static bool ath6kl_powersave_ap(struct ath6kl_vif *vif, struct sk_buff *skb, */ spin_lock_bh(&ar->mcastpsq_lock); if (!skb_queue_empty(&ar->mcastpsq)) - *more_data = true; + *flags |= WMI_DATA_HDR_FLAGS_MORE; spin_unlock_bh(&ar->mcastpsq_lock); } } @@ -142,37 +271,13 @@ static bool ath6kl_powersave_ap(struct ath6kl_vif *vif, struct sk_buff *skb, } if (conn->sta_flags & STA_PS_SLEEP) { - if (!(conn->sta_flags & STA_PS_POLLED)) { - /* Queue the frames if the STA is sleeping */ - spin_lock_bh(&conn->psq_lock); - is_psq_empty = skb_queue_empty(&conn->psq); - skb_queue_tail(&conn->psq, skb); - spin_unlock_bh(&conn->psq_lock); - - /* - * If this is the first pkt getting queued - * for this STA, update the PVB for this - * STA. - */ - if (is_psq_empty) - ath6kl_wmi_set_pvb_cmd(ar->wmi, - vif->fw_vif_idx, - conn->aid, 1); - - ps_queued = true; - } else { - /* - * This tx is because of a PsPoll. - * Determine if MoreData bit has to be set. - */ - spin_lock_bh(&conn->psq_lock); - if (!skb_queue_empty(&conn->psq)) - *more_data = true; - spin_unlock_bh(&conn->psq_lock); - } + ps_queued = ath6kl_process_uapsdq(conn, + vif, skb, flags); + if (!(*flags & WMI_DATA_HDR_FLAGS_UAPSD)) + ps_queued = ath6kl_process_psq(conn, + vif, skb, flags); } } - return ps_queued; } @@ -185,6 +290,19 @@ int ath6kl_control_tx(void *devt, struct sk_buff *skb, int status = 0; struct ath6kl_cookie *cookie = NULL; + trace_ath6kl_wmi_cmd(skb->data, skb->len); + + if (WARN_ON_ONCE(ar->state == ATH6KL_STATE_WOW)) { + dev_kfree_skb(skb); + return -EACCES; + } + + if (WARN_ON_ONCE(eid == ENDPOINT_UNUSED || + eid >= ENDPOINT_MAX)) { + status = -EINVAL; + goto fail_ctrl_tx; + } + spin_lock_bh(&ar->lock); ath6kl_dbg(ATH6KL_DBG_WLAN_TX, @@ -199,8 +317,9 @@ int ath6kl_control_tx(void *devt, struct sk_buff *skb, cookie = NULL; ath6kl_err("wmi ctrl ep full, dropping pkt : 0x%p, len:%d\n", skb, skb->len); - } else + } else { cookie = ath6kl_alloc_cookie(ar); + } if (cookie == NULL) { spin_unlock_bh(&ar->lock); @@ -219,6 +338,7 @@ int ath6kl_control_tx(void *devt, struct sk_buff *skb, cookie->map_no = 0; set_htc_pkt_info(&cookie->htc_pkt, cookie, skb->data, skb->len, eid, ATH6KL_CONTROL_PKT_TAG); + cookie->htc_pkt.skb = skb; /* * This interface is asynchronous, if there is an error, cleanup @@ -241,30 +361,44 @@ int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev) struct ath6kl_vif *vif = netdev_priv(dev); u32 map_no = 0; u16 htc_tag = ATH6KL_DATA_PKT_TAG; - u8 ac = 99 ; /* initialize to unmapped ac */ - bool chk_adhoc_ps_mapping = false, more_data = false; + u8 ac = 99; /* initialize to unmapped ac */ + bool chk_adhoc_ps_mapping = false; int ret; + struct wmi_tx_meta_v2 meta_v2; + void *meta; + u8 csum_start = 0, csum_dest = 0, csum = skb->ip_summed; + u8 meta_ver = 0; + u32 flags = 0; ath6kl_dbg(ATH6KL_DBG_WLAN_TX, "%s: skb=0x%p, data=0x%p, len=0x%x\n", __func__, skb, skb->data, skb->len); /* If target is not associated */ - if (!test_bit(CONNECTED, &vif->flags)) { - dev_kfree_skb(skb); - return 0; - } + if (!test_bit(CONNECTED, &vif->flags)) + goto fail_tx; + + if (WARN_ON_ONCE(ar->state != ATH6KL_STATE_ON)) + goto fail_tx; if (!test_bit(WMI_READY, &ar->flag)) goto fail_tx; /* AP mode Power saving processing */ if (vif->nw_type == AP_NETWORK) { - if (ath6kl_powersave_ap(vif, skb, &more_data)) + if (ath6kl_powersave_ap(vif, skb, &flags)) return 0; } if (test_bit(WMI_ENABLED, &ar->flag)) { + if ((dev->features & NETIF_F_IP_CSUM) && + (csum == CHECKSUM_PARTIAL)) { + csum_start = skb->csum_start - + (skb_network_header(skb) - skb->head) + + sizeof(struct ath6kl_llc_snap_hdr); + csum_dest = skb->csum_offset + csum_start; + } + if (skb_headroom(skb) < dev->needed_headroom) { struct sk_buff *tmp_skb = skb; @@ -281,15 +415,33 @@ int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev) goto fail_tx; } - if (ath6kl_wmi_data_hdr_add(ar->wmi, skb, DATA_MSGTYPE, - more_data, 0, 0, NULL, - vif->fw_vif_idx)) { - ath6kl_err("wmi_data_hdr_add failed\n"); + if ((dev->features & NETIF_F_IP_CSUM) && + (csum == CHECKSUM_PARTIAL)) { + meta_v2.csum_start = csum_start; + meta_v2.csum_dest = csum_dest; + + /* instruct target to calculate checksum */ + meta_v2.csum_flags = WMI_META_V2_FLAG_CSUM_OFFLOAD; + meta_ver = WMI_META_VERSION_2; + meta = &meta_v2; + } else { + meta_ver = 0; + meta = NULL; + } + + ret = ath6kl_wmi_data_hdr_add(ar->wmi, skb, + DATA_MSGTYPE, flags, 0, + meta_ver, + meta, vif->fw_vif_idx); + + if (ret) { + ath6kl_warn("failed to add wmi data header:%d\n" + , ret); goto fail_tx; } if ((vif->nw_type == ADHOC_NETWORK) && - ar->ibss_ps_enable && test_bit(CONNECTED, &vif->flags)) + ar->ibss_ps_enable && test_bit(CONNECTED, &vif->flags)) chk_adhoc_ps_mapping = true; else { /* get the stream mapping */ @@ -299,8 +451,9 @@ int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev) if (ret) goto fail_tx; } - } else + } else { goto fail_tx; + } spin_lock_bh(&ar->lock); @@ -351,6 +504,7 @@ int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev) cookie->map_no = map_no; set_htc_pkt_info(&cookie->htc_pkt, cookie, skb->data, skb->len, eid, htc_tag); + cookie->htc_pkt.skb = skb; ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "tx ", skb->data, skb->len); @@ -431,7 +585,7 @@ void ath6kl_indicate_tx_activity(void *devt, u8 traffic_class, bool active) notify_htc: /* notify HTC, this may cause credit distribution changes */ - ath6kl_htc_indicate_activity_change(ar->htc_target, eid, active); + ath6kl_htc_activity_changed(ar->htc_target, eid, active); } enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, @@ -449,10 +603,9 @@ enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, * WMI queue with too many commands the only exception to * this is during testing using endpointping. */ - spin_lock_bh(&ar->lock); set_bit(WMI_CTRL_EP_FULL, &ar->flag); - spin_unlock_bh(&ar->lock); ath6kl_err("wmi ctrl ep is full\n"); + ath6kl_recovery_err_notify(ar, ATH6KL_FW_EP_FULL); return action; } @@ -465,7 +618,8 @@ enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, */ if (ar->ac_stream_pri_map[ar->ep2ac_map[endpoint]] < ar->hiac_stream_active_pri && - ar->cookie_count <= MAX_HI_COOKIE_NUM) + ar->cookie_count <= + target->endpoint[endpoint].tx_drop_packet_threshold) /* * Give preference to the highest priority stream by * dropping the packets which overflowed. @@ -479,9 +633,7 @@ enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, action != HTC_SEND_FULL_DROP) { spin_unlock_bh(&ar->list_lock); - spin_lock_bh(&vif->if_lock); set_bit(NETQ_STOPPED, &vif->flags); - spin_unlock_bh(&vif->if_lock); netif_stop_queue(vif->ndev); return action; @@ -530,9 +682,10 @@ static void ath6kl_tx_clear_node_map(struct ath6kl_vif *vif, } } -void ath6kl_tx_complete(void *context, struct list_head *packet_queue) +void ath6kl_tx_complete(struct htc_target *target, + struct list_head *packet_queue) { - struct ath6kl *ar = context; + struct ath6kl *ar = target->dev->ar; struct sk_buff_head skb_queue; struct htc_packet *packet; struct sk_buff *skb; @@ -552,27 +705,35 @@ void ath6kl_tx_complete(void *context, struct list_head *packet_queue) /* reap completed packets */ while (!list_empty(packet_queue)) { - packet = list_first_entry(packet_queue, struct htc_packet, list); list_del(&packet->list); + if (WARN_ON_ONCE(packet->endpoint == ENDPOINT_UNUSED || + packet->endpoint >= ENDPOINT_MAX)) + continue; + ath6kl_cookie = (struct ath6kl_cookie *)packet->pkt_cntxt; - if (!ath6kl_cookie) - goto fatal; + if (WARN_ON_ONCE(!ath6kl_cookie)) + continue; status = packet->status; skb = ath6kl_cookie->skb; eid = packet->endpoint; map_no = ath6kl_cookie->map_no; - if (!skb || !skb->data) - goto fatal; + if (WARN_ON_ONCE(!skb || !skb->data)) { + dev_kfree_skb(skb); + ath6kl_free_cookie(ar, ath6kl_cookie); + continue; + } __skb_queue_tail(&skb_queue, skb); - if (!status && (packet->act_len != skb->len)) - goto fatal; + if (WARN_ON_ONCE(!status && (packet->act_len != skb->len))) { + ath6kl_free_cookie(ar, ath6kl_cookie); + continue; + } ar->tx_pending[eid]--; @@ -654,11 +815,6 @@ void ath6kl_tx_complete(void *context, struct list_head *packet_queue) wake_up(&ar->event_wq); return; - -fatal: - WARN_ON(1); - spin_unlock_bh(&ar->lock); - return; } void ath6kl_tx_data_cleanup(struct ath6kl *ar) @@ -710,10 +866,12 @@ static struct sk_buff *aggr_get_free_skb(struct aggr_info *p_aggr) { struct sk_buff *skb = NULL; - if (skb_queue_len(&p_aggr->free_q) < (AGGR_NUM_OF_FREE_NETBUFS >> 2)) - ath6kl_alloc_netbufs(&p_aggr->free_q, AGGR_NUM_OF_FREE_NETBUFS); + if (skb_queue_len(&p_aggr->rx_amsdu_freeq) < + (AGGR_NUM_OF_FREE_NETBUFS >> 2)) + ath6kl_alloc_netbufs(&p_aggr->rx_amsdu_freeq, + AGGR_NUM_OF_FREE_NETBUFS); - skb = skb_dequeue(&p_aggr->free_q); + skb = skb_dequeue(&p_aggr->rx_amsdu_freeq); return skb; } @@ -745,10 +903,14 @@ void ath6kl_rx_refill(struct htc_target *target, enum htc_endpoint_id endpoint) break; packet = (struct htc_packet *) skb->head; - if (!IS_ALIGNED((unsigned long) skb->data, 4)) + if (!IS_ALIGNED((unsigned long) skb->data, 4)) { + size_t len = skb_headlen(skb); skb->data = PTR_ALIGN(skb->data - 4, 4); + skb_set_tail_pointer(skb, len); + } set_htc_rxpkt_info(packet, skb, skb->data, - ATH6KL_BUFFER_SIZE, endpoint); + ATH6KL_BUFFER_SIZE, endpoint); + packet->skb = skb; list_add_tail(&packet->list, &queue); } @@ -767,10 +929,15 @@ void ath6kl_refill_amsdu_rxbufs(struct ath6kl *ar, int count) return; packet = (struct htc_packet *) skb->head; - if (!IS_ALIGNED((unsigned long) skb->data, 4)) + if (!IS_ALIGNED((unsigned long) skb->data, 4)) { + size_t len = skb_headlen(skb); skb->data = PTR_ALIGN(skb->data - 4, 4); + skb_set_tail_pointer(skb, len); + } set_htc_rxpkt_info(packet, skb, skb->data, ATH6KL_AMSDU_BUFFER_SIZE, 0); + packet->skb = skb; + spin_lock_bh(&ar->lock); list_add_tail(&packet->list, &ar->amsdu_rx_buffer_queue); spin_unlock_bh(&ar->lock); @@ -881,7 +1048,7 @@ static void aggr_slice_amsdu(struct aggr_info *p_aggr, dev_kfree_skb(skb); } -static void aggr_deque_frms(struct aggr_info *p_aggr, u8 tid, +static void aggr_deque_frms(struct aggr_info_conn *agg_conn, u8 tid, u16 seq_no, u8 order) { struct sk_buff *skb; @@ -890,12 +1057,10 @@ static void aggr_deque_frms(struct aggr_info *p_aggr, u8 tid, u16 idx, idx_end, seq_end; struct rxtid_stats *stats; - if (!p_aggr) - return; - - rxtid = &p_aggr->rx_tid[tid]; - stats = &p_aggr->stat[tid]; + rxtid = &agg_conn->rx_tid[tid]; + stats = &agg_conn->stat[tid]; + spin_lock_bh(&rxtid->lock); idx = AGGR_WIN_IDX(rxtid->seq_next, rxtid->hold_q_sz); /* @@ -914,8 +1079,6 @@ static void aggr_deque_frms(struct aggr_info *p_aggr, u8 tid, seq_end = seq_no ? seq_no : rxtid->seq_next; idx_end = AGGR_WIN_IDX(seq_end, rxtid->hold_q_sz); - spin_lock_bh(&rxtid->lock); - do { node = &rxtid->hold_q[idx]; if ((order == 1) && (!node->skb)) @@ -923,12 +1086,14 @@ static void aggr_deque_frms(struct aggr_info *p_aggr, u8 tid, if (node->skb) { if (node->is_amsdu) - aggr_slice_amsdu(p_aggr, rxtid, node->skb); + aggr_slice_amsdu(agg_conn->aggr_info, rxtid, + node->skb); else skb_queue_tail(&rxtid->q, node->skb); node->skb = NULL; - } else + } else { stats->num_hole++; + } rxtid->seq_next = ATH6KL_NEXT_SEQ_NO(rxtid->seq_next); idx = AGGR_WIN_IDX(rxtid->seq_next, rxtid->hold_q_sz); @@ -939,10 +1104,10 @@ static void aggr_deque_frms(struct aggr_info *p_aggr, u8 tid, stats->num_delivered += skb_queue_len(&rxtid->q); while ((skb = skb_dequeue(&rxtid->q))) - ath6kl_deliver_frames_to_nw_stack(p_aggr->dev, skb); + ath6kl_deliver_frames_to_nw_stack(agg_conn->dev, skb); } -static bool aggr_process_recv_frm(struct aggr_info *agg_info, u8 tid, +static bool aggr_process_recv_frm(struct aggr_info_conn *agg_conn, u8 tid, u16 seq_no, bool is_amsdu, struct sk_buff *frame) { @@ -954,18 +1119,18 @@ static bool aggr_process_recv_frm(struct aggr_info *agg_info, u8 tid, bool is_queued = false; u16 extended_end; - rxtid = &agg_info->rx_tid[tid]; - stats = &agg_info->stat[tid]; + rxtid = &agg_conn->rx_tid[tid]; + stats = &agg_conn->stat[tid]; stats->num_into_aggr++; if (!rxtid->aggr) { if (is_amsdu) { - aggr_slice_amsdu(agg_info, rxtid, frame); + aggr_slice_amsdu(agg_conn->aggr_info, rxtid, frame); is_queued = true; stats->num_amsdu++; while ((skb = skb_dequeue(&rxtid->q))) - ath6kl_deliver_frames_to_nw_stack(agg_info->dev, + ath6kl_deliver_frames_to_nw_stack(agg_conn->dev, skb); } return is_queued; @@ -985,12 +1150,14 @@ static bool aggr_process_recv_frm(struct aggr_info *agg_info, u8 tid, (cur < end || cur > extended_end)) || ((end > extended_end) && (cur > extended_end) && (cur < end))) { - aggr_deque_frms(agg_info, tid, 0, 0); + aggr_deque_frms(agg_conn, tid, 0, 0); + spin_lock_bh(&rxtid->lock); if (cur >= rxtid->hold_q_sz - 1) rxtid->seq_next = cur - (rxtid->hold_q_sz - 1); else rxtid->seq_next = ATH6KL_MAX_SEQ_NO - (rxtid->hold_q_sz - 2 - cur); + spin_unlock_bh(&rxtid->lock); } else { /* * Dequeue only those frames that are outside the @@ -1002,7 +1169,7 @@ static bool aggr_process_recv_frm(struct aggr_info *agg_info, u8 tid, st = ATH6KL_MAX_SEQ_NO - (rxtid->hold_q_sz - 2 - cur); - aggr_deque_frms(agg_info, tid, st, 0); + aggr_deque_frms(agg_conn, tid, st, 0); } stats->num_oow++; @@ -1041,32 +1208,101 @@ static bool aggr_process_recv_frm(struct aggr_info *agg_info, u8 tid, spin_unlock_bh(&rxtid->lock); - aggr_deque_frms(agg_info, tid, 0, 1); + aggr_deque_frms(agg_conn, tid, 0, 1); - if (agg_info->timer_scheduled) - rxtid->progress = true; - else - for (idx = 0 ; idx < rxtid->hold_q_sz; idx++) { - if (rxtid->hold_q[idx].skb) { - /* - * There is a frame in the queue and no - * timer so start a timer to ensure that - * the frame doesn't remain stuck - * forever. - */ - agg_info->timer_scheduled = true; - mod_timer(&agg_info->timer, - (jiffies + - HZ * (AGGR_RX_TIMEOUT) / 1000)); - rxtid->progress = false; - rxtid->timer_mon = true; - break; - } + if (agg_conn->timer_scheduled) + return is_queued; + + spin_lock_bh(&rxtid->lock); + for (idx = 0; idx < rxtid->hold_q_sz; idx++) { + if (rxtid->hold_q[idx].skb) { + /* + * There is a frame in the queue and no + * timer so start a timer to ensure that + * the frame doesn't remain stuck + * forever. + */ + agg_conn->timer_scheduled = true; + mod_timer(&agg_conn->timer, + (jiffies + (HZ * AGGR_RX_TIMEOUT) / 1000)); + rxtid->timer_mon = true; + break; } + } + spin_unlock_bh(&rxtid->lock); return is_queued; } +static void ath6kl_uapsd_trigger_frame_rx(struct ath6kl_vif *vif, + struct ath6kl_sta *conn) +{ + struct ath6kl *ar = vif->ar; + bool is_apsdq_empty, is_apsdq_empty_at_start; + u32 num_frames_to_deliver, flags; + struct sk_buff *skb = NULL; + + /* + * If the APSD q for this STA is not empty, dequeue and + * send a pkt from the head of the q. Also update the + * More data bit in the WMI_DATA_HDR if there are + * more pkts for this STA in the APSD q. + * If there are no more pkts for this STA, + * update the APSD bitmap for this STA. + */ + + num_frames_to_deliver = (conn->apsd_info >> ATH6KL_APSD_NUM_OF_AC) & + ATH6KL_APSD_FRAME_MASK; + /* + * Number of frames to send in a service period is + * indicated by the station + * in the QOS_INFO of the association request + * If it is zero, send all frames + */ + if (!num_frames_to_deliver) + num_frames_to_deliver = ATH6KL_APSD_ALL_FRAME; + + spin_lock_bh(&conn->psq_lock); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + spin_unlock_bh(&conn->psq_lock); + is_apsdq_empty_at_start = is_apsdq_empty; + + while ((!is_apsdq_empty) && (num_frames_to_deliver)) { + spin_lock_bh(&conn->psq_lock); + skb = skb_dequeue(&conn->apsdq); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + spin_unlock_bh(&conn->psq_lock); + + /* + * Set the STA flag to Trigger delivery, + * so that the frame will go out + */ + conn->sta_flags |= STA_PS_APSD_TRIGGER; + num_frames_to_deliver--; + + /* Last frame in the service period, set EOSP or queue empty */ + if ((is_apsdq_empty) || (!num_frames_to_deliver)) + conn->sta_flags |= STA_PS_APSD_EOSP; + + ath6kl_data_tx(skb, vif->ndev); + conn->sta_flags &= ~(STA_PS_APSD_TRIGGER); + conn->sta_flags &= ~(STA_PS_APSD_EOSP); + } + + if (is_apsdq_empty) { + if (is_apsdq_empty_at_start) + flags = WMI_AP_APSD_NO_DELIVERY_FRAMES; + else + flags = 0; + + ath6kl_wmi_set_apsd_bfrd_traf(ar->wmi, + vif->fw_vif_idx, + conn->aid, 0, flags); + } + + return; +} + void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) { struct ath6kl *ar = target->dev->ar; @@ -1075,13 +1311,16 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) struct wmi_data_hdr *dhdr; int min_hdr_len; u8 meta_type, dot11_hdr = 0; + u8 pad_before_data_start; int status = packet->status; enum htc_endpoint_id ept = packet->endpoint; bool is_amsdu, prev_ps, ps_state = false; + bool trig_state = false; struct ath6kl_sta *conn = NULL; struct sk_buff *skb1 = NULL; struct ethhdr *datap = NULL; struct ath6kl_vif *vif; + struct aggr_info_conn *aggr_conn; u16 seq_no, offset; u8 tid, if_idx; @@ -1090,7 +1329,7 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) __func__, ar, ept, skb, packet->buf, packet->act_len, status); - if (status || !(skb->data + HTC_HDR_LENGTH)) { + if (status || packet->act_len < HTC_HDR_LENGTH) { dev_kfree_skb(skb); return; } @@ -1098,7 +1337,15 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) skb_put(skb, packet->act_len + HTC_HDR_LENGTH); skb_pull(skb, HTC_HDR_LENGTH); + ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "rx ", + skb->data, skb->len); + if (ept == ar->ctrl_ep) { + if (test_bit(WMI_ENABLED, &ar->flag)) { + ath6kl_check_wow_status(ar); + ath6kl_wmi_control_rx(ar->wmi, skb); + return; + } if_idx = wmi_cmd_hdr_get_if_idx((struct wmi_cmd_hdr *) skb->data); } else { @@ -1123,10 +1370,6 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) spin_unlock_bh(&vif->if_lock); - - ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "rx ", - skb->data, skb->len); - skb->dev = vif->ndev; if (!test_bit(WMI_ENABLED, &ar->flag)) { @@ -1138,11 +1381,6 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) ath6kl_check_wow_status(ar); - if (ept == ar->ctrl_ep) { - ath6kl_wmi_control_rx(ar->wmi, skb); - return; - } - min_hdr_len = sizeof(struct ethhdr) + sizeof(struct wmi_data_hdr) + sizeof(struct ath6kl_llc_snap_hdr); @@ -1171,6 +1409,7 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) WMI_DATA_HDR_PS_MASK); offset = sizeof(struct wmi_data_hdr); + trig_state = !!(le16_to_cpu(dhdr->info3) & WMI_DATA_HDR_TRIG); switch (meta_type) { case 0: @@ -1209,18 +1448,61 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) else conn->sta_flags &= ~STA_PS_SLEEP; + /* Accept trigger only when the station is in sleep */ + if ((conn->sta_flags & STA_PS_SLEEP) && trig_state) + ath6kl_uapsd_trigger_frame_rx(vif, conn); + if (prev_ps ^ !!(conn->sta_flags & STA_PS_SLEEP)) { if (!(conn->sta_flags & STA_PS_SLEEP)) { struct sk_buff *skbuff = NULL; + bool is_apsdq_empty; + struct ath6kl_mgmt_buff *mgmt; + u8 idx; spin_lock_bh(&conn->psq_lock); - while ((skbuff = skb_dequeue(&conn->psq)) - != NULL) { + while (conn->mgmt_psq_len > 0) { + mgmt = list_first_entry( + &conn->mgmt_psq, + struct ath6kl_mgmt_buff, + list); + list_del(&mgmt->list); + conn->mgmt_psq_len--; + spin_unlock_bh(&conn->psq_lock); + idx = vif->fw_vif_idx; + + ath6kl_wmi_send_mgmt_cmd(ar->wmi, + idx, + mgmt->id, + mgmt->freq, + mgmt->wait, + mgmt->buf, + mgmt->len, + mgmt->no_cck); + + kfree(mgmt); + spin_lock_bh(&conn->psq_lock); + } + conn->mgmt_psq_len = 0; + while ((skbuff = skb_dequeue(&conn->psq))) { + spin_unlock_bh(&conn->psq_lock); + ath6kl_data_tx(skbuff, vif->ndev); + spin_lock_bh(&conn->psq_lock); + } + + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + while ((skbuff = skb_dequeue(&conn->apsdq))) { spin_unlock_bh(&conn->psq_lock); ath6kl_data_tx(skbuff, vif->ndev); spin_lock_bh(&conn->psq_lock); } spin_unlock_bh(&conn->psq_lock); + + if (!is_apsdq_empty) + ath6kl_wmi_set_apsd_bfrd_traf( + ar->wmi, + vif->fw_vif_idx, + conn->aid, 0, 0); + /* Clear the PVB for this STA */ ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, conn->aid, 0); @@ -1241,6 +1523,10 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) seq_no = wmi_data_hdr_get_seqno(dhdr); meta_type = wmi_data_hdr_get_meta(dhdr); dot11_hdr = wmi_data_hdr_get_dot11(dhdr); + pad_before_data_start = + (le16_to_cpu(dhdr->info3) >> WMI_DATA_HDR_PAD_BEFORE_DATA_SHIFT) + & WMI_DATA_HDR_PAD_BEFORE_DATA_MASK; + skb_pull(skb, sizeof(struct wmi_data_hdr)); switch (meta_type) { @@ -1259,6 +1545,8 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) break; } + skb_pull(skb, pad_before_data_start); + if (dot11_hdr) status = ath6kl_wmi_dot11_hdr_remove(ar->wmi, skb); else if (!is_amsdu) @@ -1314,11 +1602,24 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) datap = (struct ethhdr *) skb->data; - if (is_unicast_ether_addr(datap->h_dest) && - aggr_process_recv_frm(vif->aggr_cntxt, tid, seq_no, - is_amsdu, skb)) - /* aggregation code will handle the skb */ - return; + if (is_unicast_ether_addr(datap->h_dest)) { + if (vif->nw_type == AP_NETWORK) { + conn = ath6kl_find_sta(vif, datap->h_source); + if (!conn) + return; + aggr_conn = conn->aggr_conn; + } else { + aggr_conn = vif->aggr_cntxt->aggr_conn; + } + + if (aggr_process_recv_frm(aggr_conn, tid, seq_no, + is_amsdu, skb)) { + /* aggregation code will handle the skb */ + return; + } + } else if (!is_broadcast_ether_addr(datap->h_dest)) { + vif->net_stats.multicast++; + } ath6kl_deliver_frames_to_nw_stack(vif->ndev, skb); } @@ -1326,15 +1627,15 @@ void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) static void aggr_timeout(unsigned long arg) { u8 i, j; - struct aggr_info *p_aggr = (struct aggr_info *) arg; + struct aggr_info_conn *aggr_conn = (struct aggr_info_conn *) arg; struct rxtid *rxtid; struct rxtid_stats *stats; for (i = 0; i < NUM_OF_TIDS; i++) { - rxtid = &p_aggr->rx_tid[i]; - stats = &p_aggr->stat[i]; + rxtid = &aggr_conn->rx_tid[i]; + stats = &aggr_conn->stat[i]; - if (!rxtid->aggr || !rxtid->timer_mon || rxtid->progress) + if (!rxtid->aggr || !rxtid->timer_mon) continue; stats->num_timeouts++; @@ -1343,50 +1644,50 @@ static void aggr_timeout(unsigned long arg) rxtid->seq_next, ((rxtid->seq_next + rxtid->hold_q_sz-1) & ATH6KL_MAX_SEQ_NO)); - aggr_deque_frms(p_aggr, i, 0, 0); + aggr_deque_frms(aggr_conn, i, 0, 0); } - p_aggr->timer_scheduled = false; + aggr_conn->timer_scheduled = false; for (i = 0; i < NUM_OF_TIDS; i++) { - rxtid = &p_aggr->rx_tid[i]; + rxtid = &aggr_conn->rx_tid[i]; if (rxtid->aggr && rxtid->hold_q) { + spin_lock_bh(&rxtid->lock); for (j = 0; j < rxtid->hold_q_sz; j++) { if (rxtid->hold_q[j].skb) { - p_aggr->timer_scheduled = true; + aggr_conn->timer_scheduled = true; rxtid->timer_mon = true; - rxtid->progress = false; break; } } + spin_unlock_bh(&rxtid->lock); if (j >= rxtid->hold_q_sz) rxtid->timer_mon = false; } } - if (p_aggr->timer_scheduled) - mod_timer(&p_aggr->timer, + if (aggr_conn->timer_scheduled) + mod_timer(&aggr_conn->timer, jiffies + msecs_to_jiffies(AGGR_RX_TIMEOUT)); } -static void aggr_delete_tid_state(struct aggr_info *p_aggr, u8 tid) +static void aggr_delete_tid_state(struct aggr_info_conn *aggr_conn, u8 tid) { struct rxtid *rxtid; struct rxtid_stats *stats; - if (!p_aggr || tid >= NUM_OF_TIDS) + if (!aggr_conn || tid >= NUM_OF_TIDS) return; - rxtid = &p_aggr->rx_tid[tid]; - stats = &p_aggr->stat[tid]; + rxtid = &aggr_conn->rx_tid[tid]; + stats = &aggr_conn->stat[tid]; if (rxtid->aggr) - aggr_deque_frms(p_aggr, tid, 0, 0); + aggr_deque_frms(aggr_conn, tid, 0, 0); rxtid->aggr = false; - rxtid->progress = false; rxtid->timer_mon = false; rxtid->win_sz = 0; rxtid->seq_next = 0; @@ -1398,26 +1699,41 @@ static void aggr_delete_tid_state(struct aggr_info *p_aggr, u8 tid) memset(stats, 0, sizeof(struct rxtid_stats)); } -void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid, u16 seq_no, +void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid_mux, u16 seq_no, u8 win_sz) { - struct aggr_info *p_aggr = vif->aggr_cntxt; + struct ath6kl_sta *sta; + struct aggr_info_conn *aggr_conn = NULL; struct rxtid *rxtid; struct rxtid_stats *stats; u16 hold_q_size; + u8 tid, aid; + + if (vif->nw_type == AP_NETWORK) { + aid = ath6kl_get_aid(tid_mux); + sta = ath6kl_find_sta_by_aid(vif->ar, aid); + if (sta) + aggr_conn = sta->aggr_conn; + } else { + aggr_conn = vif->aggr_cntxt->aggr_conn; + } + + if (!aggr_conn) + return; - if (!p_aggr) + tid = ath6kl_get_tid(tid_mux); + if (tid >= NUM_OF_TIDS) return; - rxtid = &p_aggr->rx_tid[tid]; - stats = &p_aggr->stat[tid]; + rxtid = &aggr_conn->rx_tid[tid]; + stats = &aggr_conn->stat[tid]; if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX) ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n", __func__, win_sz, tid); if (rxtid->aggr) - aggr_delete_tid_state(p_aggr, tid); + aggr_delete_tid_state(aggr_conn, tid); rxtid->seq_next = seq_no; hold_q_size = TID_WINDOW_SZ(win_sz) * sizeof(struct skb_hold_q); @@ -1433,61 +1749,98 @@ void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid, u16 seq_no, rxtid->aggr = true; } -struct aggr_info *aggr_init(struct net_device *dev) +void aggr_conn_init(struct ath6kl_vif *vif, struct aggr_info *aggr_info, + struct aggr_info_conn *aggr_conn) { - struct aggr_info *p_aggr = NULL; struct rxtid *rxtid; u8 i; - p_aggr = kzalloc(sizeof(struct aggr_info), GFP_KERNEL); - if (!p_aggr) { - ath6kl_err("failed to alloc memory for aggr_node\n"); - return NULL; - } - - p_aggr->aggr_sz = AGGR_SZ_DEFAULT; - p_aggr->dev = dev; - init_timer(&p_aggr->timer); - p_aggr->timer.function = aggr_timeout; - p_aggr->timer.data = (unsigned long) p_aggr; + aggr_conn->aggr_sz = AGGR_SZ_DEFAULT; + aggr_conn->dev = vif->ndev; + init_timer(&aggr_conn->timer); + aggr_conn->timer.function = aggr_timeout; + aggr_conn->timer.data = (unsigned long) aggr_conn; + aggr_conn->aggr_info = aggr_info; - p_aggr->timer_scheduled = false; - skb_queue_head_init(&p_aggr->free_q); - - ath6kl_alloc_netbufs(&p_aggr->free_q, AGGR_NUM_OF_FREE_NETBUFS); + aggr_conn->timer_scheduled = false; for (i = 0; i < NUM_OF_TIDS; i++) { - rxtid = &p_aggr->rx_tid[i]; + rxtid = &aggr_conn->rx_tid[i]; rxtid->aggr = false; - rxtid->progress = false; rxtid->timer_mon = false; skb_queue_head_init(&rxtid->q); spin_lock_init(&rxtid->lock); } +} + +struct aggr_info *aggr_init(struct ath6kl_vif *vif) +{ + struct aggr_info *p_aggr = NULL; + + p_aggr = kzalloc(sizeof(struct aggr_info), GFP_KERNEL); + if (!p_aggr) { + ath6kl_err("failed to alloc memory for aggr_node\n"); + return NULL; + } + + p_aggr->aggr_conn = kzalloc(sizeof(struct aggr_info_conn), GFP_KERNEL); + if (!p_aggr->aggr_conn) { + ath6kl_err("failed to alloc memory for connection specific aggr info\n"); + kfree(p_aggr); + return NULL; + } + + aggr_conn_init(vif, p_aggr, p_aggr->aggr_conn); + + skb_queue_head_init(&p_aggr->rx_amsdu_freeq); + ath6kl_alloc_netbufs(&p_aggr->rx_amsdu_freeq, AGGR_NUM_OF_FREE_NETBUFS); return p_aggr; } -void aggr_recv_delba_req_evt(struct ath6kl_vif *vif, u8 tid) +void aggr_recv_delba_req_evt(struct ath6kl_vif *vif, u8 tid_mux) { - struct aggr_info *p_aggr = vif->aggr_cntxt; + struct ath6kl_sta *sta; struct rxtid *rxtid; + struct aggr_info_conn *aggr_conn = NULL; + u8 tid, aid; + + if (vif->nw_type == AP_NETWORK) { + aid = ath6kl_get_aid(tid_mux); + sta = ath6kl_find_sta_by_aid(vif->ar, aid); + if (sta) + aggr_conn = sta->aggr_conn; + } else { + aggr_conn = vif->aggr_cntxt->aggr_conn; + } + + if (!aggr_conn) + return; - if (!p_aggr) + tid = ath6kl_get_tid(tid_mux); + if (tid >= NUM_OF_TIDS) return; - rxtid = &p_aggr->rx_tid[tid]; + rxtid = &aggr_conn->rx_tid[tid]; if (rxtid->aggr) - aggr_delete_tid_state(p_aggr, tid); + aggr_delete_tid_state(aggr_conn, tid); } -void aggr_reset_state(struct aggr_info *aggr_info) +void aggr_reset_state(struct aggr_info_conn *aggr_conn) { u8 tid; + if (!aggr_conn) + return; + + if (aggr_conn->timer_scheduled) { + del_timer(&aggr_conn->timer); + aggr_conn->timer_scheduled = false; + } + for (tid = 0; tid < NUM_OF_TIDS; tid++) - aggr_delete_tid_state(aggr_info, tid); + aggr_delete_tid_state(aggr_conn, tid); } /* clean up our amsdu buffer list */ @@ -1514,28 +1867,11 @@ void ath6kl_cleanup_amsdu_rxbufs(struct ath6kl *ar) void aggr_module_destroy(struct aggr_info *aggr_info) { - struct rxtid *rxtid; - u8 i, k; - if (!aggr_info) return; - if (aggr_info->timer_scheduled) { - del_timer(&aggr_info->timer); - aggr_info->timer_scheduled = false; - } - - for (i = 0; i < NUM_OF_TIDS; i++) { - rxtid = &aggr_info->rx_tid[i]; - if (rxtid->hold_q) { - for (k = 0; k < rxtid->hold_q_sz; k++) - dev_kfree_skb(rxtid->hold_q[k].skb); - kfree(rxtid->hold_q); - } - - skb_queue_purge(&rxtid->q); - } - - skb_queue_purge(&aggr_info->free_q); + aggr_reset_state(aggr_info->aggr_conn); + skb_queue_purge(&aggr_info->rx_amsdu_freeq); + kfree(aggr_info->aggr_conn); kfree(aggr_info); } diff --git a/drivers/net/wireless/ath/ath6kl/usb.c b/drivers/net/wireless/ath/ath6kl/usb.c new file mode 100644 index 00000000000..3afc5a463d0 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/usb.c @@ -0,0 +1,1264 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/module.h> +#include <linux/usb.h> + +#include "debug.h" +#include "core.h" + +/* constants */ +#define TX_URB_COUNT 32 +#define RX_URB_COUNT 32 +#define ATH6KL_USB_RX_BUFFER_SIZE 4096 + +/* tx/rx pipes for usb */ +enum ATH6KL_USB_PIPE_ID { + ATH6KL_USB_PIPE_TX_CTRL = 0, + ATH6KL_USB_PIPE_TX_DATA_LP, + ATH6KL_USB_PIPE_TX_DATA_MP, + ATH6KL_USB_PIPE_TX_DATA_HP, + ATH6KL_USB_PIPE_RX_CTRL, + ATH6KL_USB_PIPE_RX_DATA, + ATH6KL_USB_PIPE_RX_DATA2, + ATH6KL_USB_PIPE_RX_INT, + ATH6KL_USB_PIPE_MAX +}; + +#define ATH6KL_USB_PIPE_INVALID ATH6KL_USB_PIPE_MAX + +struct ath6kl_usb_pipe { + struct list_head urb_list_head; + struct usb_anchor urb_submitted; + u32 urb_alloc; + u32 urb_cnt; + u32 urb_cnt_thresh; + unsigned int usb_pipe_handle; + u32 flags; + u8 ep_address; + u8 logical_pipe_num; + struct ath6kl_usb *ar_usb; + u16 max_packet_size; + struct work_struct io_complete_work; + struct sk_buff_head io_comp_queue; + struct usb_endpoint_descriptor *ep_desc; +}; + +#define ATH6KL_USB_PIPE_FLAG_TX (1 << 0) + +/* usb device object */ +struct ath6kl_usb { + /* protects pipe->urb_list_head and pipe->urb_cnt */ + spinlock_t cs_lock; + + struct usb_device *udev; + struct usb_interface *interface; + struct ath6kl_usb_pipe pipes[ATH6KL_USB_PIPE_MAX]; + u8 *diag_cmd_buffer; + u8 *diag_resp_buffer; + struct ath6kl *ar; +}; + +/* usb urb object */ +struct ath6kl_urb_context { + struct list_head link; + struct ath6kl_usb_pipe *pipe; + struct sk_buff *skb; + struct ath6kl *ar; +}; + +/* USB endpoint definitions */ +#define ATH6KL_USB_EP_ADDR_APP_CTRL_IN 0x81 +#define ATH6KL_USB_EP_ADDR_APP_DATA_IN 0x82 +#define ATH6KL_USB_EP_ADDR_APP_DATA2_IN 0x83 +#define ATH6KL_USB_EP_ADDR_APP_INT_IN 0x84 + +#define ATH6KL_USB_EP_ADDR_APP_CTRL_OUT 0x01 +#define ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT 0x02 +#define ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT 0x03 +#define ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT 0x04 + +/* diagnostic command defnitions */ +#define ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD 1 +#define ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP 2 +#define ATH6KL_USB_CONTROL_REQ_DIAG_CMD 3 +#define ATH6KL_USB_CONTROL_REQ_DIAG_RESP 4 + +#define ATH6KL_USB_CTRL_DIAG_CC_READ 0 +#define ATH6KL_USB_CTRL_DIAG_CC_WRITE 1 + +struct ath6kl_usb_ctrl_diag_cmd_write { + __le32 cmd; + __le32 address; + __le32 value; + __le32 _pad[1]; +} __packed; + +struct ath6kl_usb_ctrl_diag_cmd_read { + __le32 cmd; + __le32 address; +} __packed; + +struct ath6kl_usb_ctrl_diag_resp_read { + __le32 value; +} __packed; + +/* function declarations */ +static void ath6kl_usb_recv_complete(struct urb *urb); + +#define ATH6KL_USB_IS_BULK_EP(attr) (((attr) & 3) == 0x02) +#define ATH6KL_USB_IS_INT_EP(attr) (((attr) & 3) == 0x03) +#define ATH6KL_USB_IS_ISOC_EP(attr) (((attr) & 3) == 0x01) +#define ATH6KL_USB_IS_DIR_IN(addr) ((addr) & 0x80) + +/* pipe/urb operations */ +static struct ath6kl_urb_context * +ath6kl_usb_alloc_urb_from_pipe(struct ath6kl_usb_pipe *pipe) +{ + struct ath6kl_urb_context *urb_context = NULL; + unsigned long flags; + + spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags); + if (!list_empty(&pipe->urb_list_head)) { + urb_context = + list_first_entry(&pipe->urb_list_head, + struct ath6kl_urb_context, link); + list_del(&urb_context->link); + pipe->urb_cnt--; + } + spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags); + + return urb_context; +} + +static void ath6kl_usb_free_urb_to_pipe(struct ath6kl_usb_pipe *pipe, + struct ath6kl_urb_context *urb_context) +{ + unsigned long flags; + + spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags); + pipe->urb_cnt++; + + list_add(&urb_context->link, &pipe->urb_list_head); + spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags); +} + +static void ath6kl_usb_cleanup_recv_urb(struct ath6kl_urb_context *urb_context) +{ + dev_kfree_skb(urb_context->skb); + urb_context->skb = NULL; + + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context); +} + +static inline struct ath6kl_usb *ath6kl_usb_priv(struct ath6kl *ar) +{ + return ar->hif_priv; +} + +/* pipe resource allocation/cleanup */ +static int ath6kl_usb_alloc_pipe_resources(struct ath6kl_usb_pipe *pipe, + int urb_cnt) +{ + struct ath6kl_urb_context *urb_context; + int status = 0, i; + + INIT_LIST_HEAD(&pipe->urb_list_head); + init_usb_anchor(&pipe->urb_submitted); + + for (i = 0; i < urb_cnt; i++) { + urb_context = kzalloc(sizeof(struct ath6kl_urb_context), + GFP_KERNEL); + if (urb_context == NULL) { + status = -ENOMEM; + goto fail_alloc_pipe_resources; + } + + urb_context->pipe = pipe; + + /* + * we are only allocate the urb contexts here, the actual URB + * is allocated from the kernel as needed to do a transaction + */ + pipe->urb_alloc++; + ath6kl_usb_free_urb_to_pipe(pipe, urb_context); + } + + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: alloc resources lpipe:%d hpipe:0x%X urbs:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc); + +fail_alloc_pipe_resources: + return status; +} + +static void ath6kl_usb_free_pipe_resources(struct ath6kl_usb_pipe *pipe) +{ + struct ath6kl_urb_context *urb_context; + + if (pipe->ar_usb == NULL) { + /* nothing allocated for this pipe */ + return; + } + + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: free resources lpipe:%d" + "hpipe:0x%X urbs:%d avail:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc, pipe->urb_cnt); + + if (pipe->urb_alloc != pipe->urb_cnt) { + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: urb leak! lpipe:%d" + "hpipe:0x%X urbs:%d avail:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc, pipe->urb_cnt); + } + + while (true) { + urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe); + if (urb_context == NULL) + break; + kfree(urb_context); + } +} + +static void ath6kl_usb_cleanup_pipe_resources(struct ath6kl_usb *ar_usb) +{ + int i; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) + ath6kl_usb_free_pipe_resources(&ar_usb->pipes[i]); +} + +static u8 ath6kl_usb_get_logical_pipe_num(struct ath6kl_usb *ar_usb, + u8 ep_address, int *urb_count) +{ + u8 pipe_num = ATH6KL_USB_PIPE_INVALID; + + switch (ep_address) { + case ATH6KL_USB_EP_ADDR_APP_CTRL_IN: + pipe_num = ATH6KL_USB_PIPE_RX_CTRL; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_IN: + pipe_num = ATH6KL_USB_PIPE_RX_DATA; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_INT_IN: + pipe_num = ATH6KL_USB_PIPE_RX_INT; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA2_IN: + pipe_num = ATH6KL_USB_PIPE_RX_DATA2; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_CTRL_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_CTRL; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_LP; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_MP; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_HP; + *urb_count = TX_URB_COUNT; + break; + default: + /* note: there may be endpoints not currently used */ + break; + } + + return pipe_num; +} + +static int ath6kl_usb_setup_pipe_resources(struct ath6kl_usb *ar_usb) +{ + struct usb_interface *interface = ar_usb->interface; + struct usb_host_interface *iface_desc = interface->cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + struct ath6kl_usb_pipe *pipe; + int i, urbcount, status = 0; + u8 pipe_num; + + ath6kl_dbg(ATH6KL_DBG_USB, "setting up USB Pipes using interface\n"); + + /* walk decriptors and setup pipes */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) { + ath6kl_dbg(ATH6KL_DBG_USB, + "%s Bulk Ep:0x%2.2X maxpktsz:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize)); + } else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) { + ath6kl_dbg(ATH6KL_DBG_USB, + "%s Int Ep:0x%2.2X maxpktsz:%d interval:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize), + endpoint->bInterval); + } else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) { + /* TODO for ISO */ + ath6kl_dbg(ATH6KL_DBG_USB, + "%s ISOC Ep:0x%2.2X maxpktsz:%d interval:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize), + endpoint->bInterval); + } + urbcount = 0; + + pipe_num = + ath6kl_usb_get_logical_pipe_num(ar_usb, + endpoint->bEndpointAddress, + &urbcount); + if (pipe_num == ATH6KL_USB_PIPE_INVALID) + continue; + + pipe = &ar_usb->pipes[pipe_num]; + if (pipe->ar_usb != NULL) { + /* hmmm..pipe was already setup */ + continue; + } + + pipe->ar_usb = ar_usb; + pipe->logical_pipe_num = pipe_num; + pipe->ep_address = endpoint->bEndpointAddress; + pipe->max_packet_size = le16_to_cpu(endpoint->wMaxPacketSize); + + if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) { + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvbulkpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndbulkpipe(ar_usb->udev, + pipe->ep_address); + } + } else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) { + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvintpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndintpipe(ar_usb->udev, + pipe->ep_address); + } + } else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) { + /* TODO for ISO */ + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvisocpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndisocpipe(ar_usb->udev, + pipe->ep_address); + } + } + + pipe->ep_desc = endpoint; + + if (!ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) + pipe->flags |= ATH6KL_USB_PIPE_FLAG_TX; + + status = ath6kl_usb_alloc_pipe_resources(pipe, urbcount); + if (status != 0) + break; + } + + return status; +} + +/* pipe operations */ +static void ath6kl_usb_post_recv_transfers(struct ath6kl_usb_pipe *recv_pipe, + int buffer_length) +{ + struct ath6kl_urb_context *urb_context; + struct urb *urb; + int usb_status; + + while (true) { + urb_context = ath6kl_usb_alloc_urb_from_pipe(recv_pipe); + if (urb_context == NULL) + break; + + urb_context->skb = dev_alloc_skb(buffer_length); + if (urb_context->skb == NULL) + goto err_cleanup_urb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) + goto err_cleanup_urb; + + usb_fill_bulk_urb(urb, + recv_pipe->ar_usb->udev, + recv_pipe->usb_pipe_handle, + urb_context->skb->data, + buffer_length, + ath6kl_usb_recv_complete, urb_context); + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb: bulk recv submit:%d, 0x%X (ep:0x%2.2X), %d bytes buf:0x%p\n", + recv_pipe->logical_pipe_num, + recv_pipe->usb_pipe_handle, recv_pipe->ep_address, + buffer_length, urb_context->skb); + + usb_anchor_urb(urb, &recv_pipe->urb_submitted); + usb_status = usb_submit_urb(urb, GFP_ATOMIC); + + if (usb_status) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb : usb bulk recv failed %d\n", + usb_status); + usb_unanchor_urb(urb); + usb_free_urb(urb); + goto err_cleanup_urb; + } + usb_free_urb(urb); + } + return; + +err_cleanup_urb: + ath6kl_usb_cleanup_recv_urb(urb_context); + return; +} + +static void ath6kl_usb_flush_all(struct ath6kl_usb *ar_usb) +{ + int i; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) { + if (ar_usb->pipes[i].ar_usb != NULL) + usb_kill_anchored_urbs(&ar_usb->pipes[i].urb_submitted); + } + + /* + * Flushing any pending I/O may schedule work this call will block + * until all scheduled work runs to completion. + */ + flush_scheduled_work(); +} + +static void ath6kl_usb_start_recv_pipes(struct ath6kl_usb *ar_usb) +{ + /* + * note: control pipe is no longer used + * ar_usb->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_cnt_thresh = + * ar_usb->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_alloc/2; + * ath6kl_usb_post_recv_transfers(&ar_usb-> + * pipes[ATH6KL_USB_PIPE_RX_CTRL], + * ATH6KL_USB_RX_BUFFER_SIZE); + */ + + ar_usb->pipes[ATH6KL_USB_PIPE_RX_DATA].urb_cnt_thresh = 1; + + ath6kl_usb_post_recv_transfers(&ar_usb->pipes[ATH6KL_USB_PIPE_RX_DATA], + ATH6KL_USB_RX_BUFFER_SIZE); +} + +/* hif usb rx/tx completion functions */ +static void ath6kl_usb_recv_complete(struct urb *urb) +{ + struct ath6kl_urb_context *urb_context = urb->context; + struct ath6kl_usb_pipe *pipe = urb_context->pipe; + struct sk_buff *skb = NULL; + int status = 0; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: recv pipe: %d, stat:%d, len:%d urb:0x%p\n", __func__, + pipe->logical_pipe_num, urb->status, urb->actual_length, + urb); + + if (urb->status != 0) { + status = -EIO; + switch (urb->status) { + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* + * no need to spew these errors when device + * removed or urb killed due to driver shutdown + */ + status = -ECANCELED; + break; + default: + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s recv pipe: %d (ep:0x%2.2X), failed:%d\n", + __func__, pipe->logical_pipe_num, + pipe->ep_address, urb->status); + break; + } + goto cleanup_recv_urb; + } + + if (urb->actual_length == 0) + goto cleanup_recv_urb; + + skb = urb_context->skb; + + /* we are going to pass it up */ + urb_context->skb = NULL; + skb_put(skb, urb->actual_length); + + /* note: queue implements a lock */ + skb_queue_tail(&pipe->io_comp_queue, skb); + schedule_work(&pipe->io_complete_work); + +cleanup_recv_urb: + ath6kl_usb_cleanup_recv_urb(urb_context); + + if (status == 0 && + pipe->urb_cnt >= pipe->urb_cnt_thresh) { + /* our free urbs are piling up, post more transfers */ + ath6kl_usb_post_recv_transfers(pipe, ATH6KL_USB_RX_BUFFER_SIZE); + } +} + +static void ath6kl_usb_usb_transmit_complete(struct urb *urb) +{ + struct ath6kl_urb_context *urb_context = urb->context; + struct ath6kl_usb_pipe *pipe = urb_context->pipe; + struct sk_buff *skb; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: pipe: %d, stat:%d, len:%d\n", + __func__, pipe->logical_pipe_num, urb->status, + urb->actual_length); + + if (urb->status != 0) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: pipe: %d, failed:%d\n", + __func__, pipe->logical_pipe_num, urb->status); + } + + skb = urb_context->skb; + urb_context->skb = NULL; + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context); + + /* note: queue implements a lock */ + skb_queue_tail(&pipe->io_comp_queue, skb); + schedule_work(&pipe->io_complete_work); +} + +static void ath6kl_usb_io_comp_work(struct work_struct *work) +{ + struct ath6kl_usb_pipe *pipe = container_of(work, + struct ath6kl_usb_pipe, + io_complete_work); + struct ath6kl_usb *ar_usb; + struct sk_buff *skb; + + ar_usb = pipe->ar_usb; + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + if (pipe->flags & ATH6KL_USB_PIPE_FLAG_TX) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb xmit callback buf:0x%p\n", skb); + ath6kl_core_tx_complete(ar_usb->ar, skb); + } else { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb recv callback buf:0x%p\n", skb); + ath6kl_core_rx_complete(ar_usb->ar, skb, + pipe->logical_pipe_num); + } + } +} + +#define ATH6KL_USB_MAX_DIAG_CMD (sizeof(struct ath6kl_usb_ctrl_diag_cmd_write)) +#define ATH6KL_USB_MAX_DIAG_RESP (sizeof(struct ath6kl_usb_ctrl_diag_resp_read)) + +static void ath6kl_usb_destroy(struct ath6kl_usb *ar_usb) +{ + ath6kl_usb_flush_all(ar_usb); + + ath6kl_usb_cleanup_pipe_resources(ar_usb); + + usb_set_intfdata(ar_usb->interface, NULL); + + kfree(ar_usb->diag_cmd_buffer); + kfree(ar_usb->diag_resp_buffer); + + kfree(ar_usb); +} + +static struct ath6kl_usb *ath6kl_usb_create(struct usb_interface *interface) +{ + struct usb_device *dev = interface_to_usbdev(interface); + struct ath6kl_usb *ar_usb; + struct ath6kl_usb_pipe *pipe; + int status = 0; + int i; + + ar_usb = kzalloc(sizeof(struct ath6kl_usb), GFP_KERNEL); + if (ar_usb == NULL) + goto fail_ath6kl_usb_create; + + usb_set_intfdata(interface, ar_usb); + spin_lock_init(&(ar_usb->cs_lock)); + ar_usb->udev = dev; + ar_usb->interface = interface; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) { + pipe = &ar_usb->pipes[i]; + INIT_WORK(&pipe->io_complete_work, + ath6kl_usb_io_comp_work); + skb_queue_head_init(&pipe->io_comp_queue); + } + + ar_usb->diag_cmd_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_CMD, GFP_KERNEL); + if (ar_usb->diag_cmd_buffer == NULL) { + status = -ENOMEM; + goto fail_ath6kl_usb_create; + } + + ar_usb->diag_resp_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_RESP, + GFP_KERNEL); + if (ar_usb->diag_resp_buffer == NULL) { + status = -ENOMEM; + goto fail_ath6kl_usb_create; + } + + status = ath6kl_usb_setup_pipe_resources(ar_usb); + +fail_ath6kl_usb_create: + if (status != 0) { + ath6kl_usb_destroy(ar_usb); + ar_usb = NULL; + } + return ar_usb; +} + +static void ath6kl_usb_device_detached(struct usb_interface *interface) +{ + struct ath6kl_usb *ar_usb; + + ar_usb = usb_get_intfdata(interface); + if (ar_usb == NULL) + return; + + ath6kl_stop_txrx(ar_usb->ar); + + /* Delay to wait for the target to reboot */ + mdelay(20); + ath6kl_core_cleanup(ar_usb->ar); + ath6kl_usb_destroy(ar_usb); +} + +/* exported hif usb APIs for htc pipe */ +static void hif_start(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + int i; + + ath6kl_usb_start_recv_pipes(device); + + /* set the TX resource avail threshold for each TX pipe */ + for (i = ATH6KL_USB_PIPE_TX_CTRL; + i <= ATH6KL_USB_PIPE_TX_DATA_HP; i++) { + device->pipes[i].urb_cnt_thresh = + device->pipes[i].urb_alloc / 2; + } +} + +static int ath6kl_usb_send(struct ath6kl *ar, u8 PipeID, + struct sk_buff *hdr_skb, struct sk_buff *skb) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + struct ath6kl_usb_pipe *pipe = &device->pipes[PipeID]; + struct ath6kl_urb_context *urb_context; + int usb_status, status = 0; + struct urb *urb; + u8 *data; + u32 len; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, "+%s pipe : %d, buf:0x%p\n", + __func__, PipeID, skb); + + urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe); + + if (urb_context == NULL) { + /* + * TODO: it is possible to run out of urbs if + * 2 endpoints map to the same pipe ID + */ + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s pipe:%d no urbs left. URB Cnt : %d\n", + __func__, PipeID, pipe->urb_cnt); + status = -ENOMEM; + goto fail_hif_send; + } + + urb_context->skb = skb; + + data = skb->data; + len = skb->len; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) { + status = -ENOMEM; + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, + urb_context); + goto fail_hif_send; + } + + usb_fill_bulk_urb(urb, + device->udev, + pipe->usb_pipe_handle, + data, + len, + ath6kl_usb_usb_transmit_complete, urb_context); + + if ((len % pipe->max_packet_size) == 0) { + /* hit a max packet boundary on this pipe */ + urb->transfer_flags |= URB_ZERO_PACKET; + } + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "athusb bulk send submit:%d, 0x%X (ep:0x%2.2X), %d bytes\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->ep_address, len); + + usb_anchor_urb(urb, &pipe->urb_submitted); + usb_status = usb_submit_urb(urb, GFP_ATOMIC); + + if (usb_status) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb : usb bulk transmit failed %d\n", + usb_status); + usb_unanchor_urb(urb); + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, + urb_context); + status = -EINVAL; + } + usb_free_urb(urb); + +fail_hif_send: + return status; +} + +static void hif_stop(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + ath6kl_usb_flush_all(device); +} + +static void ath6kl_usb_get_default_pipe(struct ath6kl *ar, + u8 *ul_pipe, u8 *dl_pipe) +{ + *ul_pipe = ATH6KL_USB_PIPE_TX_CTRL; + *dl_pipe = ATH6KL_USB_PIPE_RX_CTRL; +} + +static int ath6kl_usb_map_service_pipe(struct ath6kl *ar, u16 svc_id, + u8 *ul_pipe, u8 *dl_pipe) +{ + int status = 0; + + switch (svc_id) { + case HTC_CTRL_RSVD_SVC: + case WMI_CONTROL_SVC: + *ul_pipe = ATH6KL_USB_PIPE_TX_CTRL; + /* due to large control packets, shift to data pipe */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_BE_SVC: + case WMI_DATA_BK_SVC: + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_VI_SVC: + + if (ar->hw.flags & ATH6KL_HW_MAP_LP_ENDPOINT) + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + else + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_MP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_VO_SVC: + + if (ar->hw.flags & ATH6KL_HW_MAP_LP_ENDPOINT) + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + else + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_MP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + default: + status = -EPERM; + break; + } + + return status; +} + +static u16 ath6kl_usb_get_free_queue_number(struct ath6kl *ar, u8 pipe_id) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + return device->pipes[pipe_id].urb_cnt; +} + +static void hif_detach_htc(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + ath6kl_usb_flush_all(device); +} + +static int ath6kl_usb_submit_ctrl_out(struct ath6kl_usb *ar_usb, + u8 req, u16 value, u16 index, void *data, + u32 size) +{ + u8 *buf = NULL; + int ret; + + if (size > 0) { + buf = kmemdup(data, size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + } + + /* note: if successful returns number of bytes transfered */ + ret = usb_control_msg(ar_usb->udev, + usb_sndctrlpipe(ar_usb->udev, 0), + req, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, buf, + size, 1000); + + if (ret < 0) { + ath6kl_warn("Failed to submit usb control message: %d\n", ret); + kfree(buf); + return ret; + } + + kfree(buf); + + return 0; +} + +static int ath6kl_usb_submit_ctrl_in(struct ath6kl_usb *ar_usb, + u8 req, u16 value, u16 index, void *data, + u32 size) +{ + u8 *buf = NULL; + int ret; + + if (size > 0) { + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + } + + /* note: if successful returns number of bytes transfered */ + ret = usb_control_msg(ar_usb->udev, + usb_rcvctrlpipe(ar_usb->udev, 0), + req, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, buf, + size, 2 * HZ); + + if (ret < 0) { + ath6kl_warn("Failed to read usb control message: %d\n", ret); + kfree(buf); + return ret; + } + + memcpy((u8 *) data, buf, size); + + kfree(buf); + + return 0; +} + +static int ath6kl_usb_ctrl_msg_exchange(struct ath6kl_usb *ar_usb, + u8 req_val, u8 *req_buf, u32 req_len, + u8 resp_val, u8 *resp_buf, u32 *resp_len) +{ + int ret; + + /* send command */ + ret = ath6kl_usb_submit_ctrl_out(ar_usb, req_val, 0, 0, + req_buf, req_len); + + if (ret != 0) + return ret; + + if (resp_buf == NULL) { + /* no expected response */ + return ret; + } + + /* get response */ + ret = ath6kl_usb_submit_ctrl_in(ar_usb, resp_val, 0, 0, + resp_buf, *resp_len); + + return ret; +} + +static int ath6kl_usb_diag_read32(struct ath6kl *ar, u32 address, u32 *data) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + struct ath6kl_usb_ctrl_diag_resp_read *resp; + struct ath6kl_usb_ctrl_diag_cmd_read *cmd; + u32 resp_len; + int ret; + + cmd = (struct ath6kl_usb_ctrl_diag_cmd_read *) ar_usb->diag_cmd_buffer; + + memset(cmd, 0, sizeof(*cmd)); + cmd->cmd = ATH6KL_USB_CTRL_DIAG_CC_READ; + cmd->address = cpu_to_le32(address); + resp_len = sizeof(*resp); + + ret = ath6kl_usb_ctrl_msg_exchange(ar_usb, + ATH6KL_USB_CONTROL_REQ_DIAG_CMD, + (u8 *) cmd, + sizeof(struct ath6kl_usb_ctrl_diag_cmd_write), + ATH6KL_USB_CONTROL_REQ_DIAG_RESP, + ar_usb->diag_resp_buffer, &resp_len); + + if (ret) { + ath6kl_warn("diag read32 failed: %d\n", ret); + return ret; + } + + resp = (struct ath6kl_usb_ctrl_diag_resp_read *) + ar_usb->diag_resp_buffer; + + *data = le32_to_cpu(resp->value); + + return ret; +} + +static int ath6kl_usb_diag_write32(struct ath6kl *ar, u32 address, __le32 data) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + struct ath6kl_usb_ctrl_diag_cmd_write *cmd; + int ret; + + cmd = (struct ath6kl_usb_ctrl_diag_cmd_write *) ar_usb->diag_cmd_buffer; + + memset(cmd, 0, sizeof(struct ath6kl_usb_ctrl_diag_cmd_write)); + cmd->cmd = cpu_to_le32(ATH6KL_USB_CTRL_DIAG_CC_WRITE); + cmd->address = cpu_to_le32(address); + cmd->value = data; + + ret = ath6kl_usb_ctrl_msg_exchange(ar_usb, + ATH6KL_USB_CONTROL_REQ_DIAG_CMD, + (u8 *) cmd, + sizeof(*cmd), + 0, NULL, NULL); + if (ret) { + ath6kl_warn("diag_write32 failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_bmi_read(struct ath6kl *ar, u8 *buf, u32 len) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + int ret; + + /* get response */ + ret = ath6kl_usb_submit_ctrl_in(ar_usb, + ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP, + 0, 0, buf, len); + if (ret) { + ath6kl_err("Unable to read the bmi data from the device: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_bmi_write(struct ath6kl *ar, u8 *buf, u32 len) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + int ret; + + /* send command */ + ret = ath6kl_usb_submit_ctrl_out(ar_usb, + ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD, + 0, 0, buf, len); + if (ret) { + ath6kl_err("unable to send the bmi data to the device: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_power_on(struct ath6kl *ar) +{ + hif_start(ar); + return 0; +} + +static int ath6kl_usb_power_off(struct ath6kl *ar) +{ + hif_detach_htc(ar); + return 0; +} + +static void ath6kl_usb_stop(struct ath6kl *ar) +{ + hif_stop(ar); +} + +static void ath6kl_usb_cleanup_scatter(struct ath6kl *ar) +{ + /* + * USB doesn't support it. Just return. + */ + return; +} + +static int ath6kl_usb_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + /* + * cfg80211 suspend/WOW currently not supported for USB. + */ + return 0; +} + +static int ath6kl_usb_resume(struct ath6kl *ar) +{ + /* + * cfg80211 resume currently not supported for USB. + */ + return 0; +} + +static const struct ath6kl_hif_ops ath6kl_usb_ops = { + .diag_read32 = ath6kl_usb_diag_read32, + .diag_write32 = ath6kl_usb_diag_write32, + .bmi_read = ath6kl_usb_bmi_read, + .bmi_write = ath6kl_usb_bmi_write, + .power_on = ath6kl_usb_power_on, + .power_off = ath6kl_usb_power_off, + .stop = ath6kl_usb_stop, + .pipe_send = ath6kl_usb_send, + .pipe_get_default = ath6kl_usb_get_default_pipe, + .pipe_map_service = ath6kl_usb_map_service_pipe, + .pipe_get_free_queue_number = ath6kl_usb_get_free_queue_number, + .cleanup_scatter = ath6kl_usb_cleanup_scatter, + .suspend = ath6kl_usb_suspend, + .resume = ath6kl_usb_resume, +}; + +/* ath6kl usb driver registered functions */ +static int ath6kl_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(interface); + struct ath6kl *ar; + struct ath6kl_usb *ar_usb = NULL; + int vendor_id, product_id; + int ret = 0; + + usb_get_dev(dev); + + vendor_id = le16_to_cpu(dev->descriptor.idVendor); + product_id = le16_to_cpu(dev->descriptor.idProduct); + + ath6kl_dbg(ATH6KL_DBG_USB, "vendor_id = %04x\n", vendor_id); + ath6kl_dbg(ATH6KL_DBG_USB, "product_id = %04x\n", product_id); + + if (interface->cur_altsetting) + ath6kl_dbg(ATH6KL_DBG_USB, "USB Interface %d\n", + interface->cur_altsetting->desc.bInterfaceNumber); + + + if (dev->speed == USB_SPEED_HIGH) + ath6kl_dbg(ATH6KL_DBG_USB, "USB 2.0 Host\n"); + else + ath6kl_dbg(ATH6KL_DBG_USB, "USB 1.1 Host\n"); + + ar_usb = ath6kl_usb_create(interface); + + if (ar_usb == NULL) { + ret = -ENOMEM; + goto err_usb_put; + } + + ar = ath6kl_core_create(&ar_usb->udev->dev); + if (ar == NULL) { + ath6kl_err("Failed to alloc ath6kl core\n"); + ret = -ENOMEM; + goto err_usb_destroy; + } + + ar->hif_priv = ar_usb; + ar->hif_type = ATH6KL_HIF_TYPE_USB; + ar->hif_ops = &ath6kl_usb_ops; + ar->mbox_info.block_size = 16; + ar->bmi.max_data_size = 252; + + ar_usb->ar = ar; + + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_PIPE); + if (ret) { + ath6kl_err("Failed to init ath6kl core: %d\n", ret); + goto err_core_free; + } + + return ret; + +err_core_free: + ath6kl_core_destroy(ar); +err_usb_destroy: + ath6kl_usb_destroy(ar_usb); +err_usb_put: + usb_put_dev(dev); + + return ret; +} + +static void ath6kl_usb_remove(struct usb_interface *interface) +{ + usb_put_dev(interface_to_usbdev(interface)); + ath6kl_usb_device_detached(interface); +} + +#ifdef CONFIG_PM + +static int ath6kl_usb_pm_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct ath6kl_usb *device; + device = usb_get_intfdata(interface); + + ath6kl_usb_flush_all(device); + return 0; +} + +static int ath6kl_usb_pm_resume(struct usb_interface *interface) +{ + struct ath6kl_usb *device; + device = usb_get_intfdata(interface); + + ath6kl_usb_post_recv_transfers(&device->pipes[ATH6KL_USB_PIPE_RX_DATA], + ATH6KL_USB_RX_BUFFER_SIZE); + ath6kl_usb_post_recv_transfers(&device->pipes[ATH6KL_USB_PIPE_RX_DATA2], + ATH6KL_USB_RX_BUFFER_SIZE); + + return 0; +} + +static int ath6kl_usb_pm_reset_resume(struct usb_interface *intf) +{ + if (usb_get_intfdata(intf)) + ath6kl_usb_remove(intf); + return 0; +} + +#else + +#define ath6kl_usb_pm_suspend NULL +#define ath6kl_usb_pm_resume NULL +#define ath6kl_usb_pm_reset_resume NULL + +#endif + +/* table of devices that work with this driver */ +static struct usb_device_id ath6kl_usb_ids[] = { + {USB_DEVICE(0x0cf3, 0x9374)}, + { /* Terminating entry */ }, +}; + +MODULE_DEVICE_TABLE(usb, ath6kl_usb_ids); + +static struct usb_driver ath6kl_usb_driver = { + .name = "ath6kl_usb", + .probe = ath6kl_usb_probe, + .suspend = ath6kl_usb_pm_suspend, + .resume = ath6kl_usb_pm_resume, + .reset_resume = ath6kl_usb_pm_reset_resume, + .disconnect = ath6kl_usb_remove, + .id_table = ath6kl_usb_ids, + .supports_autosuspend = true, + .disable_hub_initiated_lpm = 1, +}; + +static int ath6kl_usb_init(void) +{ + int ret; + + ret = usb_register(&ath6kl_usb_driver); + if (ret) { + ath6kl_err("usb registration failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void ath6kl_usb_exit(void) +{ + usb_deregister(&ath6kl_usb_driver); +} + +module_init(ath6kl_usb_init); +module_exit(ath6kl_usb_exit); + +MODULE_AUTHOR("Atheros Communications, Inc."); +MODULE_DESCRIPTION("Driver support for Atheros AR600x USB devices"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_FIRMWARE(AR6004_HW_1_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_FW_DIR "/" AR6004_HW_1_3_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE); diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c index f6f2aa27fc2..4d7f9e4712e 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.c +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,9 +16,11 @@ */ #include <linux/ip.h> +#include <linux/in.h> #include "core.h" #include "debug.h" #include "testmode.h" +#include "trace.h" #include "../regd.h" #include "../regd_common.h" @@ -126,7 +129,7 @@ int ath6kl_wmi_dix_2_dot3(struct wmi *wmi, struct sk_buff *skb) if (!is_ethertype(be16_to_cpu(type))) { ath6kl_dbg(ATH6KL_DBG_WMI, - "%s: pkt is already in 802.3 format\n", __func__); + "%s: pkt is already in 802.3 format\n", __func__); return 0; } @@ -180,7 +183,7 @@ static int ath6kl_wmi_meta_add(struct wmi *wmi, struct sk_buff *skb, } int ath6kl_wmi_data_hdr_add(struct wmi *wmi, struct sk_buff *skb, - u8 msg_type, bool more_data, + u8 msg_type, u32 flags, enum wmi_data_hdr_data_type data_type, u8 meta_ver, void *tx_meta_info, u8 if_idx) { @@ -204,17 +207,19 @@ int ath6kl_wmi_data_hdr_add(struct wmi *wmi, struct sk_buff *skb, data_hdr->info = msg_type << WMI_DATA_HDR_MSG_TYPE_SHIFT; data_hdr->info |= data_type << WMI_DATA_HDR_DATA_TYPE_SHIFT; - if (more_data) - data_hdr->info |= - WMI_DATA_HDR_MORE_MASK << WMI_DATA_HDR_MORE_SHIFT; + if (flags & WMI_DATA_HDR_FLAGS_MORE) + data_hdr->info |= WMI_DATA_HDR_MORE; - data_hdr->info2 = cpu_to_le16(meta_ver << WMI_DATA_HDR_META_SHIFT); - data_hdr->info3 = cpu_to_le16(if_idx & WMI_DATA_HDR_IF_IDX_MASK); + if (flags & WMI_DATA_HDR_FLAGS_EOSP) + data_hdr->info3 |= cpu_to_le16(WMI_DATA_HDR_EOSP); + + data_hdr->info2 |= cpu_to_le16(meta_ver << WMI_DATA_HDR_META_SHIFT); + data_hdr->info3 |= cpu_to_le16(if_idx & WMI_DATA_HDR_IF_IDX_MASK); return 0; } -static u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri) +u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri) { struct iphdr *ip_hdr = (struct iphdr *) pkt; u8 ip_pri; @@ -236,6 +241,11 @@ static u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri) return ip_pri; } +u8 ath6kl_wmi_get_traffic_class(u8 user_priority) +{ + return up_to_ac[user_priority & 0x7]; +} + int ath6kl_wmi_implicit_create_pstream(struct wmi *wmi, u8 if_idx, struct sk_buff *skb, u32 layer2_priority, bool wmm_enabled, @@ -279,8 +289,16 @@ int ath6kl_wmi_implicit_create_pstream(struct wmi *wmi, u8 if_idx, ath6kl_wmi_determine_user_priority(((u8 *) llc_hdr) + sizeof(struct ath6kl_llc_snap_hdr), layer2_priority); - } else + } else { usr_pri = layer2_priority & 0x7; + } + + /* + * Queue the EAPOL frames in the same WMM_AC_VO queue + * as that of management frames. + */ + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) + usr_pri = WMI_VOICE_USER_PRIORITY; } /* @@ -342,8 +360,9 @@ int ath6kl_wmi_dot11_hdr_remove(struct wmi *wmi, struct sk_buff *skb) hdr_size = roundup(sizeof(struct ieee80211_qos_hdr), sizeof(u32)); skb_pull(skb, hdr_size); - } else if (sub_type == cpu_to_le16(IEEE80211_STYPE_DATA)) + } else if (sub_type == cpu_to_le16(IEEE80211_STYPE_DATA)) { skb_pull(skb, sizeof(struct ieee80211_hdr_3addr)); + } datap = skb->data; llc_hdr = (struct ath6kl_llc_snap_hdr *)(datap); @@ -419,9 +438,6 @@ static int ath6kl_wmi_tx_complete_event_rx(u8 *datap, int len) ath6kl_dbg(ATH6KL_DBG_WMI, "comp: %d %d %d\n", evt->num_msg, evt->msg_len, evt->msg_type); - if (!AR_DBG_LVL_CHECK(ATH6KL_DBG_WMI)) - return 0; - for (index = 0; index < evt->num_msg; index++) { size = sizeof(struct wmi_tx_complete_event) + (index * sizeof(struct tx_complete_msg_v1)); @@ -455,12 +471,13 @@ static int ath6kl_wmi_remain_on_chnl_event_rx(struct wmi *wmi, u8 *datap, freq, dur); chan = ieee80211_get_channel(ar->wiphy, freq); if (!chan) { - ath6kl_dbg(ATH6KL_DBG_WMI, "remain_on_chnl: Unknown channel " - "(freq=%u)\n", freq); + ath6kl_dbg(ATH6KL_DBG_WMI, + "remain_on_chnl: Unknown channel (freq=%u)\n", + freq); return -EINVAL; } id = vif->last_roc_id; - cfg80211_ready_on_channel(vif->ndev, id, chan, NL80211_CHAN_NO_HT, + cfg80211_ready_on_channel(&vif->wdev, id, chan, dur, GFP_ATOMIC); return 0; @@ -483,12 +500,14 @@ static int ath6kl_wmi_cancel_remain_on_chnl_event_rx(struct wmi *wmi, ev = (struct wmi_cancel_remain_on_chnl_event *) datap; freq = le32_to_cpu(ev->freq); dur = le32_to_cpu(ev->duration); - ath6kl_dbg(ATH6KL_DBG_WMI, "cancel_remain_on_chnl: freq=%u dur=%u " - "status=%u\n", freq, dur, ev->status); + ath6kl_dbg(ATH6KL_DBG_WMI, + "cancel_remain_on_chnl: freq=%u dur=%u status=%u\n", + freq, dur, ev->status); chan = ieee80211_get_channel(ar->wiphy, freq); if (!chan) { - ath6kl_dbg(ATH6KL_DBG_WMI, "cancel_remain_on_chnl: Unknown " - "channel (freq=%u)\n", freq); + ath6kl_dbg(ATH6KL_DBG_WMI, + "cancel_remain_on_chnl: Unknown channel (freq=%u)\n", + freq); return -EINVAL; } if (vif->last_cancel_roc_id && @@ -497,8 +516,7 @@ static int ath6kl_wmi_cancel_remain_on_chnl_event_rx(struct wmi *wmi, else id = vif->last_roc_id; /* timeout on uncanceled r-o-c */ vif->last_cancel_roc_id = 0; - cfg80211_remain_on_channel_expired(vif->ndev, id, chan, - NL80211_CHAN_NO_HT, GFP_ATOMIC); + cfg80211_remain_on_channel_expired(&vif->wdev, id, chan, GFP_ATOMIC); return 0; } @@ -517,7 +535,7 @@ static int ath6kl_wmi_tx_status_event_rx(struct wmi *wmi, u8 *datap, int len, ath6kl_dbg(ATH6KL_DBG_WMI, "tx_status: id=%x ack_status=%u\n", id, ev->ack_status); if (wmi->last_mgmt_tx_frame) { - cfg80211_mgmt_tx_status(vif->ndev, id, + cfg80211_mgmt_tx_status(&vif->wdev, id, wmi->last_mgmt_tx_frame, wmi->last_mgmt_tx_frame_len, !!ev->ack_status, GFP_ATOMIC); @@ -543,16 +561,17 @@ static int ath6kl_wmi_rx_probe_req_event_rx(struct wmi *wmi, u8 *datap, int len, freq = le32_to_cpu(ev->freq); dlen = le16_to_cpu(ev->len); if (datap + len < ev->data + dlen) { - ath6kl_err("invalid wmi_p2p_rx_probe_req_event: " - "len=%d dlen=%u\n", len, dlen); + ath6kl_err("invalid wmi_p2p_rx_probe_req_event: len=%d dlen=%u\n", + len, dlen); return -EINVAL; } - ath6kl_dbg(ATH6KL_DBG_WMI, "rx_probe_req: len=%u freq=%u " - "probe_req_report=%d\n", + ath6kl_dbg(ATH6KL_DBG_WMI, + "rx_probe_req: len=%u freq=%u probe_req_report=%d\n", dlen, freq, vif->probe_req_report); if (vif->probe_req_report || vif->nw_type == AP_NETWORK) - cfg80211_rx_mgmt(vif->ndev, freq, ev->data, dlen, GFP_ATOMIC); + cfg80211_rx_mgmt(&vif->wdev, freq, 0, ev->data, dlen, 0, + GFP_ATOMIC); return 0; } @@ -586,12 +605,12 @@ static int ath6kl_wmi_rx_action_event_rx(struct wmi *wmi, u8 *datap, int len, freq = le32_to_cpu(ev->freq); dlen = le16_to_cpu(ev->len); if (datap + len < ev->data + dlen) { - ath6kl_err("invalid wmi_rx_action_event: " - "len=%d dlen=%u\n", len, dlen); + ath6kl_err("invalid wmi_rx_action_event: len=%d dlen=%u\n", + len, dlen); return -EINVAL; } ath6kl_dbg(ATH6KL_DBG_WMI, "rx_action: len=%u freq=%u\n", dlen, freq); - cfg80211_rx_mgmt(vif->ndev, freq, ev->data, dlen, GFP_ATOMIC); + cfg80211_rx_mgmt(&vif->wdev, freq, 0, ev->data, dlen, 0, GFP_ATOMIC); return 0; } @@ -680,7 +699,7 @@ static int ath6kl_wmi_ready_event_rx(struct wmi *wmi, u8 *datap, int len) ath6kl_ready_event(wmi->parent_dev, ev->mac_addr, le32_to_cpu(ev->sw_version), - le32_to_cpu(ev->abi_version)); + le32_to_cpu(ev->abi_version), ev->phy_cap); return 0; } @@ -725,7 +744,6 @@ int ath6kl_wmi_force_roam_cmd(struct wmi *wmi, const u8 *bssid) return -ENOMEM; cmd = (struct roam_ctrl_cmd *) skb->data; - memset(cmd, 0, sizeof(*cmd)); memcpy(cmd->info.bssid, bssid, ETH_ALEN); cmd->roam_ctrl = WMI_FORCE_ROAM; @@ -735,6 +753,39 @@ int ath6kl_wmi_force_roam_cmd(struct wmi *wmi, const u8 *bssid) NO_SYNC_WMIFLAG); } +int ath6kl_wmi_ap_set_beacon_intvl_cmd(struct wmi *wmi, u8 if_idx, + u32 beacon_intvl) +{ + struct sk_buff *skb; + struct set_beacon_int_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct set_beacon_int_cmd *) skb->data; + + cmd->beacon_intvl = cpu_to_le32(beacon_intvl); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_BEACON_INT_CMDID, NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_ap_set_dtim_cmd(struct wmi *wmi, u8 if_idx, u32 dtim_period) +{ + struct sk_buff *skb; + struct set_dtim_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct set_dtim_cmd *) skb->data; + + cmd->dtim_period = cpu_to_le32(dtim_period); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_AP_SET_DTIM_CMDID, NO_SYNC_WMIFLAG); +} + int ath6kl_wmi_set_roam_mode_cmd(struct wmi *wmi, enum wmi_roam_mode mode) { struct sk_buff *skb; @@ -745,7 +796,6 @@ int ath6kl_wmi_set_roam_mode_cmd(struct wmi *wmi, enum wmi_roam_mode mode) return -ENOMEM; cmd = (struct roam_ctrl_cmd *) skb->data; - memset(cmd, 0, sizeof(*cmd)); cmd->info.roam_mode = mode; cmd->roam_ctrl = WMI_SET_ROAM_MODE; @@ -770,28 +820,29 @@ static int ath6kl_wmi_connect_event_rx(struct wmi *wmi, u8 *datap, int len, /* AP mode start/STA connected event */ struct net_device *dev = vif->ndev; if (memcmp(dev->dev_addr, ev->u.ap_bss.bssid, ETH_ALEN) == 0) { - ath6kl_dbg(ATH6KL_DBG_WMI, "%s: freq %d bssid %pM " - "(AP started)\n", + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: freq %d bssid %pM (AP started)\n", __func__, le16_to_cpu(ev->u.ap_bss.ch), ev->u.ap_bss.bssid); ath6kl_connect_ap_mode_bss( vif, le16_to_cpu(ev->u.ap_bss.ch)); } else { - ath6kl_dbg(ATH6KL_DBG_WMI, "%s: aid %u mac_addr %pM " - "auth=%u keymgmt=%u cipher=%u apsd_info=%u " - "(STA connected)\n", + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: aid %u mac_addr %pM auth=%u keymgmt=%u cipher=%u apsd_info=%u (STA connected)\n", __func__, ev->u.ap_sta.aid, ev->u.ap_sta.mac_addr, ev->u.ap_sta.auth, ev->u.ap_sta.keymgmt, le16_to_cpu(ev->u.ap_sta.cipher), ev->u.ap_sta.apsd_info); + ath6kl_connect_ap_mode_sta( vif, ev->u.ap_sta.aid, ev->u.ap_sta.mac_addr, ev->u.ap_sta.keymgmt, le16_to_cpu(ev->u.ap_sta.cipher), ev->u.ap_sta.auth, ev->assoc_req_len, - ev->assoc_info + ev->beacon_ie_len); + ev->assoc_info + ev->beacon_ie_len, + ev->u.ap_sta.apsd_info); } return 0; } @@ -819,8 +870,8 @@ static int ath6kl_wmi_connect_event_rx(struct wmi *wmi, u8 *datap, int len, if (pie[1] > 3 && pie[2] == 0x00 && pie[3] == 0x50 && pie[4] == 0xf2 && pie[5] == WMM_OUI_TYPE) { /* WMM OUT (00:50:F2) */ - if (pie[1] > 5 - && pie[6] == WMM_PARAM_OUI_SUBTYPE) + if (pie[1] > 5 && + pie[6] == WMM_PARAM_OUI_SUBTYPE) wmi->is_wmm_enabled = true; } break; @@ -865,7 +916,7 @@ ath6kl_get_regpair(u16 regdmn) return NULL; for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { - if (regDomainPairs[i].regDmnEnum == regdmn) + if (regDomainPairs[i].reg_domain == regdmn) return ®DomainPairs[i]; } @@ -887,7 +938,6 @@ ath6kl_regd_find_country_by_rd(u16 regdmn) static void ath6kl_wmi_regdomain_event(struct wmi *wmi, u8 *datap, int len) { - struct ath6kl_wmi_regdomain *ev; struct country_code_to_enum_rd *country = NULL; struct reg_dmn_pair_mapping *regpair = NULL; @@ -897,24 +947,27 @@ static void ath6kl_wmi_regdomain_event(struct wmi *wmi, u8 *datap, int len) ev = (struct ath6kl_wmi_regdomain *) datap; reg_code = le32_to_cpu(ev->reg_code); - if ((reg_code >> ATH6KL_COUNTRY_RD_SHIFT) & COUNTRY_ERD_FLAG) + if ((reg_code >> ATH6KL_COUNTRY_RD_SHIFT) & COUNTRY_ERD_FLAG) { country = ath6kl_regd_find_country((u16) reg_code); - else if (!(((u16) reg_code & WORLD_SKU_MASK) == WORLD_SKU_PREFIX)) { - + } else if (!(((u16) reg_code & WORLD_SKU_MASK) == WORLD_SKU_PREFIX)) { regpair = ath6kl_get_regpair((u16) reg_code); country = ath6kl_regd_find_country_by_rd((u16) reg_code); - ath6kl_dbg(ATH6KL_DBG_WMI, "Regpair used: 0x%0x\n", - regpair->regDmnEnum); + if (regpair) + ath6kl_dbg(ATH6KL_DBG_WMI, "Regpair used: 0x%0x\n", + regpair->reg_domain); + else + ath6kl_warn("Regpair not found reg_code 0x%0x\n", + reg_code); } - if (country) { + if (country && wmi->parent_dev->wiphy_registered) { alpha2[0] = country->isoName[0]; alpha2[1] = country->isoName[1]; regulatory_hint(wmi->parent_dev->wiphy, alpha2); ath6kl_dbg(ATH6KL_DBG_WMI, "Country alpha2 being used: %c%c\n", - alpha2[0], alpha2[1]); + alpha2[0], alpha2[1]); } } @@ -1025,8 +1078,9 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, if (len < 8 + 2 + 2) return -EINVAL; - if (bih->frame_type == BEACON_FTYPE && test_bit(CONNECTED, &vif->flags) - && memcmp(bih->bssid, vif->bssid, ETH_ALEN) == 0) { + if (bih->frame_type == BEACON_FTYPE && + test_bit(CONNECTED, &vif->flags) && + memcmp(bih->bssid, vif->bssid, ETH_ALEN) == 0) { const u8 *tim; tim = cfg80211_find_ie(WLAN_EID_TIM, buf + 8 + 2 + 2, len - 8 - 2 - 2); @@ -1071,7 +1125,7 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, kfree(mgmt); if (bss == NULL) return -ENOMEM; - cfg80211_put_bss(bss); + cfg80211_put_bss(ar->wiphy, bss); /* * Firmware doesn't return any event when scheduled scan has @@ -1082,7 +1136,7 @@ static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, * the timer would not ever fire if the scan interval is short * enough. */ - if (ar->state == ATH6KL_STATE_SCHED_SCAN && + if (test_bit(SCHED_SCANNING, &vif->flags) && !timer_pending(&vif->sched_scan_timer)) { mod_timer(&vif->sched_scan_timer, jiffies + msecs_to_jiffies(ATH6KL_SCHED_SCAN_RESULT_DELAY)); @@ -1136,6 +1190,9 @@ static int ath6kl_wmi_bitrate_reply_rx(struct wmi *wmi, u8 *datap, int len) rate = RATE_AUTO; } else { index = reply->rate_index & 0x7f; + if (WARN_ON_ONCE(index > (RATE_MCS_7_40 + 1))) + return -EINVAL; + sgi = (reply->rate_index & 0x80) ? 1 : 0; rate = wmi_rate_tbl[index][sgi]; } @@ -1145,9 +1202,9 @@ static int ath6kl_wmi_bitrate_reply_rx(struct wmi *wmi, u8 *datap, int len) return 0; } -static int ath6kl_wmi_tcmd_test_report_rx(struct wmi *wmi, u8 *datap, int len) +static int ath6kl_wmi_test_rx(struct wmi *wmi, u8 *datap, int len) { - ath6kl_tm_rx_report_event(wmi->parent_dev, datap, len); + ath6kl_tm_rx_event(wmi->parent_dev, datap, len); return 0; } @@ -1219,8 +1276,9 @@ static int ath6kl_wmi_neighbor_report_event_rx(struct wmi *wmi, u8 *datap, ev = (struct wmi_neighbor_report_event *) datap; if (sizeof(*ev) + ev->num_neighbors * sizeof(struct wmi_neighbor_info) > len) { - ath6kl_dbg(ATH6KL_DBG_WMI, "truncated neighbor event " - "(num=%d len=%d)\n", ev->num_neighbors, len); + ath6kl_dbg(ATH6KL_DBG_WMI, + "truncated neighbor event (num=%d len=%d)\n", + ev->num_neighbors, len); return -EINVAL; } for (i = 0; i < ev->num_neighbors; i++) { @@ -1358,8 +1416,8 @@ static int ath6kl_wmi_rssi_threshold_event_rx(struct wmi *wmi, u8 *datap, /* Upper threshold breached */ if (rssi < sq_thresh->upper_threshold[0]) { ath6kl_dbg(ATH6KL_DBG_WMI, - "spurious upper rssi threshold event: %d\n", - rssi); + "spurious upper rssi threshold event: %d\n", + rssi); } else if ((rssi < sq_thresh->upper_threshold[1]) && (rssi >= sq_thresh->upper_threshold[0])) { new_threshold = WMI_RSSI_THRESHOLD1_ABOVE; @@ -1382,7 +1440,7 @@ static int ath6kl_wmi_rssi_threshold_event_rx(struct wmi *wmi, u8 *datap, /* Lower threshold breached */ if (rssi > sq_thresh->lower_threshold[0]) { ath6kl_dbg(ATH6KL_DBG_WMI, - "spurious lower rssi threshold event: %d %d\n", + "spurious lower rssi threshold event: %d %d\n", rssi, sq_thresh->lower_threshold[0]); } else if ((rssi > sq_thresh->lower_threshold[1]) && (rssi <= sq_thresh->lower_threshold[0])) { @@ -1441,7 +1499,6 @@ static int ath6kl_wmi_cac_event_rx(struct wmi *wmi, u8 *datap, int len, if ((reply->cac_indication == CAC_INDICATION_ADMISSION_RESP) && (reply->status_code != IEEE80211_TSPEC_STATUS_ADMISS_ACCEPTED)) { - ts = (struct ieee80211_tspec_ie *) &(reply->tspec_suggestion); tsinfo = le16_to_cpu(ts->tsinfo); tsid = (tsinfo >> IEEE80211_WMM_IE_TSPEC_TID_SHIFT) & @@ -1472,7 +1529,6 @@ static int ath6kl_wmi_cac_event_rx(struct wmi *wmi, u8 *datap, int len, * for delete qos stream from AP */ else if (reply->cac_indication == CAC_INDICATION_DELETE) { - ts = (struct ieee80211_tspec_ie *) &(reply->tspec_suggestion); tsinfo = le16_to_cpu(ts->tsinfo); ts_id = ((tsinfo >> IEEE80211_WMM_IE_TSPEC_TID_SHIFT) & @@ -1496,6 +1552,68 @@ static int ath6kl_wmi_cac_event_rx(struct wmi *wmi, u8 *datap, int len, return 0; } +static int ath6kl_wmi_txe_notify_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_txe_notify_event *ev; + u32 rate, pkts; + + if (len < sizeof(*ev)) + return -EINVAL; + + if (vif->sme_state != SME_CONNECTED) + return -ENOTCONN; + + ev = (struct wmi_txe_notify_event *) datap; + rate = le32_to_cpu(ev->rate); + pkts = le32_to_cpu(ev->pkts); + + ath6kl_dbg(ATH6KL_DBG_WMI, "TXE notify event: peer %pM rate %d% pkts %d intvl %ds\n", + vif->bssid, rate, pkts, vif->txe_intvl); + + cfg80211_cqm_txe_notify(vif->ndev, vif->bssid, pkts, + rate, vif->txe_intvl, GFP_KERNEL); + + return 0; +} + +int ath6kl_wmi_set_txe_notify(struct wmi *wmi, u8 idx, + u32 rate, u32 pkts, u32 intvl) +{ + struct sk_buff *skb; + struct wmi_txe_notify_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_txe_notify_cmd *) skb->data; + cmd->rate = cpu_to_le32(rate); + cmd->pkts = cpu_to_le32(pkts); + cmd->intvl = cpu_to_le32(intvl); + + return ath6kl_wmi_cmd_send(wmi, idx, skb, WMI_SET_TXE_NOTIFY_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_rssi_filter_cmd(struct wmi *wmi, u8 if_idx, s8 rssi) +{ + struct sk_buff *skb; + struct wmi_set_rssi_filter_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_rssi_filter_cmd *) skb->data; + cmd->rssi = rssi; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_RSSI_FILTER_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + static int ath6kl_wmi_send_snr_threshold_params(struct wmi *wmi, struct wmi_snr_threshold_params_cmd *snr_cmd) { @@ -1543,8 +1661,8 @@ static int ath6kl_wmi_snr_threshold_event_rx(struct wmi *wmi, u8 *datap, /* Upper threshold breached */ if (snr < sq_thresh->upper_threshold[0]) { ath6kl_dbg(ATH6KL_DBG_WMI, - "spurious upper snr threshold event: %d\n", - snr); + "spurious upper snr threshold event: %d\n", + snr); } else if ((snr < sq_thresh->upper_threshold[1]) && (snr >= sq_thresh->upper_threshold[0])) { new_threshold = WMI_SNR_THRESHOLD1_ABOVE; @@ -1561,8 +1679,8 @@ static int ath6kl_wmi_snr_threshold_event_rx(struct wmi *wmi, u8 *datap, /* Lower threshold breached */ if (snr > sq_thresh->lower_threshold[0]) { ath6kl_dbg(ATH6KL_DBG_WMI, - "spurious lower snr threshold event: %d\n", - sq_thresh->lower_threshold[0]); + "spurious lower snr threshold event: %d\n", + sq_thresh->lower_threshold[0]); } else if ((snr > sq_thresh->lower_threshold[1]) && (snr <= sq_thresh->lower_threshold[0])) { new_threshold = WMI_SNR_THRESHOLD4_BELOW; @@ -1642,8 +1760,11 @@ int ath6kl_wmi_cmd_send(struct wmi *wmi, u8 if_idx, struct sk_buff *skb, int ret; u16 info1; - if (WARN_ON(skb == NULL || (if_idx > (wmi->parent_dev->vif_max - 1)))) + if (WARN_ON(skb == NULL || + (if_idx > (wmi->parent_dev->vif_max - 1)))) { + dev_kfree_skb(skb); return -EINVAL; + } ath6kl_dbg(ATH6KL_DBG_WMI, "wmi tx id %d len %d flag %d\n", cmd_id, skb->len, sync_flag); @@ -1798,20 +1919,23 @@ int ath6kl_wmi_disconnect_cmd(struct wmi *wmi, u8 if_idx) return ret; } -int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, - enum wmi_scan_type scan_type, - u32 force_fgscan, u32 is_legacy, - u32 home_dwell_time, u32 force_scan_interval, - s8 num_chan, u16 *ch_list, u32 no_cck, u32 *rates) +/* ath6kl_wmi_start_scan_cmd is to be deprecated. Use + * ath6kl_wmi_begin_scan_cmd instead. The new function supports P2P + * mgmt operations using station interface. + */ +static int ath6kl_wmi_startscan_cmd(struct wmi *wmi, u8 if_idx, + enum wmi_scan_type scan_type, + u32 force_fgscan, u32 is_legacy, + u32 home_dwell_time, + u32 force_scan_interval, + s8 num_chan, u16 *ch_list) { struct sk_buff *skb; - struct wmi_begin_scan_cmd *sc; + struct wmi_start_scan_cmd *sc; s8 size; - int i, band, ret; - struct ath6kl *ar = wmi->parent_dev; - int num_rates; + int i, ret; - size = sizeof(struct wmi_begin_scan_cmd); + size = sizeof(struct wmi_start_scan_cmd); if ((scan_type != WMI_LONG_SCAN) && (scan_type != WMI_SHORT_SCAN)) return -EINVAL; @@ -1826,56 +1950,53 @@ int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, if (!skb) return -ENOMEM; - sc = (struct wmi_begin_scan_cmd *) skb->data; + sc = (struct wmi_start_scan_cmd *) skb->data; sc->scan_type = scan_type; sc->force_fg_scan = cpu_to_le32(force_fgscan); sc->is_legacy = cpu_to_le32(is_legacy); sc->home_dwell_time = cpu_to_le32(home_dwell_time); sc->force_scan_intvl = cpu_to_le32(force_scan_interval); - sc->no_cck = cpu_to_le32(no_cck); sc->num_ch = num_chan; - for (band = 0; band < IEEE80211_NUM_BANDS; band++) { - struct ieee80211_supported_band *sband = - ar->wiphy->bands[band]; - u32 ratemask = rates[band]; - u8 *supp_rates = sc->supp_rates[band].rates; - num_rates = 0; - - for (i = 0; i < sband->n_bitrates; i++) { - if ((BIT(i) & ratemask) == 0) - continue; /* skip rate */ - supp_rates[num_rates++] = - (u8) (sband->bitrates[i].bitrate / 5); - } - sc->supp_rates[band].nrates = num_rates; - } - for (i = 0; i < num_chan; i++) sc->ch_list[i] = cpu_to_le16(ch_list[i]); - ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_BEGIN_SCAN_CMDID, + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_START_SCAN_CMDID, NO_SYNC_WMIFLAG); return ret; } -/* ath6kl_wmi_start_scan_cmd is to be deprecated. Use - * ath6kl_wmi_begin_scan_cmd instead. The new function supports P2P - * mgmt operations using station interface. +/* + * beginscan supports (compared to old startscan) P2P mgmt operations using + * station interface, send additional information like supported rates to + * advertise and xmit rates for probe requests */ -int ath6kl_wmi_startscan_cmd(struct wmi *wmi, u8 if_idx, +int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, enum wmi_scan_type scan_type, u32 force_fgscan, u32 is_legacy, u32 home_dwell_time, u32 force_scan_interval, - s8 num_chan, u16 *ch_list) + s8 num_chan, u16 *ch_list, u32 no_cck, u32 *rates) { + struct ieee80211_supported_band *sband; struct sk_buff *skb; - struct wmi_start_scan_cmd *sc; - s8 size; - int i, ret; + struct wmi_begin_scan_cmd *sc; + s8 size, *supp_rates; + int i, band, ret; + struct ath6kl *ar = wmi->parent_dev; + int num_rates; + u32 ratemask; + + if (!test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + return ath6kl_wmi_startscan_cmd(wmi, if_idx, + scan_type, force_fgscan, + is_legacy, home_dwell_time, + force_scan_interval, + num_chan, ch_list); + } - size = sizeof(struct wmi_start_scan_cmd); + size = sizeof(struct wmi_begin_scan_cmd); if ((scan_type != WMI_LONG_SCAN) && (scan_type != WMI_SHORT_SCAN)) return -EINVAL; @@ -1890,23 +2011,67 @@ int ath6kl_wmi_startscan_cmd(struct wmi *wmi, u8 if_idx, if (!skb) return -ENOMEM; - sc = (struct wmi_start_scan_cmd *) skb->data; + sc = (struct wmi_begin_scan_cmd *) skb->data; sc->scan_type = scan_type; sc->force_fg_scan = cpu_to_le32(force_fgscan); sc->is_legacy = cpu_to_le32(is_legacy); sc->home_dwell_time = cpu_to_le32(home_dwell_time); sc->force_scan_intvl = cpu_to_le32(force_scan_interval); + sc->no_cck = cpu_to_le32(no_cck); sc->num_ch = num_chan; + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + sband = ar->wiphy->bands[band]; + + if (!sband) + continue; + + if (WARN_ON(band >= ATH6KL_NUM_BANDS)) + break; + + ratemask = rates[band]; + supp_rates = sc->supp_rates[band].rates; + num_rates = 0; + + for (i = 0; i < sband->n_bitrates; i++) { + if ((BIT(i) & ratemask) == 0) + continue; /* skip rate */ + supp_rates[num_rates++] = + (u8) (sband->bitrates[i].bitrate / 5); + } + sc->supp_rates[band].nrates = num_rates; + } + for (i = 0; i < num_chan; i++) sc->ch_list[i] = cpu_to_le16(ch_list[i]); - ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_START_SCAN_CMDID, + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_BEGIN_SCAN_CMDID, NO_SYNC_WMIFLAG); return ret; } +int ath6kl_wmi_enable_sched_scan_cmd(struct wmi *wmi, u8 if_idx, bool enable) +{ + struct sk_buff *skb; + struct wmi_enable_sched_scan_cmd *sc; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*sc)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "%s scheduled scan on vif %d\n", + enable ? "enabling" : "disabling", if_idx); + sc = (struct wmi_enable_sched_scan_cmd *) skb->data; + sc->enable = enable ? 1 : 0; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_ENABLE_SCHED_SCAN_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + int ath6kl_wmi_scanparams_cmd(struct wmi *wmi, u8 if_idx, u16 fg_start_sec, u16 fg_end_sec, u16 bg_sec, @@ -1969,7 +2134,7 @@ int ath6kl_wmi_probedssid_cmd(struct wmi *wmi, u8 if_idx, u8 index, u8 flag, struct wmi_probed_ssid_cmd *cmd; int ret; - if (index > MAX_PROBED_SSID_INDEX) + if (index >= MAX_PROBED_SSIDS) return -EINVAL; if (ssid_len > sizeof(cmd->ssid)) @@ -2020,6 +2185,26 @@ int ath6kl_wmi_listeninterval_cmd(struct wmi *wmi, u8 if_idx, return ret; } +int ath6kl_wmi_bmisstime_cmd(struct wmi *wmi, u8 if_idx, + u16 bmiss_time, u16 num_beacons) +{ + struct sk_buff *skb; + struct wmi_bmiss_time_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_bmiss_time_cmd *) skb->data; + cmd->bmiss_time = cpu_to_le16(bmiss_time); + cmd->num_beacons = cpu_to_le16(num_beacons); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_BMISS_TIME_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + int ath6kl_wmi_powermode_cmd(struct wmi *wmi, u8 if_idx, u8 pwr_mode) { struct sk_buff *skb; @@ -2099,8 +2284,8 @@ int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index, struct wmi_add_cipher_key_cmd *cmd; int ret; - ath6kl_dbg(ATH6KL_DBG_WMI, "addkey cmd: key_index=%u key_type=%d " - "key_usage=%d key_len=%d key_op_ctrl=%d\n", + ath6kl_dbg(ATH6KL_DBG_WMI, + "addkey cmd: key_index=%u key_type=%d key_usage=%d key_len=%d key_op_ctrl=%d\n", key_index, key_type, key_usage, key_len, key_op_ctrl); if ((key_index > WMI_MAX_KEY_INDEX) || (key_len > WMI_MAX_KEY_LEN) || @@ -2135,7 +2320,7 @@ int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index, return ret; } -int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, u8 *krk) +int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, const u8 *krk) { struct sk_buff *skb; struct wmi_add_krk_cmd *cmd; @@ -2215,8 +2400,10 @@ static int ath6kl_wmi_data_sync_send(struct wmi *wmi, struct sk_buff *skb, struct wmi_data_hdr *data_hdr; int ret; - if (WARN_ON(skb == NULL || ep_id == wmi->ep_id)) + if (WARN_ON(skb == NULL || ep_id == wmi->ep_id)) { + dev_kfree_skb(skb); return -EINVAL; + } skb_push(skb, sizeof(struct wmi_data_hdr)); @@ -2253,10 +2440,8 @@ static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx) spin_unlock_bh(&wmi->lock); skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); - if (!skb) { - ret = -ENOMEM; - goto free_skb; - } + if (!skb) + return -ENOMEM; cmd = (struct wmi_sync_cmd *) skb->data; @@ -2279,7 +2464,7 @@ static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx) * then do not send the Synchronize cmd on the control ep */ if (ret) - goto free_skb; + goto free_cmd_skb; /* * Send sync cmd followed by sync data messages on all @@ -2289,15 +2474,11 @@ static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx) NO_SYNC_WMIFLAG); if (ret) - goto free_skb; - - /* cmd buffer sent, we no longer own it */ - skb = NULL; + goto free_data_skb; for (index = 0; index < num_pri_streams; index++) { - if (WARN_ON(!data_sync_bufs[index].skb)) - break; + goto free_data_skb; ep_id = ath6kl_ac2_endpoint_id(wmi->parent_dev, data_sync_bufs[index]. @@ -2306,23 +2487,21 @@ static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx) ath6kl_wmi_data_sync_send(wmi, data_sync_bufs[index].skb, ep_id, if_idx); - if (ret) - break; - data_sync_bufs[index].skb = NULL; + + if (ret) + goto free_data_skb; } -free_skb: + return 0; + +free_cmd_skb: /* free up any resources left over (possibly due to an error) */ - if (skb) - dev_kfree_skb(skb); + dev_kfree_skb(skb); - for (index = 0; index < num_pri_streams; index++) { - if (data_sync_bufs[index].skb != NULL) { - dev_kfree_skb((struct sk_buff *)data_sync_bufs[index]. - skb); - } - } +free_data_skb: + for (index = 0; index < num_pri_streams; index++) + dev_kfree_skb((struct sk_buff *)data_sync_bufs[index].skb); return ret; } @@ -2479,15 +2658,16 @@ int ath6kl_wmi_delete_pstream_cmd(struct wmi *wmi, u8 if_idx, u8 traffic_class, return ret; } -int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, struct wmi_set_ip_cmd *ip_cmd) +int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, u8 if_idx, + __be32 ips0, __be32 ips1) { struct sk_buff *skb; struct wmi_set_ip_cmd *cmd; int ret; /* Multicast address are not valid */ - if ((*((u8 *) &ip_cmd->ips[0]) >= 0xE0) || - (*((u8 *) &ip_cmd->ips[1]) >= 0xE0)) + if (ipv4_is_multicast(ips0) || + ipv4_is_multicast(ips1)) return -EINVAL; skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_ip_cmd)); @@ -2495,9 +2675,10 @@ int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, struct wmi_set_ip_cmd *ip_cmd) return -ENOMEM; cmd = (struct wmi_set_ip_cmd *) skb->data; - memcpy(cmd, ip_cmd, sizeof(struct wmi_set_ip_cmd)); + cmd->ips[0] = ips0; + cmd->ips[1] = ips1; - ret = ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SET_IP_CMDID, + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_IP_CMDID, NO_SYNC_WMIFLAG); return ret; } @@ -2520,7 +2701,6 @@ static void ath6kl_wmi_relinquish_implicit_pstream_credits(struct wmi *wmi) for (i = 0; i < WMM_NUM_AC; i++) { if (stream_exist & (1 << i)) { - /* * FIXME: Is this lock & unlock inside * for loop correct? may need rework. @@ -2551,6 +2731,119 @@ static void ath6kl_wmi_relinquish_implicit_pstream_credits(struct wmi *wmi) spin_unlock_bh(&wmi->lock); } +static int ath6kl_set_bitrate_mask64(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct sk_buff *skb; + int ret, mode, band; + u64 mcsrate, ratemask[ATH6KL_NUM_BANDS]; + struct wmi_set_tx_select_rates64_cmd *cmd; + + memset(&ratemask, 0, sizeof(ratemask)); + + /* only check 2.4 and 5 GHz bands, skip the rest */ + for (band = 0; band <= IEEE80211_BAND_5GHZ; band++) { + /* copy legacy rate mask */ + ratemask[band] = mask->control[band].legacy; + if (band == IEEE80211_BAND_5GHZ) + ratemask[band] = + mask->control[band].legacy << 4; + + /* copy mcs rate mask */ + mcsrate = mask->control[band].ht_mcs[1]; + mcsrate <<= 8; + mcsrate |= mask->control[band].ht_mcs[0]; + ratemask[band] |= mcsrate << 12; + ratemask[band] |= mcsrate << 28; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Ratemask 64 bit: 2.4:%llx 5:%llx\n", + ratemask[0], ratemask[1]); + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd) * WMI_RATES_MODE_MAX); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_tx_select_rates64_cmd *) skb->data; + for (mode = 0; mode < WMI_RATES_MODE_MAX; mode++) { + /* A mode operate in 5GHZ band */ + if (mode == WMI_RATES_MODE_11A || + mode == WMI_RATES_MODE_11A_HT20 || + mode == WMI_RATES_MODE_11A_HT40) + band = IEEE80211_BAND_5GHZ; + else + band = IEEE80211_BAND_2GHZ; + cmd->ratemask[mode] = cpu_to_le64(ratemask[band]); + } + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_TX_SELECT_RATES_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +static int ath6kl_set_bitrate_mask32(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct sk_buff *skb; + int ret, mode, band; + u32 mcsrate, ratemask[ATH6KL_NUM_BANDS]; + struct wmi_set_tx_select_rates32_cmd *cmd; + + memset(&ratemask, 0, sizeof(ratemask)); + + /* only check 2.4 and 5 GHz bands, skip the rest */ + for (band = 0; band <= IEEE80211_BAND_5GHZ; band++) { + /* copy legacy rate mask */ + ratemask[band] = mask->control[band].legacy; + if (band == IEEE80211_BAND_5GHZ) + ratemask[band] = + mask->control[band].legacy << 4; + + /* copy mcs rate mask */ + mcsrate = mask->control[band].ht_mcs[0]; + ratemask[band] |= mcsrate << 12; + ratemask[band] |= mcsrate << 20; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Ratemask 32 bit: 2.4:%x 5:%x\n", + ratemask[0], ratemask[1]); + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd) * WMI_RATES_MODE_MAX); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_tx_select_rates32_cmd *) skb->data; + for (mode = 0; mode < WMI_RATES_MODE_MAX; mode++) { + /* A mode operate in 5GHZ band */ + if (mode == WMI_RATES_MODE_11A || + mode == WMI_RATES_MODE_11A_HT20 || + mode == WMI_RATES_MODE_11A_HT40) + band = IEEE80211_BAND_5GHZ; + else + band = IEEE80211_BAND_2GHZ; + cmd->ratemask[mode] = cpu_to_le32(ratemask[band]); + } + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_TX_SELECT_RATES_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_bitrate_mask(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct ath6kl *ar = wmi->parent_dev; + + if (ar->hw.flags & ATH6KL_HW_64BIT_RATES) + return ath6kl_set_bitrate_mask64(wmi, if_idx, mask); + else + return ath6kl_set_bitrate_mask32(wmi, if_idx, mask); +} + int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, enum ath6kl_host_mode host_mode) { @@ -2573,8 +2866,9 @@ int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, if (host_mode == ATH6KL_HOST_MODE_ASLEEP) { ath6kl_wmi_relinquish_implicit_pstream_credits(wmi); cmd->asleep = cpu_to_le32(1); - } else + } else { cmd->awake = cpu_to_le32(1); + } ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_HOST_SLEEP_MODE_CMDID, @@ -2582,6 +2876,18 @@ int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, return ret; } +/* This command has zero length payload */ +static int ath6kl_wmi_host_sleep_mode_cmd_prcd_evt_rx(struct wmi *wmi, + struct ath6kl_vif *vif) +{ + struct ath6kl *ar = wmi->parent_dev; + + set_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); + wake_up(&ar->event_wq); + + return 0; +} + int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, enum ath6kl_wow_mode wow_mode, u32 filter, u16 host_req_delay) @@ -2591,7 +2897,7 @@ int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, int ret; if ((wow_mode != ATH6KL_WOW_MODE_ENABLE) && - wow_mode != ATH6KL_WOW_MODE_DISABLE) { + wow_mode != ATH6KL_WOW_MODE_DISABLE) { ath6kl_err("invalid wow mode: %d\n", wow_mode); return -EINVAL; } @@ -2612,7 +2918,8 @@ int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, int ath6kl_wmi_add_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, u8 list_id, u8 filter_size, - u8 filter_offset, u8 *filter, u8 *mask) + u8 filter_offset, const u8 *filter, + const u8 *mask) { struct sk_buff *skb; struct wmi_add_wow_pattern_cmd *cmd; @@ -2837,6 +3144,43 @@ int ath6kl_wmi_set_keepalive_cmd(struct wmi *wmi, u8 if_idx, return ret; } +int ath6kl_wmi_set_htcap_cmd(struct wmi *wmi, u8 if_idx, + enum ieee80211_band band, + struct ath6kl_htcap *htcap) +{ + struct sk_buff *skb; + struct wmi_set_htcap_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_htcap_cmd *) skb->data; + + /* + * NOTE: Band in firmware matches enum ieee80211_band, it is unlikely + * this will be changed in firmware. If at all there is any change in + * band value, the host needs to be fixed. + */ + cmd->band = band; + cmd->ht_enable = !!htcap->ht_enable; + cmd->ht20_sgi = !!(htcap->cap_info & IEEE80211_HT_CAP_SGI_20); + cmd->ht40_supported = + !!(htcap->cap_info & IEEE80211_HT_CAP_SUP_WIDTH_20_40); + cmd->ht40_sgi = !!(htcap->cap_info & IEEE80211_HT_CAP_SGI_40); + cmd->intolerant_40mhz = + !!(htcap->cap_info & IEEE80211_HT_CAP_40MHZ_INTOLERANT); + cmd->max_ampdu_len_exp = htcap->ampdu_factor; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Set htcap: band:%d ht_enable:%d 40mhz:%d sgi_20mhz:%d sgi_40mhz:%d 40mhz_intolerant:%d ampdu_len_exp:%d\n", + cmd->band, cmd->ht_enable, cmd->ht40_supported, + cmd->ht20_sgi, cmd->ht40_sgi, cmd->intolerant_40mhz, + cmd->max_ampdu_len_exp); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_HT_CAP_CMDID, + NO_SYNC_WMIFLAG); +} + int ath6kl_wmi_test_cmd(struct wmi *wmi, void *buf, size_t len) { struct sk_buff *skb; @@ -2853,13 +3197,105 @@ int ath6kl_wmi_test_cmd(struct wmi *wmi, void *buf, size_t len) return ret; } +int ath6kl_wmi_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, bool mc_all_on) +{ + struct sk_buff *skb; + struct wmi_mcast_filter_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_mcast_filter_cmd *) skb->data; + cmd->mcast_all_enable = mc_all_on; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_MCAST_FILTER_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_add_del_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, + u8 *filter, bool add_filter) +{ + struct sk_buff *skb; + struct wmi_mcast_filter_add_del_cmd *cmd; + int ret; + + if ((filter[0] != 0x33 || filter[1] != 0x33) && + (filter[0] != 0x01 || filter[1] != 0x00 || + filter[2] != 0x5e || filter[3] > 0x7f)) { + ath6kl_warn("invalid multicast filter address\n"); + return -EINVAL; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_mcast_filter_add_del_cmd *) skb->data; + memcpy(cmd->mcast_mac, filter, ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE); + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + add_filter ? WMI_SET_MCAST_FILTER_CMDID : + WMI_DEL_MCAST_FILTER_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_sta_bmiss_enhance_cmd(struct wmi *wmi, u8 if_idx, bool enhance) +{ + struct sk_buff *skb; + struct wmi_sta_bmiss_enhance_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_sta_bmiss_enhance_cmd *) skb->data; + cmd->enable = enhance ? 1 : 0; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_STA_BMISS_ENHANCE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_regdomain_cmd(struct wmi *wmi, const char *alpha2) +{ + struct sk_buff *skb; + struct wmi_set_regdomain_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_regdomain_cmd *) skb->data; + memcpy(cmd->iso_name, alpha2, 2); + + return ath6kl_wmi_cmd_send(wmi, 0, skb, + WMI_SET_REGDOMAIN_CMDID, + NO_SYNC_WMIFLAG); +} s32 ath6kl_wmi_get_rate(s8 rate_index) { + u8 sgi = 0; + if (rate_index == RATE_AUTO) return 0; - return wmi_rate_tbl[(u32) rate_index][0]; + /* SGI is stored as the MSB of the rate_index */ + if (rate_index & RATE_INDEX_MSB) { + rate_index &= RATE_INDEX_WITHOUT_SGI_MASK; + sgi = 1; + } + + if (WARN_ON(rate_index > RATE_MCS_7_40)) + rate_index = RATE_MCS_7_40; + + return wmi_rate_tbl[(u32) rate_index][sgi]; } static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap, @@ -2920,8 +3356,8 @@ int ath6kl_wmi_ap_profile_commit(struct wmi *wmip, u8 if_idx, res = ath6kl_wmi_cmd_send(wmip, if_idx, skb, WMI_AP_CONFIG_COMMIT_CMDID, NO_SYNC_WMIFLAG); - ath6kl_dbg(ATH6KL_DBG_WMI, "%s: nw_type=%u auth_mode=%u ch=%u " - "ctrl_flags=0x%x-> res=%d\n", + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: nw_type=%u auth_mode=%u ch=%u ctrl_flags=0x%x-> res=%d\n", __func__, p->nw_type, p->auth_mode, le16_to_cpu(p->ch), le32_to_cpu(p->ctrl_flags), res); return res; @@ -2942,10 +3378,66 @@ int ath6kl_wmi_ap_set_mlme(struct wmi *wmip, u8 if_idx, u8 cmd, const u8 *mac, cm->reason = cpu_to_le16(reason); cm->cmd = cmd; + ath6kl_dbg(ATH6KL_DBG_WMI, "ap_set_mlme: cmd=%d reason=%d\n", cm->cmd, + cm->reason); + return ath6kl_wmi_cmd_send(wmip, if_idx, skb, WMI_AP_SET_MLME_CMDID, NO_SYNC_WMIFLAG); } +int ath6kl_wmi_ap_hidden_ssid(struct wmi *wmi, u8 if_idx, bool enable) +{ + struct sk_buff *skb; + struct wmi_ap_hidden_ssid_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_hidden_ssid_cmd *) skb->data; + cmd->hidden_ssid = enable ? 1 : 0; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_HIDDEN_SSID_CMDID, + NO_SYNC_WMIFLAG); +} + +/* This command will be used to enable/disable AP uAPSD feature */ +int ath6kl_wmi_ap_set_apsd(struct wmi *wmi, u8 if_idx, u8 enable) +{ + struct wmi_ap_set_apsd_cmd *cmd; + struct sk_buff *skb; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_set_apsd_cmd *)skb->data; + cmd->enable = enable; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_SET_APSD_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_apsd_bfrd_traf(struct wmi *wmi, u8 if_idx, + u16 aid, u16 bitmap, u32 flags) +{ + struct wmi_ap_apsd_buffered_traffic_cmd *cmd; + struct sk_buff *skb; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_apsd_buffered_traffic_cmd *)skb->data; + cmd->aid = cpu_to_le16(aid); + cmd->bitmap = cpu_to_le16(bitmap); + cmd->flags = cpu_to_le32(flags); + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_AP_APSD_BUFFERED_TRAFFIC_CMDID, + NO_SYNC_WMIFLAG); +} + static int ath6kl_wmi_pspoll_event_rx(struct wmi *wmi, u8 *datap, int len, struct ath6kl_vif *vif) { @@ -3025,8 +3517,9 @@ int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, if (!skb) return -ENOMEM; - ath6kl_dbg(ATH6KL_DBG_WMI, "set_appie_cmd: mgmt_frm_type=%u " - "ie_len=%u\n", mgmt_frm_type, ie_len); + ath6kl_dbg(ATH6KL_DBG_WMI, + "set_appie_cmd: mgmt_frm_type=%u ie_len=%u\n", + mgmt_frm_type, ie_len); p = (struct wmi_set_appie_cmd *) skb->data; p->mgmt_frm_type = mgmt_frm_type; p->ie_len = ie_len; @@ -3038,6 +3531,29 @@ int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, NO_SYNC_WMIFLAG); } +int ath6kl_wmi_set_ie_cmd(struct wmi *wmi, u8 if_idx, u8 ie_id, u8 ie_field, + const u8 *ie_info, u8 ie_len) +{ + struct sk_buff *skb; + struct wmi_set_ie_cmd *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p) + ie_len); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "set_ie_cmd: ie_id=%u ie_ie_field=%u ie_len=%u\n", + ie_id, ie_field, ie_len); + p = (struct wmi_set_ie_cmd *) skb->data; + p->ie_id = ie_id; + p->ie_field = ie_field; + p->ie_len = ie_len; + if (ie_info && ie_len > 0) + memcpy(p->ie_info, ie_info, ie_len); + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_IE_CMDID, + NO_SYNC_WMIFLAG); +} + int ath6kl_wmi_disable_11b_rates_cmd(struct wmi *wmi, bool disable) { struct sk_buff *skb; @@ -3078,8 +3594,9 @@ int ath6kl_wmi_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx, u32 freq, u32 dur) * ath6kl_wmi_send_mgmt_cmd instead. The new function supports P2P * mgmt operations using station interface. */ -int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, - u32 wait, const u8 *data, u16 data_len) +static int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, + u32 freq, u32 wait, const u8 *data, + u16 data_len) { struct sk_buff *skb; struct wmi_send_action_cmd *p; @@ -3103,8 +3620,9 @@ int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, wmi->last_mgmt_tx_frame = buf; wmi->last_mgmt_tx_frame_len = data_len; - ath6kl_dbg(ATH6KL_DBG_WMI, "send_action_cmd: id=%u freq=%u wait=%u " - "len=%u\n", id, freq, wait, data_len); + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_action_cmd: id=%u freq=%u wait=%u len=%u\n", + id, freq, wait, data_len); p = (struct wmi_send_action_cmd *) skb->data; p->id = cpu_to_le32(id); p->freq = cpu_to_le32(freq); @@ -3115,9 +3633,9 @@ int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, NO_SYNC_WMIFLAG); } -int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, - u32 wait, const u8 *data, u16 data_len, - u32 no_cck) +static int __ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, + u32 freq, u32 wait, const u8 *data, + u16 data_len, u32 no_cck) { struct sk_buff *skb; struct wmi_send_mgmt_cmd *p; @@ -3141,8 +3659,9 @@ int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, wmi->last_mgmt_tx_frame = buf; wmi->last_mgmt_tx_frame_len = data_len; - ath6kl_dbg(ATH6KL_DBG_WMI, "send_action_cmd: id=%u freq=%u wait=%u " - "len=%u\n", id, freq, wait, data_len); + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_action_cmd: id=%u freq=%u wait=%u len=%u\n", + id, freq, wait, data_len); p = (struct wmi_send_mgmt_cmd *) skb->data; p->id = cpu_to_le32(id); p->freq = cpu_to_le32(freq); @@ -3154,6 +3673,32 @@ int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, NO_SYNC_WMIFLAG); } +int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, + u32 wait, const u8 *data, u16 data_len, + u32 no_cck) +{ + int status; + struct ath6kl *ar = wmi->parent_dev; + + if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + /* + * If capable of doing P2P mgmt operations using + * station interface, send additional information like + * supported rates to advertise and xmit rates for + * probe requests + */ + status = __ath6kl_wmi_send_mgmt_cmd(ar->wmi, if_idx, id, freq, + wait, data, data_len, + no_cck); + } else { + status = ath6kl_wmi_send_action_cmd(ar->wmi, if_idx, id, freq, + wait, data, data_len); + } + + return status; +} + int ath6kl_wmi_send_probe_response_cmd(struct wmi *wmi, u8 if_idx, u32 freq, const u8 *dst, const u8 *data, u16 data_len) @@ -3169,8 +3714,9 @@ int ath6kl_wmi_send_probe_response_cmd(struct wmi *wmi, u8 if_idx, u32 freq, if (!skb) return -ENOMEM; - ath6kl_dbg(ATH6KL_DBG_WMI, "send_probe_response_cmd: freq=%u dst=%pM " - "len=%u\n", freq, dst, data_len); + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_probe_response_cmd: freq=%u dst=%pM len=%u\n", + freq, dst, data_len); p = (struct wmi_p2p_probe_response_cmd *) skb->data; p->freq = cpu_to_le32(freq); memcpy(p->destination_addr, dst, ETH_ALEN); @@ -3222,6 +3768,36 @@ int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx) WMI_CANCEL_REMAIN_ON_CHNL_CMDID); } +int ath6kl_wmi_set_inact_period(struct wmi *wmi, u8 if_idx, int inact_timeout) +{ + struct sk_buff *skb; + struct wmi_set_inact_period_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_inact_period_cmd *) skb->data; + cmd->inact_period = cpu_to_le32(inact_timeout); + cmd->num_null_func = 0; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_CONN_INACT_CMDID, + NO_SYNC_WMIFLAG); +} + +static void ath6kl_wmi_hb_challenge_resp_event(struct wmi *wmi, u8 *datap, + int len) +{ + struct wmix_hb_challenge_resp_cmd *cmd; + + if (len < sizeof(struct wmix_hb_challenge_resp_cmd)) + return; + + cmd = (struct wmix_hb_challenge_resp_cmd *) datap; + ath6kl_recovery_hb_event(wmi->parent_dev, + le32_to_cpu(cmd->cookie)); +} + static int ath6kl_wmi_control_rx_xtnd(struct wmi *wmi, struct sk_buff *skb) { struct wmix_cmd_hdr *cmd; @@ -3246,6 +3822,7 @@ static int ath6kl_wmi_control_rx_xtnd(struct wmi *wmi, struct sk_buff *skb) switch (id) { case WMIX_HB_CHALLENGE_RESP_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "wmi event hb challenge resp\n"); + ath6kl_wmi_hb_challenge_resp_event(wmi, datap, len); break; case WMIX_DBGLOG_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "wmi event dbglog len %d\n", len); @@ -3265,32 +3842,104 @@ static int ath6kl_wmi_roam_tbl_event_rx(struct wmi *wmi, u8 *datap, int len) return ath6kl_debug_roam_tbl_event(wmi->parent_dev, datap, len); } -/* Control Path */ -int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) +/* Process interface specific wmi events, caller would free the datap */ +static int ath6kl_wmi_proc_events_vif(struct wmi *wmi, u16 if_idx, u16 cmd_id, + u8 *datap, u32 len) { - struct wmi_cmd_hdr *cmd; struct ath6kl_vif *vif; - u32 len; - u16 id; - u8 if_idx; - u8 *datap; - int ret = 0; - if (WARN_ON(skb == NULL)) + vif = ath6kl_get_vif_by_index(wmi->parent_dev, if_idx); + if (!vif) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "Wmi event for unavailable vif, vif_index:%d\n", + if_idx); return -EINVAL; + } - if (skb->len < sizeof(struct wmi_cmd_hdr)) { - ath6kl_err("bad packet 1\n"); - dev_kfree_skb(skb); + switch (cmd_id) { + case WMI_CONNECT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CONNECT_EVENTID\n"); + return ath6kl_wmi_connect_event_rx(wmi, datap, len, vif); + case WMI_DISCONNECT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DISCONNECT_EVENTID\n"); + return ath6kl_wmi_disconnect_event_rx(wmi, datap, len, vif); + case WMI_TKIP_MICERR_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TKIP_MICERR_EVENTID\n"); + return ath6kl_wmi_tkip_micerr_event_rx(wmi, datap, len, vif); + case WMI_BSSINFO_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_BSSINFO_EVENTID\n"); + return ath6kl_wmi_bssinfo_event_rx(wmi, datap, len, vif); + case WMI_NEIGHBOR_REPORT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_NEIGHBOR_REPORT_EVENTID\n"); + return ath6kl_wmi_neighbor_report_event_rx(wmi, datap, len, + vif); + case WMI_SCAN_COMPLETE_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SCAN_COMPLETE_EVENTID\n"); + return ath6kl_wmi_scan_complete_rx(wmi, datap, len, vif); + case WMI_REPORT_STATISTICS_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_STATISTICS_EVENTID\n"); + return ath6kl_wmi_stats_event_rx(wmi, datap, len, vif); + case WMI_CAC_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CAC_EVENTID\n"); + return ath6kl_wmi_cac_event_rx(wmi, datap, len, vif); + case WMI_PSPOLL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PSPOLL_EVENTID\n"); + return ath6kl_wmi_pspoll_event_rx(wmi, datap, len, vif); + case WMI_DTIMEXPIRY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DTIMEXPIRY_EVENTID\n"); + return ath6kl_wmi_dtimexpiry_event_rx(wmi, datap, len, vif); + case WMI_ADDBA_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ADDBA_REQ_EVENTID\n"); + return ath6kl_wmi_addba_req_event_rx(wmi, datap, len, vif); + case WMI_DELBA_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DELBA_REQ_EVENTID\n"); + return ath6kl_wmi_delba_req_event_rx(wmi, datap, len, vif); + case WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID"); + return ath6kl_wmi_host_sleep_mode_cmd_prcd_evt_rx(wmi, vif); + case WMI_REMAIN_ON_CHNL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REMAIN_ON_CHNL_EVENTID\n"); + return ath6kl_wmi_remain_on_chnl_event_rx(wmi, datap, len, vif); + case WMI_CANCEL_REMAIN_ON_CHNL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_CANCEL_REMAIN_ON_CHNL_EVENTID\n"); + return ath6kl_wmi_cancel_remain_on_chnl_event_rx(wmi, datap, + len, vif); + case WMI_TX_STATUS_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_STATUS_EVENTID\n"); + return ath6kl_wmi_tx_status_event_rx(wmi, datap, len, vif); + case WMI_RX_PROBE_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_PROBE_REQ_EVENTID\n"); + return ath6kl_wmi_rx_probe_req_event_rx(wmi, datap, len, vif); + case WMI_RX_ACTION_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_ACTION_EVENTID\n"); + return ath6kl_wmi_rx_action_event_rx(wmi, datap, len, vif); + case WMI_TXE_NOTIFY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TXE_NOTIFY_EVENTID\n"); + return ath6kl_wmi_txe_notify_event_rx(wmi, datap, len, vif); + default: + ath6kl_dbg(ATH6KL_DBG_WMI, "unknown cmd id 0x%x\n", cmd_id); return -EINVAL; } + return 0; +} + +static int ath6kl_wmi_proc_events(struct wmi *wmi, struct sk_buff *skb) +{ + struct wmi_cmd_hdr *cmd; + int ret = 0; + u32 len; + u16 id; + u8 if_idx; + u8 *datap; + cmd = (struct wmi_cmd_hdr *) skb->data; id = le16_to_cpu(cmd->cmd_id); if_idx = le16_to_cpu(cmd->info1) & WMI_CMD_HDR_IF_ID_MASK; skb_pull(skb, sizeof(struct wmi_cmd_hdr)); - datap = skb->data; len = skb->len; @@ -3298,15 +3947,6 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg_dump(ATH6KL_DBG_WMI_DUMP, NULL, "wmi rx ", datap, len); - vif = ath6kl_get_vif_by_index(wmi->parent_dev, if_idx); - if (!vif) { - ath6kl_dbg(ATH6KL_DBG_WMI, - "Wmi event for unavailable vif, vif_index:%d\n", - if_idx); - dev_kfree_skb(skb); - return -EINVAL; - } - switch (id) { case WMI_GET_BITRATE_CMDID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_BITRATE_CMDID\n"); @@ -3324,26 +3964,10 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_READY_EVENTID\n"); ret = ath6kl_wmi_ready_event_rx(wmi, datap, len); break; - case WMI_CONNECT_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CONNECT_EVENTID\n"); - ret = ath6kl_wmi_connect_event_rx(wmi, datap, len, vif); - break; - case WMI_DISCONNECT_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DISCONNECT_EVENTID\n"); - ret = ath6kl_wmi_disconnect_event_rx(wmi, datap, len, vif); - break; case WMI_PEER_NODE_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PEER_NODE_EVENTID\n"); ret = ath6kl_wmi_peer_node_event_rx(wmi, datap, len); break; - case WMI_TKIP_MICERR_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TKIP_MICERR_EVENTID\n"); - ret = ath6kl_wmi_tkip_micerr_event_rx(wmi, datap, len, vif); - break; - case WMI_BSSINFO_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_BSSINFO_EVENTID\n"); - ret = ath6kl_wmi_bssinfo_event_rx(wmi, datap, len, vif); - break; case WMI_REGDOMAIN_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REGDOMAIN_EVENTID\n"); ath6kl_wmi_regdomain_event(wmi, datap, len); @@ -3352,23 +3976,10 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PSTREAM_TIMEOUT_EVENTID\n"); ret = ath6kl_wmi_pstream_timeout_event_rx(wmi, datap, len); break; - case WMI_NEIGHBOR_REPORT_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_NEIGHBOR_REPORT_EVENTID\n"); - ret = ath6kl_wmi_neighbor_report_event_rx(wmi, datap, len, - vif); - break; - case WMI_SCAN_COMPLETE_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SCAN_COMPLETE_EVENTID\n"); - ret = ath6kl_wmi_scan_complete_rx(wmi, datap, len, vif); - break; case WMI_CMDERROR_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CMDERROR_EVENTID\n"); ret = ath6kl_wmi_error_event_rx(wmi, datap, len); break; - case WMI_REPORT_STATISTICS_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_STATISTICS_EVENTID\n"); - ret = ath6kl_wmi_stats_event_rx(wmi, datap, len, vif); - break; case WMI_RSSI_THRESHOLD_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RSSI_THRESHOLD_EVENTID\n"); ret = ath6kl_wmi_rssi_threshold_event_rx(wmi, datap, len); @@ -3388,10 +3999,6 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_EXTENSION_EVENTID\n"); ret = ath6kl_wmi_control_rx_xtnd(wmi, skb); break; - case WMI_CAC_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CAC_EVENTID\n"); - ret = ath6kl_wmi_cac_event_rx(wmi, datap, len, vif); - break; case WMI_CHANNEL_CHANGE_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CHANNEL_CHANGE_EVENTID\n"); break; @@ -3400,7 +4007,7 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) break; case WMI_TEST_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TEST_EVENTID\n"); - ret = ath6kl_wmi_tcmd_test_report_rx(wmi, datap, len); + ret = ath6kl_wmi_test_rx(wmi, datap, len); break; case WMI_GET_FIXRATES_CMDID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_FIXRATES_CMDID\n"); @@ -3431,28 +4038,12 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_PMKID_LIST_EVENTID\n"); ret = ath6kl_wmi_get_pmkid_list_event_rx(wmi, datap, len); break; - case WMI_PSPOLL_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PSPOLL_EVENTID\n"); - ret = ath6kl_wmi_pspoll_event_rx(wmi, datap, len, vif); - break; - case WMI_DTIMEXPIRY_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DTIMEXPIRY_EVENTID\n"); - ret = ath6kl_wmi_dtimexpiry_event_rx(wmi, datap, len, vif); - break; case WMI_SET_PARAMS_REPLY_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SET_PARAMS_REPLY_EVENTID\n"); break; - case WMI_ADDBA_REQ_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ADDBA_REQ_EVENTID\n"); - ret = ath6kl_wmi_addba_req_event_rx(wmi, datap, len, vif); - break; case WMI_ADDBA_RESP_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ADDBA_RESP_EVENTID\n"); break; - case WMI_DELBA_REQ_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DELBA_REQ_EVENTID\n"); - ret = ath6kl_wmi_delba_req_event_rx(wmi, datap, len, vif); - break; case WMI_REPORT_BTCOEX_CONFIG_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_BTCOEX_CONFIG_EVENTID\n"); @@ -3465,47 +4056,41 @@ int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_COMPLETE_EVENTID\n"); ret = ath6kl_wmi_tx_complete_event_rx(datap, len); break; - case WMI_REMAIN_ON_CHNL_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REMAIN_ON_CHNL_EVENTID\n"); - ret = ath6kl_wmi_remain_on_chnl_event_rx(wmi, datap, len, vif); - break; - case WMI_CANCEL_REMAIN_ON_CHNL_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, - "WMI_CANCEL_REMAIN_ON_CHNL_EVENTID\n"); - ret = ath6kl_wmi_cancel_remain_on_chnl_event_rx(wmi, datap, - len, vif); - break; - case WMI_TX_STATUS_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_STATUS_EVENTID\n"); - ret = ath6kl_wmi_tx_status_event_rx(wmi, datap, len, vif); - break; - case WMI_RX_PROBE_REQ_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_PROBE_REQ_EVENTID\n"); - ret = ath6kl_wmi_rx_probe_req_event_rx(wmi, datap, len, vif); - break; case WMI_P2P_CAPABILITIES_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_P2P_CAPABILITIES_EVENTID\n"); ret = ath6kl_wmi_p2p_capabilities_event_rx(datap, len); break; - case WMI_RX_ACTION_EVENTID: - ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_ACTION_EVENTID\n"); - ret = ath6kl_wmi_rx_action_event_rx(wmi, datap, len, vif); - break; case WMI_P2P_INFO_EVENTID: ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_P2P_INFO_EVENTID\n"); ret = ath6kl_wmi_p2p_info_event_rx(datap, len); break; default: - ath6kl_dbg(ATH6KL_DBG_WMI, "unknown cmd id 0x%x\n", id); - ret = -EINVAL; + /* may be the event is interface specific */ + ret = ath6kl_wmi_proc_events_vif(wmi, if_idx, id, datap, len); break; } dev_kfree_skb(skb); - return ret; } +/* Control Path */ +int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) +{ + if (WARN_ON(skb == NULL)) + return -EINVAL; + + if (skb->len < sizeof(struct wmi_cmd_hdr)) { + ath6kl_err("bad packet 1\n"); + dev_kfree_skb(skb); + return -EINVAL; + } + + trace_ath6kl_wmi_event(skb->data, skb->len); + + return ath6kl_wmi_proc_events(wmi, skb); +} + void ath6kl_wmi_reset(struct wmi *wmi) { spin_lock_bh(&wmi->lock); diff --git a/drivers/net/wireless/ath/ath6kl/wmi.h b/drivers/net/wireless/ath/ath6kl/wmi.h index 42ac311eda4..bb23fc00111 100644 --- a/drivers/net/wireless/ath/ath6kl/wmi.h +++ b/drivers/net/wireless/ath/ath6kl/wmi.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -47,7 +48,7 @@ #define A_BAND_24GHZ 0 #define A_BAND_5GHZ 1 -#define A_NUM_BANDS 2 +#define ATH6KL_NUM_BANDS 2 /* in ms */ #define WMI_IMPLICIT_PSTREAM_INACTIVITY_INT 5000 @@ -105,11 +106,15 @@ struct wmi_data_sync_bufs { #define WMM_AC_VI 2 /* video */ #define WMM_AC_VO 3 /* voice */ +#define WMI_VOICE_USER_PRIORITY 0x7 + struct wmi { u16 stream_exist_for_ac[WMM_NUM_AC]; u8 fat_pipe_exist; struct ath6kl *parent_dev; u8 pwr_mode; + + /* protects fat_pipe_exist and stream_exist_for_ac */ spinlock_t lock; enum htc_endpoint_id ep_id; struct sq_threshold_params @@ -149,8 +154,7 @@ enum wmi_msg_type { #define WMI_DATA_HDR_PS_MASK 0x1 #define WMI_DATA_HDR_PS_SHIFT 5 -#define WMI_DATA_HDR_MORE_MASK 0x1 -#define WMI_DATA_HDR_MORE_SHIFT 5 +#define WMI_DATA_HDR_MORE 0x20 enum wmi_data_hdr_data_type { WMI_DATA_HDR_DATA_TYPE_802_3 = 0, @@ -160,6 +164,13 @@ enum wmi_data_hdr_data_type { WMI_DATA_HDR_DATA_TYPE_ACL, }; +/* Bitmap of data header flags */ +enum wmi_data_hdr_flags { + WMI_DATA_HDR_FLAGS_MORE = 0x1, + WMI_DATA_HDR_FLAGS_EOSP = 0x2, + WMI_DATA_HDR_FLAGS_UAPSD = 0x4, +}; + #define WMI_DATA_HDR_DATA_TYPE_MASK 0x3 #define WMI_DATA_HDR_DATA_TYPE_SHIFT 6 @@ -173,8 +184,15 @@ enum wmi_data_hdr_data_type { #define WMI_DATA_HDR_META_MASK 0x7 #define WMI_DATA_HDR_META_SHIFT 13 +#define WMI_DATA_HDR_PAD_BEFORE_DATA_MASK 0xFF +#define WMI_DATA_HDR_PAD_BEFORE_DATA_SHIFT 0x8 + +/* Macros for operating on WMI_DATA_HDR (info3) field */ #define WMI_DATA_HDR_IF_IDX_MASK 0xF +#define WMI_DATA_HDR_TRIG 0x10 +#define WMI_DATA_HDR_EOSP 0x10 + struct wmi_data_hdr { s8 rssi; @@ -203,7 +221,8 @@ struct wmi_data_hdr { /* * usage of info3, 16-bit: * b3:b0 - Interface index - * b15:b4 - Reserved + * b4 - uAPSD trigger in rx & EOSP in tx + * b15:b5 - Reserved */ __le16 info3; } __packed; @@ -257,6 +276,9 @@ static inline u8 wmi_data_hdr_get_if_idx(struct wmi_data_hdr *dhdr) #define WMI_META_VERSION_1 0x01 #define WMI_META_VERSION_2 0x02 +/* Flag to signal to FW to calculate TCP checksum */ +#define WMI_META_V2_FLAG_CSUM_OFFLOAD 0x01 + struct wmi_tx_meta_v1 { /* packet ID to identify the tx request */ u8 pkt_id; @@ -406,6 +428,7 @@ enum wmi_cmd_id { WMI_SET_FRAMERATES_CMDID, WMI_SET_AP_PS_CMDID, WMI_SET_QOS_SUPP_CMDID, + WMI_SET_IE_CMDID, /* WMI_THIN_RESERVED_... mark the start and end * values for WMI_THIN_RESERVED command IDs. These @@ -601,6 +624,24 @@ enum wmi_cmd_id { WMI_SEND_MGMT_CMDID, WMI_BEGIN_SCAN_CMDID, + WMI_SET_BLACK_LIST, + WMI_SET_MCASTRATE, + + WMI_STA_BMISS_ENHANCE_CMDID, + + WMI_SET_REGDOMAIN_CMDID, + + WMI_SET_RSSI_FILTER_CMDID, + + WMI_SET_KEEP_ALIVE_EXT, + + WMI_VOICE_DETECTION_ENABLE_CMDID, + + WMI_SET_TXE_NOTIFY_CMDID, + + WMI_SET_RECOVERY_TEST_PARAMETER_CMDID, /*0xf094*/ + + WMI_ENABLE_SCHED_SCAN_CMDID, }; enum wmi_mgmt_frame_type { @@ -612,6 +653,11 @@ enum wmi_mgmt_frame_type { WMI_NUM_MGMT_FRAME }; +enum wmi_ie_field_type { + WMI_RSN_IE_CAPB = 0x1, + WMI_IE_FULL = 0xFF, /* indicats full IE */ +}; + /* WMI_CONNECT_CMDID */ enum network_type { INFRA_NETWORK = 0x01, @@ -646,7 +692,6 @@ enum auth_mode { WPA2_AUTH_CCKM = 0x40, }; -#define WMI_MIN_KEY_INDEX 0 #define WMI_MAX_KEY_INDEX 3 #define WMI_MAX_KEY_LEN 32 @@ -812,7 +857,7 @@ struct wmi_begin_scan_cmd { u8 scan_type; /* Supported rates to advertise in the probe request frames */ - struct wmi_supp_rates supp_rates[IEEE80211_NUM_BANDS]; + struct wmi_supp_rates supp_rates[ATH6KL_NUM_BANDS]; /* how many channels follow */ u8 num_ch; @@ -853,7 +898,6 @@ struct wmi_start_scan_cmd { * flags here */ enum wmi_scan_ctrl_flags_bits { - /* set if can scan in the connect cmd */ CONNECT_SCAN_CTRL_FLAGS = 0x01, @@ -910,6 +954,11 @@ struct wmi_scan_params_cmd { __le32 max_dfsch_act_time; } __packed; +/* WMI_ENABLE_SCHED_SCAN_CMDID */ +struct wmi_enable_sched_scan_cmd { + u8 enable; +} __packed; + /* WMI_SET_BSS_FILTER_CMDID */ enum wmi_bss_filter { /* no beacons forwarded */ @@ -933,6 +982,9 @@ enum wmi_bss_filter { /* beacons matching probed ssid */ PROBED_SSID_FILTER, + /* beacons matching matched ssid */ + MATCHED_SSID_FILTER, + /* marker only */ LAST_BSS_FILTER, }; @@ -951,7 +1003,7 @@ struct wmi_bss_filter_cmd { } __packed; /* WMI_SET_PROBED_SSID_CMDID */ -#define MAX_PROBED_SSID_INDEX 9 +#define MAX_PROBED_SSIDS 16 enum wmi_ssid_flag { /* disables entry */ @@ -962,10 +1014,13 @@ enum wmi_ssid_flag { /* probes for any ssid */ ANY_SSID_FLAG = 0x02, + + /* match for ssid */ + MATCH_SSID_FLAG = 0x08, }; struct wmi_probed_ssid_cmd { - /* 0 to MAX_PROBED_SSID_INDEX */ + /* 0 to MAX_PROBED_SSIDS - 1 */ u8 entry_index; /* see, enum wmi_ssid_flg */ @@ -984,6 +1039,22 @@ struct wmi_listen_int_cmd { __le16 num_beacons; } __packed; +/* WMI_SET_BMISS_TIME_CMDID */ +struct wmi_bmiss_time_cmd { + __le16 bmiss_time; + __le16 num_beacons; +}; + +/* WMI_STA_ENHANCE_BMISS_CMDID */ +struct wmi_sta_bmiss_enhance_cmd { + u8 enable; +} __packed; + +struct wmi_set_regdomain_cmd { + u8 length; + u8 iso_name[2]; +} __packed; + /* WMI_SET_POWER_MODE_CMDID */ enum wmi_power_mode { REC_POWER = 0x01, @@ -996,12 +1067,12 @@ struct wmi_power_mode_cmd { } __packed; /* - * Policy to determnine whether power save failure event should be sent to + * Policy to determine whether power save failure event should be sent to * host during scanning */ enum power_save_fail_event_policy { SEND_POWER_SAVE_FAIL_EVENT_ALWAYS = 1, - IGNORE_POWER_SAVE_FAIL_EVENT_DURING_SCAN = 2, + IGNORE_PS_FAIL_DURING_SCAN = 2, }; struct wmi_power_params_cmd { @@ -1015,6 +1086,36 @@ struct wmi_power_params_cmd { __le16 ps_fail_event_policy; } __packed; +/* + * Ratemask for below modes should be passed + * to WMI_SET_TX_SELECT_RATES_CMDID. + * AR6003 has 32 bit mask for each modes. + * First 12 bits for legacy rates, 13 to 20 + * bits for HT 20 rates and 21 to 28 bits for + * HT 40 rates + */ +enum wmi_mode_phy { + WMI_RATES_MODE_11A = 0, + WMI_RATES_MODE_11G, + WMI_RATES_MODE_11B, + WMI_RATES_MODE_11GONLY, + WMI_RATES_MODE_11A_HT20, + WMI_RATES_MODE_11G_HT20, + WMI_RATES_MODE_11A_HT40, + WMI_RATES_MODE_11G_HT40, + WMI_RATES_MODE_MAX +}; + +/* WMI_SET_TX_SELECT_RATES_CMDID */ +struct wmi_set_tx_select_rates32_cmd { + __le32 ratemask[WMI_RATES_MODE_MAX]; +} __packed; + +/* WMI_SET_TX_SELECT_RATES_CMDID */ +struct wmi_set_tx_select_rates64_cmd { + __le64 ratemask[WMI_RATES_MODE_MAX]; +} __packed; + /* WMI_SET_DISC_TIMEOUT_CMDID */ struct wmi_disc_timeout_cmd { /* seconds */ @@ -1120,6 +1221,7 @@ enum wmi_phy_mode { WMI_11AG_MODE = 0x3, WMI_11B_MODE = 0x4, WMI_11GONLY_MODE = 0x5, + WMI_11G_HT20 = 0x6, }; #define WMI_MAX_CHANNELS 32 @@ -1197,9 +1299,14 @@ struct wmi_snr_threshold_params_cmd { u8 reserved[3]; } __packed; +/* Don't report BSSs with signal (RSSI) below this threshold */ +struct wmi_set_rssi_filter_cmd { + s8 rssi; +} __packed; + enum wmi_preamble_policy { WMI_IGNORE_BARKER_IN_ERP = 0, - WMI_DONOT_IGNORE_BARKER_IN_ERP + WMI_FOLLOW_BARKER_IN_ERP, }; struct wmi_set_lpreamble_cmd { @@ -1237,6 +1344,25 @@ enum target_event_report_config { NO_DISCONN_EVT_IN_RECONN }; +struct wmi_mcast_filter_cmd { + u8 mcast_all_enable; +} __packed; + +#define ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE 6 +struct wmi_mcast_filter_add_del_cmd { + u8 mcast_mac[ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE]; +} __packed; + +struct wmi_set_htcap_cmd { + u8 band; + u8 ht_enable; + u8 ht40_supported; + u8 ht20_sgi; + u8 ht40_sgi; + u8 intolerant_40mhz; + u8 max_ampdu_len_exp; +} __packed; + /* Command Replies */ /* WMI_GET_CHANNEL_LIST_CMDID reply */ @@ -1335,6 +1461,8 @@ enum wmi_event_id { WMI_P2P_START_SDPD_EVENTID, WMI_P2P_SDPD_RX_EVENTID, + WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID = 0x1047, + WMI_THIN_RESERVED_START_EVENTID = 0x8000, /* Events in this range are reserved for thinmode */ WMI_THIN_RESERVED_END_EVENTID = 0x8fff, @@ -1355,6 +1483,20 @@ enum wmi_event_id { WMI_P2P_CAPABILITIES_EVENTID, WMI_RX_ACTION_EVENTID, WMI_P2P_INFO_EVENTID, + + /* WPS Events */ + WMI_WPS_GET_STATUS_EVENTID, + WMI_WPS_PROFILE_EVENTID, + + /* more P2P events */ + WMI_NOA_INFO_EVENTID, + WMI_OPPPS_INFO_EVENTID, + WMI_PORT_STATUS_EVENTID, + + /* 802.11w */ + WMI_GET_RSN_CAP_EVENTID, + + WMI_TXE_NOTIFY_EVENTID, }; struct wmi_ready_event_2 { @@ -1364,6 +1506,16 @@ struct wmi_ready_event_2 { u8 phy_cap; } __packed; +/* WMI_PHY_CAPABILITY */ +enum wmi_phy_cap { + WMI_11A_CAP = 0x01, + WMI_11G_CAP = 0x02, + WMI_11AG_CAP = 0x03, + WMI_11AN_CAP = 0x04, + WMI_11GN_CAP = 0x05, + WMI_11AGN_CAP = 0x06, +}; + /* Connect Event */ struct wmi_connect_event { union { @@ -1416,6 +1568,17 @@ enum wmi_disconnect_reason { IBSS_MERGE = 0xe, }; +/* AP mode disconnect proto_reasons */ +enum ap_disconnect_reason { + WMI_AP_REASON_STA_LEFT = 101, + WMI_AP_REASON_FROM_HOST = 102, + WMI_AP_REASON_COMM_TIMEOUT = 103, + WMI_AP_REASON_MAX_STA = 104, + WMI_AP_REASON_ACL = 105, + WMI_AP_REASON_STA_ROAM = 106, + WMI_AP_REASON_DFS_CHANNEL = 107, +}; + #define ATH6KL_COUNTRY_RD_SHIFT 16 struct ath6kl_wmi_regdomain { @@ -1496,6 +1659,14 @@ struct roam_ctrl_cmd { u8 roam_ctrl; } __packed; +struct set_beacon_int_cmd { + __le32 beacon_intvl; +} __packed; + +struct set_dtim_cmd { + __le32 dtim_period; +} __packed; + /* BSS INFO HDR version 2.0 */ struct wmi_bss_info_hdr2 { __le16 ch; /* frequency in MHz */ @@ -1624,6 +1795,9 @@ struct rx_stats { a_sle32 ucast_rate; } __packed; +#define RATE_INDEX_WITHOUT_SGI_MASK 0x7f +#define RATE_INDEX_MSB 0x80 + struct tkip_ccmp_stats { __le32 tkip_local_mic_fail; __le32 tkip_cnter_measures_invoked; @@ -1880,13 +2054,20 @@ struct wmi_set_appie_cmd { u8 ie_info[0]; } __packed; +struct wmi_set_ie_cmd { + u8 ie_id; + u8 ie_field; /* enum wmi_ie_field_type */ + u8 ie_len; + u8 reserved; + u8 ie_info[0]; +} __packed; + /* Notify the WSC registration status to the target */ #define WSC_REG_ACTIVE 1 #define WSC_REG_INACTIVE 0 #define WOW_MAX_FILTERS_PER_LIST 4 #define WOW_PATTERN_SIZE 64 -#define WOW_MASK_SIZE 64 #define MAC_MAX_FILTERS_PER_LIST 4 @@ -1895,7 +2076,7 @@ struct wow_filter { u8 wow_filter_id; u8 wow_filter_size; u8 wow_filter_offset; - u8 wow_filter_mask[WOW_MASK_SIZE]; + u8 wow_filter_mask[WOW_PATTERN_SIZE]; u8 wow_filter_pattern[WOW_PATTERN_SIZE]; } __packed; @@ -1903,7 +2084,7 @@ struct wow_filter { struct wmi_set_ip_cmd { /* IP in network byte order */ - __le32 ips[MAX_IP_ADDRS]; + __be32 ips[MAX_IP_ADDRS]; } __packed; enum ath6kl_wow_filters { @@ -1954,6 +2135,19 @@ struct wmi_del_wow_pattern_cmd { __le16 filter_id; } __packed; +/* WMI_SET_TXE_NOTIFY_CMDID */ +struct wmi_txe_notify_cmd { + __le32 rate; + __le32 pkts; + __le32 intvl; +} __packed; + +/* WMI_TXE_NOTIFY_EVENTID */ +struct wmi_txe_notify_event { + __le32 rate; + __le32 pkts; +} __packed; + /* WMI_SET_AKMP_PARAMS_CMD */ struct wmi_pmkid { @@ -2104,7 +2298,29 @@ struct wmi_rx_frame_format_cmd { u8 reserved[1]; } __packed; +struct wmi_ap_hidden_ssid_cmd { + u8 hidden_ssid; +} __packed; + +struct wmi_set_inact_period_cmd { + __le32 inact_period; + u8 num_null_func; +} __packed; + /* AP mode events */ +struct wmi_ap_set_apsd_cmd { + u8 enable; +} __packed; + +enum wmi_ap_apsd_buffered_traffic_flags { + WMI_AP_APSD_NO_DELIVERY_FRAMES = 0x1, +}; + +struct wmi_ap_apsd_buffered_traffic_cmd { + __le16 aid; + __le16 bitmap; + __le32 flags; +} __packed; /* WMI_PS_POLL_EVENT */ struct wmi_pspoll_event { @@ -2321,7 +2537,7 @@ enum htc_endpoint_id ath6kl_wmi_get_control_ep(struct wmi *wmi); void ath6kl_wmi_set_control_ep(struct wmi *wmi, enum htc_endpoint_id ep_id); int ath6kl_wmi_dix_2_dot3(struct wmi *wmi, struct sk_buff *skb); int ath6kl_wmi_data_hdr_add(struct wmi *wmi, struct sk_buff *skb, - u8 msg_type, bool more_data, + u8 msg_type, u32 flags, enum wmi_data_hdr_data_type data_type, u8 meta_ver, void *tx_meta_info, u8 if_idx); @@ -2350,11 +2566,6 @@ int ath6kl_wmi_connect_cmd(struct wmi *wmi, u8 if_idx, int ath6kl_wmi_reconnect_cmd(struct wmi *wmi, u8 if_idx, u8 *bssid, u16 channel); int ath6kl_wmi_disconnect_cmd(struct wmi *wmi, u8 if_idx); -int ath6kl_wmi_startscan_cmd(struct wmi *wmi, u8 if_idx, - enum wmi_scan_type scan_type, - u32 force_fgscan, u32 is_legacy, - u32 home_dwell_time, u32 force_scan_interval, - s8 num_chan, u16 *ch_list); int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, enum wmi_scan_type scan_type, @@ -2362,6 +2573,7 @@ int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, u32 home_dwell_time, u32 force_scan_interval, s8 num_chan, u16 *ch_list, u32 no_cck, u32 *rates); +int ath6kl_wmi_enable_sched_scan_cmd(struct wmi *wmi, u8 if_idx, bool enable); int ath6kl_wmi_scanparams_cmd(struct wmi *wmi, u8 if_idx, u16 fg_start_sec, u16 fg_end_sec, u16 bg_sec, @@ -2376,6 +2588,8 @@ int ath6kl_wmi_probedssid_cmd(struct wmi *wmi, u8 if_idx, u8 index, u8 flag, int ath6kl_wmi_listeninterval_cmd(struct wmi *wmi, u8 if_idx, u16 listen_interval, u16 listen_beacons); +int ath6kl_wmi_bmisstime_cmd(struct wmi *wmi, u8 if_idx, + u16 bmiss_time, u16 num_beacons); int ath6kl_wmi_powermode_cmd(struct wmi *wmi, u8 if_idx, u8 pwr_mode); int ath6kl_wmi_pmparams_cmd(struct wmi *wmi, u8 if_idx, u16 idle_period, u16 ps_poll_num, u16 dtim_policy, @@ -2402,7 +2616,7 @@ int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index, u8 *key_material, u8 key_op_ctrl, u8 *mac_addr, enum wmi_sync_flag sync_flag); -int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, u8 *krk); +int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, const u8 *krk); int ath6kl_wmi_deletekey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index); int ath6kl_wmi_setpmkid_cmd(struct wmi *wmi, u8 if_idx, const u8 *bssid, const u8 *pmkid, bool set); @@ -2413,26 +2627,55 @@ int ath6kl_wmi_get_roam_tbl_cmd(struct wmi *wmi); int ath6kl_wmi_set_wmm_txop(struct wmi *wmi, u8 if_idx, enum wmi_txop_cfg cfg); int ath6kl_wmi_set_keepalive_cmd(struct wmi *wmi, u8 if_idx, u8 keep_alive_intvl); +int ath6kl_wmi_set_htcap_cmd(struct wmi *wmi, u8 if_idx, + enum ieee80211_band band, + struct ath6kl_htcap *htcap); int ath6kl_wmi_test_cmd(struct wmi *wmi, void *buf, size_t len); s32 ath6kl_wmi_get_rate(s8 rate_index); -int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, struct wmi_set_ip_cmd *ip_cmd); +int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, u8 if_idx, + __be32 ips0, __be32 ips1); int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, enum ath6kl_host_mode host_mode); +int ath6kl_wmi_set_bitrate_mask(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask); int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, enum ath6kl_wow_mode wow_mode, u32 filter, u16 host_req_delay); int ath6kl_wmi_add_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, u8 list_id, u8 filter_size, - u8 filter_offset, u8 *filter, u8 *mask); + u8 filter_offset, const u8 *filter, + const u8 *mask); int ath6kl_wmi_del_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, u16 list_id, u16 filter_id); +int ath6kl_wmi_set_rssi_filter_cmd(struct wmi *wmi, u8 if_idx, s8 rssi); int ath6kl_wmi_set_roam_lrssi_cmd(struct wmi *wmi, u8 lrssi); +int ath6kl_wmi_ap_set_dtim_cmd(struct wmi *wmi, u8 if_idx, u32 dtim_period); +int ath6kl_wmi_ap_set_beacon_intvl_cmd(struct wmi *wmi, u8 if_idx, + u32 beacon_interval); int ath6kl_wmi_force_roam_cmd(struct wmi *wmi, const u8 *bssid); int ath6kl_wmi_set_roam_mode_cmd(struct wmi *wmi, enum wmi_roam_mode mode); +int ath6kl_wmi_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, bool mc_all_on); +int ath6kl_wmi_add_del_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, + u8 *filter, bool add_filter); +int ath6kl_wmi_sta_bmiss_enhance_cmd(struct wmi *wmi, u8 if_idx, bool enable); +int ath6kl_wmi_set_txe_notify(struct wmi *wmi, u8 idx, + u32 rate, u32 pkts, u32 intvl); +int ath6kl_wmi_set_regdomain_cmd(struct wmi *wmi, const char *alpha2); + +/* AP mode uAPSD */ +int ath6kl_wmi_ap_set_apsd(struct wmi *wmi, u8 if_idx, u8 enable); + +int ath6kl_wmi_set_apsd_bfrd_traf(struct wmi *wmi, + u8 if_idx, u16 aid, + u16 bitmap, u32 flags); + +u8 ath6kl_wmi_get_traffic_class(u8 user_priority); +u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri); /* AP mode */ +int ath6kl_wmi_ap_hidden_ssid(struct wmi *wmi, u8 if_idx, bool enable); int ath6kl_wmi_ap_profile_commit(struct wmi *wmip, u8 if_idx, struct wmi_connect_cmd *p); @@ -2448,15 +2691,15 @@ int ath6kl_wmi_set_rx_frame_format_cmd(struct wmi *wmi, u8 if_idx, int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, const u8 *ie, u8 ie_len); +int ath6kl_wmi_set_ie_cmd(struct wmi *wmi, u8 if_idx, u8 ie_id, u8 ie_field, + const u8 *ie_info, u8 ie_len); + /* P2P */ int ath6kl_wmi_disable_11b_rates_cmd(struct wmi *wmi, bool disable); int ath6kl_wmi_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx, u32 freq, u32 dur); -int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, - u32 wait, const u8 *data, u16 data_len); - int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, u32 wait, const u8 *data, u16 data_len, u32 no_cck); @@ -2474,8 +2717,12 @@ int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx); int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, const u8 *ie, u8 ie_len); +int ath6kl_wmi_set_inact_period(struct wmi *wmi, u8 if_idx, int inact_timeout); + void ath6kl_wmi_sscan_timer(unsigned long ptr); +int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source); + struct ath6kl_vif *ath6kl_get_vif_by_index(struct ath6kl *ar, u8 if_idx); void *ath6kl_wmi_init(struct ath6kl *devt); void ath6kl_wmi_shutdown(struct wmi *wmi); |
