ath9k_htc: Support for AR9271 chipset.
Features: * Station mode * IBSS mode * Monitor mode * Legacy support * HT support * TX/RX 11n Aggregation * HW encryption * LED * Suspend/Resume For more information: http://wireless.kernel.org/en/users/Drivers/ath9k_htc Signed-off-by: Sujith <Sujith.Manoharan@atheros.com> Signed-off-by: Vasanthakumar Thiagarajan <vasanth@atheros.com> Signed-off-by: Senthil Balasubramanian <senthilkumar@atheros.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
736b3a27b3
commit
fb9987d0f7
@ -3,7 +3,7 @@ menuconfig ATH_COMMON
|
||||
depends on CFG80211
|
||||
---help---
|
||||
This will enable the support for the Atheros wireless drivers.
|
||||
ath5k, ath9k and ar9170 drivers share some common code, this option
|
||||
ath5k, ath9k, ath9k_htc and ar9170 drivers share some common code, this option
|
||||
enables the common ath.ko module which shares common helpers.
|
||||
|
||||
For more information and documentation on this module you can visit:
|
||||
|
@ -32,3 +32,24 @@ config ATH9K_DEBUGFS
|
||||
|
||||
Also required for changing debug message flags at run time.
|
||||
|
||||
config ATH9K_HTC
|
||||
tristate "Atheros HTC based wireless cards support"
|
||||
depends on USB && MAC80211
|
||||
select ATH9K_HW
|
||||
select MAC80211_LEDS
|
||||
select LEDS_CLASS
|
||||
select NEW_LEDS
|
||||
select ATH9K_COMMON
|
||||
---help---
|
||||
Support for Atheros HTC based cards.
|
||||
Chipsets supported: AR9271
|
||||
|
||||
For more information: http://wireless.kernel.org/en/users/Drivers/ath9k_htc
|
||||
|
||||
The built module will be ath9k_htc.
|
||||
|
||||
config ATH9K_HTC_DEBUGFS
|
||||
bool "Atheros ath9k_htc debugging"
|
||||
depends on ATH9K_HTC && DEBUG_FS
|
||||
---help---
|
||||
Say Y, if you need access to ath9k_htc's statistics.
|
||||
|
@ -28,3 +28,13 @@ obj-$(CONFIG_ATH9K_HW) += ath9k_hw.o
|
||||
|
||||
obj-$(CONFIG_ATH9K_COMMON) += ath9k_common.o
|
||||
ath9k_common-y:= common.o
|
||||
|
||||
ath9k_htc-y += htc_hst.o \
|
||||
hif_usb.o \
|
||||
wmi.o \
|
||||
htc_drv_txrx.o \
|
||||
htc_drv_main.o \
|
||||
htc_drv_beacon.o \
|
||||
htc_drv_init.o
|
||||
|
||||
obj-$(CONFIG_ATH9K_HTC) += ath9k_htc.o
|
||||
|
@ -286,6 +286,427 @@ int ath9k_cmn_padpos(__le16 frame_control)
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_padpos);
|
||||
|
||||
int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb)
|
||||
{
|
||||
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
||||
|
||||
if (tx_info->control.hw_key) {
|
||||
if (tx_info->control.hw_key->alg == ALG_WEP)
|
||||
return ATH9K_KEY_TYPE_WEP;
|
||||
else if (tx_info->control.hw_key->alg == ALG_TKIP)
|
||||
return ATH9K_KEY_TYPE_TKIP;
|
||||
else if (tx_info->control.hw_key->alg == ALG_CCMP)
|
||||
return ATH9K_KEY_TYPE_AES;
|
||||
}
|
||||
|
||||
return ATH9K_KEY_TYPE_CLEAR;
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_get_hw_crypto_keytype);
|
||||
|
||||
/*
|
||||
* Calculate the RX filter to be set in the HW.
|
||||
*/
|
||||
u32 ath9k_cmn_calcrxfilter(struct ieee80211_hw *hw, struct ath_hw *ah,
|
||||
unsigned int rxfilter)
|
||||
{
|
||||
#define RX_FILTER_PRESERVE (ATH9K_RX_FILTER_PHYERR | ATH9K_RX_FILTER_PHYRADAR)
|
||||
|
||||
u32 rfilt;
|
||||
|
||||
rfilt = (ath9k_hw_getrxfilter(ah) & RX_FILTER_PRESERVE)
|
||||
| ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST
|
||||
| ATH9K_RX_FILTER_MCAST;
|
||||
|
||||
/* If not a STA, enable processing of Probe Requests */
|
||||
if (ah->opmode != NL80211_IFTYPE_STATION)
|
||||
rfilt |= ATH9K_RX_FILTER_PROBEREQ;
|
||||
|
||||
/*
|
||||
* Set promiscuous mode when FIF_PROMISC_IN_BSS is enabled for station
|
||||
* mode interface or when in monitor mode. AP mode does not need this
|
||||
* since it receives all in-BSS frames anyway.
|
||||
*/
|
||||
if (((ah->opmode != NL80211_IFTYPE_AP) &&
|
||||
(rxfilter & FIF_PROMISC_IN_BSS)) ||
|
||||
(ah->opmode == NL80211_IFTYPE_MONITOR))
|
||||
rfilt |= ATH9K_RX_FILTER_PROM;
|
||||
|
||||
if (rxfilter & FIF_CONTROL)
|
||||
rfilt |= ATH9K_RX_FILTER_CONTROL;
|
||||
|
||||
if ((ah->opmode == NL80211_IFTYPE_STATION) &&
|
||||
!(rxfilter & FIF_BCN_PRBRESP_PROMISC))
|
||||
rfilt |= ATH9K_RX_FILTER_MYBEACON;
|
||||
else
|
||||
rfilt |= ATH9K_RX_FILTER_BEACON;
|
||||
|
||||
if ((AR_SREV_9280_10_OR_LATER(ah) ||
|
||||
AR_SREV_9285_10_OR_LATER(ah)) &&
|
||||
(ah->opmode == NL80211_IFTYPE_AP) &&
|
||||
(rxfilter & FIF_PSPOLL))
|
||||
rfilt |= ATH9K_RX_FILTER_PSPOLL;
|
||||
|
||||
if (conf_is_ht(&hw->conf))
|
||||
rfilt |= ATH9K_RX_FILTER_COMP_BAR;
|
||||
|
||||
return rfilt;
|
||||
|
||||
#undef RX_FILTER_PRESERVE
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_calcrxfilter);
|
||||
|
||||
/*
|
||||
* Recv initialization for opmode change.
|
||||
*/
|
||||
void ath9k_cmn_opmode_init(struct ieee80211_hw *hw, struct ath_hw *ah,
|
||||
unsigned int rxfilter)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
|
||||
u32 rfilt, mfilt[2];
|
||||
|
||||
/* configure rx filter */
|
||||
rfilt = ath9k_cmn_calcrxfilter(hw, ah, rxfilter);
|
||||
ath9k_hw_setrxfilter(ah, rfilt);
|
||||
|
||||
/* configure bssid mask */
|
||||
if (ah->caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK)
|
||||
ath_hw_setbssidmask(common);
|
||||
|
||||
/* configure operational mode */
|
||||
ath9k_hw_setopmode(ah);
|
||||
|
||||
/* Handle any link-level address change. */
|
||||
ath9k_hw_setmac(ah, common->macaddr);
|
||||
|
||||
/* calculate and install multicast filter */
|
||||
mfilt[0] = mfilt[1] = ~0;
|
||||
ath9k_hw_setmcastfilter(ah, mfilt[0], mfilt[1]);
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_opmode_init);
|
||||
|
||||
static u32 ath9k_get_extchanmode(struct ieee80211_channel *chan,
|
||||
enum nl80211_channel_type channel_type)
|
||||
{
|
||||
u32 chanmode = 0;
|
||||
|
||||
switch (chan->band) {
|
||||
case IEEE80211_BAND_2GHZ:
|
||||
switch (channel_type) {
|
||||
case NL80211_CHAN_NO_HT:
|
||||
case NL80211_CHAN_HT20:
|
||||
chanmode = CHANNEL_G_HT20;
|
||||
break;
|
||||
case NL80211_CHAN_HT40PLUS:
|
||||
chanmode = CHANNEL_G_HT40PLUS;
|
||||
break;
|
||||
case NL80211_CHAN_HT40MINUS:
|
||||
chanmode = CHANNEL_G_HT40MINUS;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IEEE80211_BAND_5GHZ:
|
||||
switch (channel_type) {
|
||||
case NL80211_CHAN_NO_HT:
|
||||
case NL80211_CHAN_HT20:
|
||||
chanmode = CHANNEL_A_HT20;
|
||||
break;
|
||||
case NL80211_CHAN_HT40PLUS:
|
||||
chanmode = CHANNEL_A_HT40PLUS;
|
||||
break;
|
||||
case NL80211_CHAN_HT40MINUS:
|
||||
chanmode = CHANNEL_A_HT40MINUS;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return chanmode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update internal channel flags.
|
||||
*/
|
||||
void ath9k_cmn_update_ichannel(struct ieee80211_hw *hw,
|
||||
struct ath9k_channel *ichan)
|
||||
{
|
||||
struct ieee80211_channel *chan = hw->conf.channel;
|
||||
struct ieee80211_conf *conf = &hw->conf;
|
||||
|
||||
ichan->channel = chan->center_freq;
|
||||
ichan->chan = chan;
|
||||
|
||||
if (chan->band == IEEE80211_BAND_2GHZ) {
|
||||
ichan->chanmode = CHANNEL_G;
|
||||
ichan->channelFlags = CHANNEL_2GHZ | CHANNEL_OFDM | CHANNEL_G;
|
||||
} else {
|
||||
ichan->chanmode = CHANNEL_A;
|
||||
ichan->channelFlags = CHANNEL_5GHZ | CHANNEL_OFDM;
|
||||
}
|
||||
|
||||
if (conf_is_ht(conf))
|
||||
ichan->chanmode = ath9k_get_extchanmode(chan,
|
||||
conf->channel_type);
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_update_ichannel);
|
||||
|
||||
/*
|
||||
* Get the internal channel reference.
|
||||
*/
|
||||
struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw,
|
||||
struct ath_hw *ah)
|
||||
{
|
||||
struct ieee80211_channel *curchan = hw->conf.channel;
|
||||
struct ath9k_channel *channel;
|
||||
u8 chan_idx;
|
||||
|
||||
chan_idx = curchan->hw_value;
|
||||
channel = &ah->channels[chan_idx];
|
||||
ath9k_cmn_update_ichannel(hw, channel);
|
||||
|
||||
return channel;
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_get_curchannel);
|
||||
|
||||
static int ath_setkey_tkip(struct ath_common *common, u16 keyix, const u8 *key,
|
||||
struct ath9k_keyval *hk, const u8 *addr,
|
||||
bool authenticator)
|
||||
{
|
||||
struct ath_hw *ah = common->ah;
|
||||
const u8 *key_rxmic;
|
||||
const u8 *key_txmic;
|
||||
|
||||
key_txmic = key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY;
|
||||
key_rxmic = key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY;
|
||||
|
||||
if (addr == NULL) {
|
||||
/*
|
||||
* Group key installation - only two key cache entries are used
|
||||
* regardless of splitmic capability since group key is only
|
||||
* used either for TX or RX.
|
||||
*/
|
||||
if (authenticator) {
|
||||
memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic));
|
||||
memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_mic));
|
||||
} else {
|
||||
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
|
||||
memcpy(hk->kv_txmic, key_rxmic, sizeof(hk->kv_mic));
|
||||
}
|
||||
return ath9k_hw_set_keycache_entry(ah, keyix, hk, addr);
|
||||
}
|
||||
if (!common->splitmic) {
|
||||
/* TX and RX keys share the same key cache entry. */
|
||||
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
|
||||
memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_txmic));
|
||||
return ath9k_hw_set_keycache_entry(ah, keyix, hk, addr);
|
||||
}
|
||||
|
||||
/* Separate key cache entries for TX and RX */
|
||||
|
||||
/* TX key goes at first index, RX key at +32. */
|
||||
memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic));
|
||||
if (!ath9k_hw_set_keycache_entry(ah, keyix, hk, NULL)) {
|
||||
/* TX MIC entry failed. No need to proceed further */
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Setting TX MIC Key Failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
|
||||
/* XXX delete tx key on failure? */
|
||||
return ath9k_hw_set_keycache_entry(ah, keyix + 32, hk, addr);
|
||||
}
|
||||
|
||||
static int ath_reserve_key_cache_slot_tkip(struct ath_common *common)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) {
|
||||
if (test_bit(i, common->keymap) ||
|
||||
test_bit(i + 64, common->keymap))
|
||||
continue; /* At least one part of TKIP key allocated */
|
||||
if (common->splitmic &&
|
||||
(test_bit(i + 32, common->keymap) ||
|
||||
test_bit(i + 64 + 32, common->keymap)))
|
||||
continue; /* At least one part of TKIP key allocated */
|
||||
|
||||
/* Found a free slot for a TKIP key */
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ath_reserve_key_cache_slot(struct ath_common *common)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* First, try to find slots that would not be available for TKIP. */
|
||||
if (common->splitmic) {
|
||||
for (i = IEEE80211_WEP_NKID; i < common->keymax / 4; i++) {
|
||||
if (!test_bit(i, common->keymap) &&
|
||||
(test_bit(i + 32, common->keymap) ||
|
||||
test_bit(i + 64, common->keymap) ||
|
||||
test_bit(i + 64 + 32, common->keymap)))
|
||||
return i;
|
||||
if (!test_bit(i + 32, common->keymap) &&
|
||||
(test_bit(i, common->keymap) ||
|
||||
test_bit(i + 64, common->keymap) ||
|
||||
test_bit(i + 64 + 32, common->keymap)))
|
||||
return i + 32;
|
||||
if (!test_bit(i + 64, common->keymap) &&
|
||||
(test_bit(i , common->keymap) ||
|
||||
test_bit(i + 32, common->keymap) ||
|
||||
test_bit(i + 64 + 32, common->keymap)))
|
||||
return i + 64;
|
||||
if (!test_bit(i + 64 + 32, common->keymap) &&
|
||||
(test_bit(i, common->keymap) ||
|
||||
test_bit(i + 32, common->keymap) ||
|
||||
test_bit(i + 64, common->keymap)))
|
||||
return i + 64 + 32;
|
||||
}
|
||||
} else {
|
||||
for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) {
|
||||
if (!test_bit(i, common->keymap) &&
|
||||
test_bit(i + 64, common->keymap))
|
||||
return i;
|
||||
if (test_bit(i, common->keymap) &&
|
||||
!test_bit(i + 64, common->keymap))
|
||||
return i + 64;
|
||||
}
|
||||
}
|
||||
|
||||
/* No partially used TKIP slots, pick any available slot */
|
||||
for (i = IEEE80211_WEP_NKID; i < common->keymax; i++) {
|
||||
/* Do not allow slots that could be needed for TKIP group keys
|
||||
* to be used. This limitation could be removed if we know that
|
||||
* TKIP will not be used. */
|
||||
if (i >= 64 && i < 64 + IEEE80211_WEP_NKID)
|
||||
continue;
|
||||
if (common->splitmic) {
|
||||
if (i >= 32 && i < 32 + IEEE80211_WEP_NKID)
|
||||
continue;
|
||||
if (i >= 64 + 32 && i < 64 + 32 + IEEE80211_WEP_NKID)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!test_bit(i, common->keymap))
|
||||
return i; /* Found a free slot for a key */
|
||||
}
|
||||
|
||||
/* No free slot found */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure encryption in the HW.
|
||||
*/
|
||||
int ath9k_cmn_key_config(struct ath_common *common,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
struct ieee80211_key_conf *key)
|
||||
{
|
||||
struct ath_hw *ah = common->ah;
|
||||
struct ath9k_keyval hk;
|
||||
const u8 *mac = NULL;
|
||||
int ret = 0;
|
||||
int idx;
|
||||
|
||||
memset(&hk, 0, sizeof(hk));
|
||||
|
||||
switch (key->alg) {
|
||||
case ALG_WEP:
|
||||
hk.kv_type = ATH9K_CIPHER_WEP;
|
||||
break;
|
||||
case ALG_TKIP:
|
||||
hk.kv_type = ATH9K_CIPHER_TKIP;
|
||||
break;
|
||||
case ALG_CCMP:
|
||||
hk.kv_type = ATH9K_CIPHER_AES_CCM;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
hk.kv_len = key->keylen;
|
||||
memcpy(hk.kv_val, key->key, key->keylen);
|
||||
|
||||
if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
|
||||
/* For now, use the default keys for broadcast keys. This may
|
||||
* need to change with virtual interfaces. */
|
||||
idx = key->keyidx;
|
||||
} else if (key->keyidx) {
|
||||
if (WARN_ON(!sta))
|
||||
return -EOPNOTSUPP;
|
||||
mac = sta->addr;
|
||||
|
||||
if (vif->type != NL80211_IFTYPE_AP) {
|
||||
/* Only keyidx 0 should be used with unicast key, but
|
||||
* allow this for client mode for now. */
|
||||
idx = key->keyidx;
|
||||
} else
|
||||
return -EIO;
|
||||
} else {
|
||||
if (WARN_ON(!sta))
|
||||
return -EOPNOTSUPP;
|
||||
mac = sta->addr;
|
||||
|
||||
if (key->alg == ALG_TKIP)
|
||||
idx = ath_reserve_key_cache_slot_tkip(common);
|
||||
else
|
||||
idx = ath_reserve_key_cache_slot(common);
|
||||
if (idx < 0)
|
||||
return -ENOSPC; /* no free key cache entries */
|
||||
}
|
||||
|
||||
if (key->alg == ALG_TKIP)
|
||||
ret = ath_setkey_tkip(common, idx, key->key, &hk, mac,
|
||||
vif->type == NL80211_IFTYPE_AP);
|
||||
else
|
||||
ret = ath9k_hw_set_keycache_entry(ah, idx, &hk, mac);
|
||||
|
||||
if (!ret)
|
||||
return -EIO;
|
||||
|
||||
set_bit(idx, common->keymap);
|
||||
if (key->alg == ALG_TKIP) {
|
||||
set_bit(idx + 64, common->keymap);
|
||||
if (common->splitmic) {
|
||||
set_bit(idx + 32, common->keymap);
|
||||
set_bit(idx + 64 + 32, common->keymap);
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_key_config);
|
||||
|
||||
/*
|
||||
* Delete Key.
|
||||
*/
|
||||
void ath9k_cmn_key_delete(struct ath_common *common,
|
||||
struct ieee80211_key_conf *key)
|
||||
{
|
||||
struct ath_hw *ah = common->ah;
|
||||
|
||||
ath9k_hw_keyreset(ah, key->hw_key_idx);
|
||||
if (key->hw_key_idx < IEEE80211_WEP_NKID)
|
||||
return;
|
||||
|
||||
clear_bit(key->hw_key_idx, common->keymap);
|
||||
if (key->alg != ALG_TKIP)
|
||||
return;
|
||||
|
||||
clear_bit(key->hw_key_idx + 64, common->keymap);
|
||||
if (common->splitmic) {
|
||||
ath9k_hw_keyreset(ah, key->hw_key_idx + 32);
|
||||
clear_bit(key->hw_key_idx + 32, common->keymap);
|
||||
clear_bit(key->hw_key_idx + 64 + 32, common->keymap);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(ath9k_cmn_key_delete);
|
||||
|
||||
static int __init ath9k_cmn_init(void)
|
||||
{
|
||||
return 0;
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
/* Common header for Atheros 802.11n base driver cores */
|
||||
|
||||
#define IEEE80211_WEP_NKID 4
|
||||
|
||||
#define WME_NUM_TID 16
|
||||
#define WME_BA_BMP_SIZE 64
|
||||
#define WME_MAX_BA WME_BA_BMP_SIZE
|
||||
@ -125,3 +127,18 @@ void ath9k_cmn_rx_skb_postprocess(struct ath_common *common,
|
||||
bool decrypt_error);
|
||||
|
||||
int ath9k_cmn_padpos(__le16 frame_control);
|
||||
int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb);
|
||||
u32 ath9k_cmn_calcrxfilter(struct ieee80211_hw *hw, struct ath_hw *ah,
|
||||
unsigned int rxfilter);
|
||||
void ath9k_cmn_opmode_init(struct ieee80211_hw *hw, struct ath_hw *ah,
|
||||
unsigned int rxfilter);
|
||||
void ath9k_cmn_update_ichannel(struct ieee80211_hw *hw,
|
||||
struct ath9k_channel *ichan);
|
||||
struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw,
|
||||
struct ath_hw *ah);
|
||||
int ath9k_cmn_key_config(struct ath_common *common,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
struct ieee80211_key_conf *key);
|
||||
void ath9k_cmn_key_delete(struct ath_common *common,
|
||||
struct ieee80211_key_conf *key);
|
||||
|
993
drivers/net/wireless/ath/ath9k/hif_usb.c
Normal file
993
drivers/net/wireless/ath/ath9k/hif_usb.c
Normal file
@ -0,0 +1,993 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
#define ATH9K_FW_USB_DEV(devid, fw) \
|
||||
{ USB_DEVICE(0x0cf3, devid), .driver_info = (unsigned long) fw }
|
||||
|
||||
static struct usb_device_id ath9k_hif_usb_ids[] = {
|
||||
ATH9K_FW_USB_DEV(0x9271, "ar9271.fw"),
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, ath9k_hif_usb_ids);
|
||||
|
||||
static int __hif_usb_tx(struct hif_device_usb *hif_dev);
|
||||
|
||||
static void hif_usb_regout_cb(struct urb *urb)
|
||||
{
|
||||
struct cmd_buf *cmd = (struct cmd_buf *)urb->context;
|
||||
struct hif_device_usb *hif_dev = cmd->hif_dev;
|
||||
|
||||
if (!hif_dev) {
|
||||
usb_free_urb(urb);
|
||||
if (cmd) {
|
||||
if (cmd->skb)
|
||||
dev_kfree_skb_any(cmd->skb);
|
||||
kfree(cmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ECONNRESET:
|
||||
break;
|
||||
case -ENODEV:
|
||||
case -ESHUTDOWN:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle,
|
||||
cmd->skb, 1);
|
||||
kfree(cmd);
|
||||
usb_free_urb(urb);
|
||||
}
|
||||
}
|
||||
|
||||
static int hif_usb_send_regout(struct hif_device_usb *hif_dev,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct urb *urb;
|
||||
struct cmd_buf *cmd;
|
||||
int ret = 0;
|
||||
|
||||
urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (urb == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (cmd == NULL) {
|
||||
usb_free_urb(urb);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
cmd->skb = skb;
|
||||
cmd->hif_dev = hif_dev;
|
||||
|
||||
usb_fill_int_urb(urb, hif_dev->udev,
|
||||
usb_sndintpipe(hif_dev->udev, USB_REG_OUT_PIPE),
|
||||
skb->data, skb->len,
|
||||
hif_usb_regout_cb, cmd, 1);
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_KERNEL);
|
||||
if (ret) {
|
||||
usb_free_urb(urb);
|
||||
kfree(cmd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hif_usb_tx_cb(struct urb *urb)
|
||||
{
|
||||
struct tx_buf *tx_buf = (struct tx_buf *) urb->context;
|
||||
struct hif_device_usb *hif_dev = tx_buf->hif_dev;
|
||||
struct sk_buff *skb;
|
||||
bool drop, flush;
|
||||
|
||||
if (!hif_dev)
|
||||
return;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ECONNRESET:
|
||||
break;
|
||||
case -ENODEV:
|
||||
case -ESHUTDOWN:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (tx_buf) {
|
||||
spin_lock(&hif_dev->tx.tx_lock);
|
||||
drop = !!(hif_dev->tx.flags & HIF_USB_TX_STOP);
|
||||
flush = !!(hif_dev->tx.flags & HIF_USB_TX_FLUSH);
|
||||
spin_unlock(&hif_dev->tx.tx_lock);
|
||||
|
||||
while ((skb = __skb_dequeue(&tx_buf->skb_queue)) != NULL) {
|
||||
if (!drop && !flush) {
|
||||
ath9k_htc_txcompletion_cb(hif_dev->htc_handle,
|
||||
skb, 1);
|
||||
TX_STAT_INC(skb_completed);
|
||||
} else {
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
}
|
||||
|
||||
if (flush)
|
||||
return;
|
||||
|
||||
tx_buf->len = tx_buf->offset = 0;
|
||||
__skb_queue_head_init(&tx_buf->skb_queue);
|
||||
|
||||
spin_lock(&hif_dev->tx.tx_lock);
|
||||
list_del(&tx_buf->list);
|
||||
list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
||||
hif_dev->tx.tx_buf_cnt++;
|
||||
if (!drop)
|
||||
__hif_usb_tx(hif_dev); /* Check for pending SKBs */
|
||||
TX_STAT_INC(buf_completed);
|
||||
spin_unlock(&hif_dev->tx.tx_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* TX lock has to be taken */
|
||||
static int __hif_usb_tx(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
struct tx_buf *tx_buf = NULL;
|
||||
struct sk_buff *nskb = NULL;
|
||||
int ret = 0, i;
|
||||
u16 *hdr, tx_skb_cnt = 0;
|
||||
u8 *buf;
|
||||
|
||||
if (hif_dev->tx.tx_skb_cnt == 0)
|
||||
return 0;
|
||||
|
||||
/* Check if a free TX buffer is available */
|
||||
if (list_empty(&hif_dev->tx.tx_buf))
|
||||
return 0;
|
||||
|
||||
tx_buf = list_first_entry(&hif_dev->tx.tx_buf, struct tx_buf, list);
|
||||
list_del(&tx_buf->list);
|
||||
list_add_tail(&tx_buf->list, &hif_dev->tx.tx_pending);
|
||||
hif_dev->tx.tx_buf_cnt--;
|
||||
|
||||
tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM);
|
||||
|
||||
for (i = 0; i < tx_skb_cnt; i++) {
|
||||
nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue);
|
||||
|
||||
/* Should never be NULL */
|
||||
BUG_ON(!nskb);
|
||||
|
||||
hif_dev->tx.tx_skb_cnt--;
|
||||
|
||||
buf = tx_buf->buf;
|
||||
buf += tx_buf->offset;
|
||||
hdr = (u16 *)buf;
|
||||
*hdr++ = nskb->len;
|
||||
*hdr++ = ATH_USB_TX_STREAM_MODE_TAG;
|
||||
buf += 4;
|
||||
memcpy(buf, nskb->data, nskb->len);
|
||||
tx_buf->len = nskb->len + 4;
|
||||
|
||||
if (i < (tx_skb_cnt - 1))
|
||||
tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4;
|
||||
|
||||
if (i == (tx_skb_cnt - 1))
|
||||
tx_buf->len += tx_buf->offset;
|
||||
|
||||
__skb_queue_tail(&tx_buf->skb_queue, nskb);
|
||||
TX_STAT_INC(skb_queued);
|
||||
}
|
||||
|
||||
usb_fill_bulk_urb(tx_buf->urb, hif_dev->udev,
|
||||
usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE),
|
||||
tx_buf->buf, tx_buf->len,
|
||||
hif_usb_tx_cb, tx_buf);
|
||||
|
||||
ret = usb_submit_urb(tx_buf->urb, GFP_ATOMIC);
|
||||
if (ret) {
|
||||
tx_buf->len = tx_buf->offset = 0;
|
||||
__skb_queue_purge(&tx_buf->skb_queue);
|
||||
__skb_queue_head_init(&tx_buf->skb_queue);
|
||||
list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
||||
hif_dev->tx.tx_buf_cnt++;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
TX_STAT_INC(buf_queued);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hif_usb_send_tx(struct hif_device_usb *hif_dev, struct sk_buff *skb,
|
||||
struct ath9k_htc_tx_ctl *tx_ctl)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
||||
|
||||
if (hif_dev->tx.flags & HIF_USB_TX_STOP) {
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Check if the max queue count has been reached */
|
||||
if (hif_dev->tx.tx_skb_cnt > MAX_TX_BUF_NUM) {
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
__skb_queue_tail(&hif_dev->tx.tx_skb_queue, skb);
|
||||
hif_dev->tx.tx_skb_cnt++;
|
||||
|
||||
/* Send normal frames immediately */
|
||||
if (!tx_ctl || (tx_ctl && (tx_ctl->type == ATH9K_HTC_NORMAL)))
|
||||
__hif_usb_tx(hif_dev);
|
||||
|
||||
/* Check if AMPDUs have to be sent immediately */
|
||||
if (tx_ctl && (tx_ctl->type == ATH9K_HTC_AMPDU) &&
|
||||
(hif_dev->tx.tx_buf_cnt == MAX_TX_URB_NUM) &&
|
||||
(hif_dev->tx.tx_skb_cnt < 2)) {
|
||||
__hif_usb_tx(hif_dev);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hif_usb_start(void *hif_handle, u8 pipe_id)
|
||||
{
|
||||
struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
|
||||
unsigned long flags;
|
||||
|
||||
hif_dev->flags |= HIF_USB_START;
|
||||
|
||||
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
||||
hif_dev->tx.flags &= ~HIF_USB_TX_STOP;
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
}
|
||||
|
||||
static void hif_usb_stop(void *hif_handle, u8 pipe_id)
|
||||
{
|
||||
struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
||||
__skb_queue_purge(&hif_dev->tx.tx_skb_queue);
|
||||
hif_dev->tx.tx_skb_cnt = 0;
|
||||
hif_dev->tx.flags |= HIF_USB_TX_STOP;
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
}
|
||||
|
||||
static int hif_usb_send(void *hif_handle, u8 pipe_id, struct sk_buff *skb,
|
||||
struct ath9k_htc_tx_ctl *tx_ctl)
|
||||
{
|
||||
struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle;
|
||||
int ret = 0;
|
||||
|
||||
switch (pipe_id) {
|
||||
case USB_WLAN_TX_PIPE:
|
||||
ret = hif_usb_send_tx(hif_dev, skb, tx_ctl);
|
||||
break;
|
||||
case USB_REG_OUT_PIPE:
|
||||
ret = hif_usb_send_regout(hif_dev, skb);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct ath9k_htc_hif hif_usb = {
|
||||
.transport = ATH9K_HIF_USB,
|
||||
.name = "ath9k_hif_usb",
|
||||
|
||||
.control_ul_pipe = USB_REG_OUT_PIPE,
|
||||
.control_dl_pipe = USB_REG_IN_PIPE,
|
||||
|
||||
.start = hif_usb_start,
|
||||
.stop = hif_usb_stop,
|
||||
.send = hif_usb_send,
|
||||
};
|
||||
|
||||
static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct sk_buff *nskb, *skb_pool[8];
|
||||
int index = 0, i = 0, chk_idx, len = skb->len;
|
||||
int rx_remain_len = 0, rx_pkt_len = 0;
|
||||
u16 pkt_len, pkt_tag, pool_index = 0;
|
||||
u8 *ptr;
|
||||
|
||||
rx_remain_len = hif_dev->rx_remain_len;
|
||||
rx_pkt_len = hif_dev->rx_transfer_len;
|
||||
|
||||
if (rx_remain_len != 0) {
|
||||
struct sk_buff *remain_skb = hif_dev->remain_skb;
|
||||
|
||||
if (remain_skb) {
|
||||
ptr = (u8 *) remain_skb->data;
|
||||
|
||||
index = rx_remain_len;
|
||||
rx_remain_len -= hif_dev->rx_pad_len;
|
||||
ptr += rx_pkt_len;
|
||||
|
||||
memcpy(ptr, skb->data, rx_remain_len);
|
||||
|
||||
rx_pkt_len += rx_remain_len;
|
||||
hif_dev->rx_remain_len = 0;
|
||||
skb_put(remain_skb, rx_pkt_len);
|
||||
|
||||
skb_pool[pool_index++] = remain_skb;
|
||||
|
||||
} else {
|
||||
index = rx_remain_len;
|
||||
}
|
||||
}
|
||||
|
||||
while (index < len) {
|
||||
ptr = (u8 *) skb->data;
|
||||
|
||||
pkt_len = ptr[index] + (ptr[index+1] << 8);
|
||||
pkt_tag = ptr[index+2] + (ptr[index+3] << 8);
|
||||
|
||||
if (pkt_tag == ATH_USB_RX_STREAM_MODE_TAG) {
|
||||
u16 pad_len;
|
||||
|
||||
pad_len = 4 - (pkt_len & 0x3);
|
||||
if (pad_len == 4)
|
||||
pad_len = 0;
|
||||
|
||||
chk_idx = index;
|
||||
index = index + 4 + pkt_len + pad_len;
|
||||
|
||||
if (index > MAX_RX_BUF_SIZE) {
|
||||
hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE;
|
||||
hif_dev->rx_transfer_len =
|
||||
MAX_RX_BUF_SIZE - chk_idx - 4;
|
||||
hif_dev->rx_pad_len = pad_len;
|
||||
|
||||
nskb = __dev_alloc_skb(pkt_len + 32,
|
||||
GFP_ATOMIC);
|
||||
if (!nskb) {
|
||||
dev_err(&hif_dev->udev->dev,
|
||||
"ath9k_htc: RX memory allocation"
|
||||
" error\n");
|
||||
goto err;
|
||||
}
|
||||
skb_reserve(nskb, 32);
|
||||
RX_STAT_INC(skb_allocated);
|
||||
|
||||
memcpy(nskb->data, &(skb->data[chk_idx+4]),
|
||||
hif_dev->rx_transfer_len);
|
||||
|
||||
/* Record the buffer pointer */
|
||||
hif_dev->remain_skb = nskb;
|
||||
} else {
|
||||
nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC);
|
||||
if (!nskb) {
|
||||
dev_err(&hif_dev->udev->dev,
|
||||
"ath9k_htc: RX memory allocation"
|
||||
" error\n");
|
||||
goto err;
|
||||
}
|
||||
skb_reserve(nskb, 32);
|
||||
RX_STAT_INC(skb_allocated);
|
||||
|
||||
memcpy(nskb->data, &(skb->data[chk_idx+4]), pkt_len);
|
||||
skb_put(nskb, pkt_len);
|
||||
skb_pool[pool_index++] = nskb;
|
||||
}
|
||||
} else {
|
||||
RX_STAT_INC(skb_dropped);
|
||||
dev_kfree_skb_any(skb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
dev_kfree_skb_any(skb);
|
||||
|
||||
for (i = 0; i < pool_index; i++) {
|
||||
ath9k_htc_rx_msg(hif_dev->htc_handle, skb_pool[i],
|
||||
skb_pool[i]->len, USB_WLAN_RX_PIPE);
|
||||
RX_STAT_INC(skb_completed);
|
||||
}
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_rx_cb(struct urb *urb)
|
||||
{
|
||||
struct sk_buff *skb = (struct sk_buff *) urb->context;
|
||||
struct sk_buff *nskb;
|
||||
struct hif_device_usb *hif_dev = (struct hif_device_usb *)
|
||||
usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
|
||||
int ret;
|
||||
|
||||
if (!hif_dev)
|
||||
goto free;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ECONNRESET:
|
||||
case -ENODEV:
|
||||
case -ESHUTDOWN:
|
||||
goto free;
|
||||
default:
|
||||
goto resubmit;
|
||||
}
|
||||
|
||||
if (likely(urb->actual_length != 0)) {
|
||||
skb_put(skb, urb->actual_length);
|
||||
|
||||
nskb = __dev_alloc_skb(MAX_RX_BUF_SIZE, GFP_ATOMIC);
|
||||
if (!nskb)
|
||||
goto resubmit;
|
||||
|
||||
usb_fill_bulk_urb(urb, hif_dev->udev,
|
||||
usb_rcvbulkpipe(hif_dev->udev,
|
||||
USB_WLAN_RX_PIPE),
|
||||
nskb->data, MAX_RX_BUF_SIZE,
|
||||
ath9k_hif_usb_rx_cb, nskb);
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret) {
|
||||
dev_kfree_skb_any(nskb);
|
||||
goto free;
|
||||
}
|
||||
|
||||
ath9k_hif_usb_rx_stream(hif_dev, skb);
|
||||
return;
|
||||
}
|
||||
|
||||
resubmit:
|
||||
skb_reset_tail_pointer(skb);
|
||||
skb_trim(skb, 0);
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
return;
|
||||
free:
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
|
||||
{
|
||||
struct sk_buff *skb = (struct sk_buff *) urb->context;
|
||||
struct sk_buff *nskb;
|
||||
struct hif_device_usb *hif_dev = (struct hif_device_usb *)
|
||||
usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0));
|
||||
int ret;
|
||||
|
||||
if (!hif_dev)
|
||||
goto free;
|
||||
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ECONNRESET:
|
||||
case -ENODEV:
|
||||
case -ESHUTDOWN:
|
||||
goto free;
|
||||
default:
|
||||
goto resubmit;
|
||||
}
|
||||
|
||||
if (likely(urb->actual_length != 0)) {
|
||||
skb_put(skb, urb->actual_length);
|
||||
|
||||
nskb = __dev_alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_ATOMIC);
|
||||
if (!nskb)
|
||||
goto resubmit;
|
||||
|
||||
usb_fill_int_urb(urb, hif_dev->udev,
|
||||
usb_rcvintpipe(hif_dev->udev, USB_REG_IN_PIPE),
|
||||
nskb->data, MAX_REG_IN_BUF_SIZE,
|
||||
ath9k_hif_usb_reg_in_cb, nskb, 1);
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret) {
|
||||
dev_kfree_skb_any(nskb);
|
||||
goto free;
|
||||
}
|
||||
|
||||
ath9k_htc_rx_msg(hif_dev->htc_handle, skb,
|
||||
skb->len, USB_REG_IN_PIPE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
resubmit:
|
||||
skb_reset_tail_pointer(skb);
|
||||
skb_trim(skb, 0);
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
return;
|
||||
free:
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dealloc_tx_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL;
|
||||
|
||||
list_for_each_entry_safe(tx_buf, tx_buf_tmp, &hif_dev->tx.tx_buf, list) {
|
||||
list_del(&tx_buf->list);
|
||||
usb_free_urb(tx_buf->urb);
|
||||
kfree(tx_buf->buf);
|
||||
kfree(tx_buf);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
||||
hif_dev->tx.flags |= HIF_USB_TX_FLUSH;
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
|
||||
list_for_each_entry_safe(tx_buf, tx_buf_tmp,
|
||||
&hif_dev->tx.tx_pending, list) {
|
||||
usb_kill_urb(tx_buf->urb);
|
||||
list_del(&tx_buf->list);
|
||||
usb_free_urb(tx_buf->urb);
|
||||
kfree(tx_buf->buf);
|
||||
kfree(tx_buf);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&hif_dev->tx.tx_lock, flags);
|
||||
hif_dev->tx.flags &= ~HIF_USB_TX_FLUSH;
|
||||
spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags);
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_alloc_tx_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
struct tx_buf *tx_buf;
|
||||
int i;
|
||||
|
||||
INIT_LIST_HEAD(&hif_dev->tx.tx_buf);
|
||||
INIT_LIST_HEAD(&hif_dev->tx.tx_pending);
|
||||
spin_lock_init(&hif_dev->tx.tx_lock);
|
||||
__skb_queue_head_init(&hif_dev->tx.tx_skb_queue);
|
||||
|
||||
for (i = 0; i < MAX_TX_URB_NUM; i++) {
|
||||
tx_buf = kzalloc(sizeof(struct tx_buf), GFP_KERNEL);
|
||||
if (!tx_buf)
|
||||
goto err;
|
||||
|
||||
tx_buf->buf = kzalloc(MAX_TX_BUF_SIZE, GFP_KERNEL);
|
||||
if (!tx_buf->buf)
|
||||
goto err;
|
||||
|
||||
tx_buf->urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!tx_buf->urb)
|
||||
goto err;
|
||||
|
||||
tx_buf->hif_dev = hif_dev;
|
||||
__skb_queue_head_init(&tx_buf->skb_queue);
|
||||
|
||||
list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf);
|
||||
}
|
||||
|
||||
hif_dev->tx.tx_buf_cnt = MAX_TX_URB_NUM;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dealloc_rx_skbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_RX_URB_NUM; i++) {
|
||||
if (hif_dev->wlan_rx_data_urb[i]) {
|
||||
if (hif_dev->wlan_rx_data_urb[i]->transfer_buffer)
|
||||
dev_kfree_skb_any((void *)
|
||||
hif_dev->wlan_rx_data_urb[i]->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_RX_URB_NUM; i++) {
|
||||
if (hif_dev->wlan_rx_data_urb[i]) {
|
||||
usb_kill_urb(hif_dev->wlan_rx_data_urb[i]);
|
||||
usb_free_urb(hif_dev->wlan_rx_data_urb[i]);
|
||||
hif_dev->wlan_rx_data_urb[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_prep_rx_urb(struct hif_device_usb *hif_dev,
|
||||
struct urb *urb)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = __dev_alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
usb_fill_bulk_urb(urb, hif_dev->udev,
|
||||
usb_rcvbulkpipe(hif_dev->udev, USB_WLAN_RX_PIPE),
|
||||
skb->data, MAX_RX_BUF_SIZE,
|
||||
ath9k_hif_usb_rx_cb, skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < MAX_RX_URB_NUM; i++) {
|
||||
|
||||
/* Allocate URB */
|
||||
hif_dev->wlan_rx_data_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (hif_dev->wlan_rx_data_urb[i] == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_rx_urb;
|
||||
}
|
||||
|
||||
/* Allocate buffer */
|
||||
ret = ath9k_hif_usb_prep_rx_urb(hif_dev,
|
||||
hif_dev->wlan_rx_data_urb[i]);
|
||||
if (ret)
|
||||
goto err_rx_urb;
|
||||
|
||||
/* Submit URB */
|
||||
ret = usb_submit_urb(hif_dev->wlan_rx_data_urb[i], GFP_KERNEL);
|
||||
if (ret)
|
||||
goto err_rx_urb;
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_rx_urb:
|
||||
ath9k_hif_usb_dealloc_rx_skbs(hif_dev);
|
||||
ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dealloc_reg_in_urb(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
if (hif_dev->reg_in_urb) {
|
||||
usb_kill_urb(hif_dev->reg_in_urb);
|
||||
usb_free_urb(hif_dev->reg_in_urb);
|
||||
hif_dev->reg_in_urb = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_alloc_reg_in_urb(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
hif_dev->reg_in_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (hif_dev->reg_in_urb == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
skb = __dev_alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);
|
||||
if (!skb)
|
||||
goto err;
|
||||
|
||||
usb_fill_int_urb(hif_dev->reg_in_urb, hif_dev->udev,
|
||||
usb_rcvintpipe(hif_dev->udev, USB_REG_IN_PIPE),
|
||||
skb->data, MAX_REG_IN_BUF_SIZE,
|
||||
ath9k_hif_usb_reg_in_cb, skb, 1);
|
||||
|
||||
if (usb_submit_urb(hif_dev->reg_in_urb, GFP_KERNEL) != 0)
|
||||
goto err_skb;
|
||||
|
||||
return 0;
|
||||
|
||||
err_skb:
|
||||
dev_kfree_skb_any(skb);
|
||||
err:
|
||||
ath9k_hif_usb_dealloc_reg_in_urb(hif_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
/* TX */
|
||||
if (ath9k_hif_usb_alloc_tx_urbs(hif_dev) < 0)
|
||||
goto err;
|
||||
|
||||
/* RX */
|
||||
if (ath9k_hif_usb_alloc_rx_urbs(hif_dev) < 0)
|
||||
goto err;
|
||||
|
||||
/* Register Read/Write */
|
||||
if (ath9k_hif_usb_alloc_reg_in_urb(hif_dev) < 0)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
int transfer, err;
|
||||
const void *data = hif_dev->firmware->data;
|
||||
size_t len = hif_dev->firmware->size;
|
||||
u32 addr = AR9271_FIRMWARE;
|
||||
u8 *buf = kzalloc(4096, GFP_KERNEL);
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
while (len) {
|
||||
transfer = min_t(int, len, 4096);
|
||||
memcpy(buf, data, transfer);
|
||||
|
||||
err = usb_control_msg(hif_dev->udev,
|
||||
usb_sndctrlpipe(hif_dev->udev, 0),
|
||||
FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT,
|
||||
addr >> 8, 0, buf, transfer, HZ);
|
||||
if (err < 0) {
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
len -= transfer;
|
||||
data += transfer;
|
||||
addr += transfer;
|
||||
}
|
||||
kfree(buf);
|
||||
|
||||
/*
|
||||
* Issue FW download complete command to firmware.
|
||||
*/
|
||||
err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0),
|
||||
FIRMWARE_DOWNLOAD_COMP,
|
||||
0x40 | USB_DIR_OUT,
|
||||
AR9271_FIRMWARE_TEXT >> 8, 0, NULL, 0, HZ);
|
||||
if (err)
|
||||
return -EIO;
|
||||
|
||||
dev_info(&hif_dev->udev->dev, "ath9k_htc: Transferred FW: %s, size: %ld\n",
|
||||
"ar9271.fw", (unsigned long) hif_dev->firmware->size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev,
|
||||
const char *fw_name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Request firmware */
|
||||
ret = request_firmware(&hif_dev->firmware, fw_name, &hif_dev->udev->dev);
|
||||
if (ret) {
|
||||
dev_err(&hif_dev->udev->dev,
|
||||
"ath9k_htc: Firmware - %s not found\n", fw_name);
|
||||
goto err_fw_req;
|
||||
}
|
||||
|
||||
/* Download firmware */
|
||||
ret = ath9k_hif_usb_download_fw(hif_dev);
|
||||
if (ret) {
|
||||
dev_err(&hif_dev->udev->dev,
|
||||
"ath9k_htc: Firmware - %s download failed\n", fw_name);
|
||||
goto err_fw_download;
|
||||
}
|
||||
|
||||
/* Alloc URBs */
|
||||
ret = ath9k_hif_usb_alloc_urbs(hif_dev);
|
||||
if (ret) {
|
||||
dev_err(&hif_dev->udev->dev,
|
||||
"ath9k_htc: Unable to allocate URBs\n");
|
||||
goto err_urb;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_urb:
|
||||
/* Nothing */
|
||||
err_fw_download:
|
||||
release_firmware(hif_dev->firmware);
|
||||
err_fw_req:
|
||||
hif_dev->firmware = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dealloc_urbs(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
ath9k_hif_usb_dealloc_reg_in_urb(hif_dev);
|
||||
ath9k_hif_usb_dealloc_tx_urbs(hif_dev);
|
||||
ath9k_hif_usb_dealloc_rx_urbs(hif_dev);
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_dev_deinit(struct hif_device_usb *hif_dev)
|
||||
{
|
||||
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
||||
if (hif_dev->firmware)
|
||||
release_firmware(hif_dev->firmware);
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_probe(struct usb_interface *interface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_device *udev = interface_to_usbdev(interface);
|
||||
struct hif_device_usb *hif_dev;
|
||||
const char *fw_name = (const char *) id->driver_info;
|
||||
int ret = 0;
|
||||
|
||||
hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL);
|
||||
if (!hif_dev) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
usb_get_dev(udev);
|
||||
hif_dev->udev = udev;
|
||||
hif_dev->interface = interface;
|
||||
hif_dev->device_id = id->idProduct;
|
||||
#ifdef CONFIG_PM
|
||||
udev->reset_resume = 1;
|
||||
#endif
|
||||
usb_set_intfdata(interface, hif_dev);
|
||||
|
||||
ret = ath9k_hif_usb_dev_init(hif_dev, fw_name);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_hif_init_usb;
|
||||
}
|
||||
|
||||
hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev);
|
||||
if (hif_dev->htc_handle == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_htc_hw_alloc;
|
||||
}
|
||||
|
||||
ret = ath9k_htc_hw_init(&hif_usb, hif_dev->htc_handle, hif_dev,
|
||||
&hif_dev->udev->dev, hif_dev->device_id,
|
||||
ATH9K_HIF_USB);
|
||||
if (ret) {
|
||||
ret = -EINVAL;
|
||||
goto err_htc_hw_init;
|
||||
}
|
||||
|
||||
dev_info(&hif_dev->udev->dev, "ath9k_htc: USB layer initialized\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_htc_hw_init:
|
||||
ath9k_htc_hw_free(hif_dev->htc_handle);
|
||||
err_htc_hw_alloc:
|
||||
ath9k_hif_usb_dev_deinit(hif_dev);
|
||||
err_hif_init_usb:
|
||||
usb_set_intfdata(interface, NULL);
|
||||
kfree(hif_dev);
|
||||
usb_put_dev(udev);
|
||||
err_alloc:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ath9k_hif_usb_disconnect(struct usb_interface *interface)
|
||||
{
|
||||
struct usb_device *udev = interface_to_usbdev(interface);
|
||||
struct hif_device_usb *hif_dev =
|
||||
(struct hif_device_usb *) usb_get_intfdata(interface);
|
||||
|
||||
if (hif_dev) {
|
||||
ath9k_htc_hw_deinit(hif_dev->htc_handle, true);
|
||||
ath9k_htc_hw_free(hif_dev->htc_handle);
|
||||
ath9k_hif_usb_dev_deinit(hif_dev);
|
||||
usb_set_intfdata(interface, NULL);
|
||||
}
|
||||
|
||||
if (hif_dev->flags & HIF_USB_START)
|
||||
usb_reset_device(udev);
|
||||
|
||||
kfree(hif_dev);
|
||||
dev_info(&udev->dev, "ath9k_htc: USB layer deinitialized\n");
|
||||
usb_put_dev(udev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ath9k_hif_usb_suspend(struct usb_interface *interface,
|
||||
pm_message_t message)
|
||||
{
|
||||
struct hif_device_usb *hif_dev =
|
||||
(struct hif_device_usb *) usb_get_intfdata(interface);
|
||||
|
||||
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ath9k_hif_usb_resume(struct usb_interface *interface)
|
||||
{
|
||||
struct hif_device_usb *hif_dev =
|
||||
(struct hif_device_usb *) usb_get_intfdata(interface);
|
||||
int ret;
|
||||
|
||||
ret = ath9k_hif_usb_alloc_urbs(hif_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hif_dev->firmware) {
|
||||
ret = ath9k_hif_usb_download_fw(hif_dev);
|
||||
if (ret)
|
||||
goto fail_resume;
|
||||
} else {
|
||||
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mdelay(100);
|
||||
|
||||
ret = ath9k_htc_resume(hif_dev->htc_handle);
|
||||
|
||||
if (ret)
|
||||
goto fail_resume;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_resume:
|
||||
ath9k_hif_usb_dealloc_urbs(hif_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct usb_driver ath9k_hif_usb_driver = {
|
||||
.name = "ath9k_hif_usb",
|
||||
.probe = ath9k_hif_usb_probe,
|
||||
.disconnect = ath9k_hif_usb_disconnect,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = ath9k_hif_usb_suspend,
|
||||
.resume = ath9k_hif_usb_resume,
|
||||
.reset_resume = ath9k_hif_usb_resume,
|
||||
#endif
|
||||
.id_table = ath9k_hif_usb_ids,
|
||||
.soft_unbind = 1,
|
||||
};
|
||||
|
||||
int ath9k_hif_usb_init(void)
|
||||
{
|
||||
return usb_register(&ath9k_hif_usb_driver);
|
||||
}
|
||||
|
||||
void ath9k_hif_usb_exit(void)
|
||||
{
|
||||
usb_deregister(&ath9k_hif_usb_driver);
|
||||
}
|
105
drivers/net/wireless/ath/ath9k/hif_usb.h
Normal file
105
drivers/net/wireless/ath/ath9k/hif_usb.h
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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_USB_H
|
||||
#define HTC_USB_H
|
||||
|
||||
#define AR9271_FIRMWARE 0x501000
|
||||
#define AR9271_FIRMWARE_TEXT 0x903000
|
||||
|
||||
#define FIRMWARE_DOWNLOAD 0x30
|
||||
#define FIRMWARE_DOWNLOAD_COMP 0x31
|
||||
|
||||
#define ATH_USB_RX_STREAM_MODE_TAG 0x4e00
|
||||
#define ATH_USB_TX_STREAM_MODE_TAG 0x697e
|
||||
|
||||
/* FIXME: Verify these numbers (with Windows) */
|
||||
#define MAX_TX_URB_NUM 8
|
||||
#define MAX_TX_BUF_NUM 1024
|
||||
#define MAX_TX_BUF_SIZE 32768
|
||||
#define MAX_TX_AGGR_NUM 20
|
||||
|
||||
#define MAX_RX_URB_NUM 8
|
||||
#define MAX_RX_BUF_SIZE 16384
|
||||
|
||||
#define MAX_REG_OUT_URB_NUM 1
|
||||
#define MAX_REG_OUT_BUF_NUM 8
|
||||
|
||||
#define MAX_REG_IN_BUF_SIZE 64
|
||||
|
||||
/* USB Endpoint definition */
|
||||
#define USB_WLAN_TX_PIPE 1
|
||||
#define USB_WLAN_RX_PIPE 2
|
||||
#define USB_REG_IN_PIPE 3
|
||||
#define USB_REG_OUT_PIPE 4
|
||||
|
||||
#define HIF_USB_MAX_RXPIPES 2
|
||||
#define HIF_USB_MAX_TXPIPES 4
|
||||
|
||||
struct tx_buf {
|
||||
u8 *buf;
|
||||
u16 len;
|
||||
u16 offset;
|
||||
struct urb *urb;
|
||||
struct sk_buff_head skb_queue;
|
||||
struct hif_device_usb *hif_dev;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
#define HIF_USB_TX_STOP BIT(0)
|
||||
#define HIF_USB_TX_FLUSH BIT(1)
|
||||
|
||||
struct hif_usb_tx {
|
||||
u8 flags;
|
||||
u8 tx_buf_cnt;
|
||||
u16 tx_skb_cnt;
|
||||
struct sk_buff_head tx_skb_queue;
|
||||
struct list_head tx_buf;
|
||||
struct list_head tx_pending;
|
||||
spinlock_t tx_lock;
|
||||
};
|
||||
|
||||
struct cmd_buf {
|
||||
struct sk_buff *skb;
|
||||
struct hif_device_usb *hif_dev;
|
||||
};
|
||||
|
||||
#define HIF_USB_START BIT(0)
|
||||
|
||||
struct hif_device_usb {
|
||||
u16 device_id;
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *interface;
|
||||
const struct firmware *firmware;
|
||||
struct htc_target *htc_handle;
|
||||
u8 flags;
|
||||
|
||||
struct hif_usb_tx tx;
|
||||
|
||||
struct urb *wlan_rx_data_urb[MAX_RX_URB_NUM];
|
||||
struct urb *reg_in_urb;
|
||||
|
||||
struct sk_buff *remain_skb;
|
||||
int rx_remain_len;
|
||||
int rx_pkt_len;
|
||||
int rx_transfer_len;
|
||||
int rx_pad_len;
|
||||
};
|
||||
|
||||
int ath9k_hif_usb_init(void);
|
||||
void ath9k_hif_usb_exit(void);
|
||||
|
||||
#endif /* HTC_USB_H */
|
441
drivers/net/wireless/ath/ath9k/htc.h
Normal file
441
drivers/net/wireless/ath/ath9k/htc.h
Normal file
@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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_H
|
||||
#define HTC_H
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/leds.h>
|
||||
#include <net/mac80211.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "htc_hst.h"
|
||||
#include "hif_usb.h"
|
||||
#include "wmi.h"
|
||||
|
||||
#define ATH_STA_SHORT_CALINTERVAL 1000 /* 1 second */
|
||||
#define ATH_ANI_POLLINTERVAL 100 /* 100 ms */
|
||||
#define ATH_LONG_CALINTERVAL 30000 /* 30 seconds */
|
||||
#define ATH_RESTART_CALINTERVAL 1200000 /* 20 minutes */
|
||||
|
||||
#define ATH_DEFAULT_BMISS_LIMIT 10
|
||||
#define IEEE80211_MS_TO_TU(x) (((x) * 1000) / 1024)
|
||||
#define TSF_TO_TU(_h, _l) \
|
||||
((((u32)(_h)) << 22) | (((u32)(_l)) >> 10))
|
||||
|
||||
extern struct ieee80211_ops ath9k_htc_ops;
|
||||
extern int modparam_nohwcrypt;
|
||||
|
||||
enum htc_phymode {
|
||||
HTC_MODE_AUTO = 0,
|
||||
HTC_MODE_11A = 1,
|
||||
HTC_MODE_11B = 2,
|
||||
HTC_MODE_11G = 3,
|
||||
HTC_MODE_FH = 4,
|
||||
HTC_MODE_TURBO_A = 5,
|
||||
HTC_MODE_TURBO_G = 6,
|
||||
HTC_MODE_11NA = 7,
|
||||
HTC_MODE_11NG = 8
|
||||
};
|
||||
|
||||
enum htc_opmode {
|
||||
HTC_M_STA = 1,
|
||||
HTC_M_IBSS = 0,
|
||||
HTC_M_AHDEMO = 3,
|
||||
HTC_M_HOSTAP = 6,
|
||||
HTC_M_MONITOR = 8,
|
||||
HTC_M_WDS = 2
|
||||
};
|
||||
|
||||
#define ATH9K_HTC_HDRSPACE sizeof(struct htc_frame_hdr)
|
||||
#define ATH9K_HTC_AMPDU 1
|
||||
#define ATH9K_HTC_NORMAL 2
|
||||
|
||||
#define ATH9K_HTC_TX_CTSONLY 0x1
|
||||
#define ATH9K_HTC_TX_RTSCTS 0x2
|
||||
#define ATH9K_HTC_TX_USE_MIN_RATE 0x100
|
||||
|
||||
struct tx_frame_hdr {
|
||||
u8 data_type;
|
||||
u8 node_idx;
|
||||
u8 vif_idx;
|
||||
u8 tidno;
|
||||
u32 flags; /* ATH9K_HTC_TX_* */
|
||||
u8 key_type;
|
||||
u8 keyix;
|
||||
u8 reserved[26];
|
||||
} __packed;
|
||||
|
||||
struct tx_mgmt_hdr {
|
||||
u8 node_idx;
|
||||
u8 vif_idx;
|
||||
u8 tidno;
|
||||
u8 flags;
|
||||
u8 key_type;
|
||||
u8 keyix;
|
||||
u16 reserved;
|
||||
} __packed;
|
||||
|
||||
struct tx_beacon_header {
|
||||
u8 len_changed;
|
||||
u8 vif_index;
|
||||
u16 rev;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_target_hw {
|
||||
u32 flags;
|
||||
u32 flags_ext;
|
||||
u32 ampdu_limit;
|
||||
u8 ampdu_subframes;
|
||||
u8 tx_chainmask;
|
||||
u8 tx_chainmask_legacy;
|
||||
u8 rtscts_ratecode;
|
||||
u8 protmode;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_cap_target {
|
||||
u32 flags;
|
||||
u32 flags_ext;
|
||||
u32 ampdu_limit;
|
||||
u8 ampdu_subframes;
|
||||
u8 tx_chainmask;
|
||||
u8 tx_chainmask_legacy;
|
||||
u8 rtscts_ratecode;
|
||||
u8 protmode;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_target_vif {
|
||||
u8 index;
|
||||
u8 des_bssid[ETH_ALEN];
|
||||
enum htc_opmode opmode;
|
||||
u8 myaddr[ETH_ALEN];
|
||||
u8 bssid[ETH_ALEN];
|
||||
u32 flags;
|
||||
u32 flags_ext;
|
||||
u16 ps_sta;
|
||||
u16 rtsthreshold;
|
||||
u8 ath_cap;
|
||||
u8 node;
|
||||
s8 mcast_rate;
|
||||
} __packed;
|
||||
|
||||
#define ATH_HTC_STA_AUTH 0x0001
|
||||
#define ATH_HTC_STA_QOS 0x0002
|
||||
#define ATH_HTC_STA_ERP 0x0004
|
||||
#define ATH_HTC_STA_HT 0x0008
|
||||
|
||||
/* FIXME: UAPSD variables */
|
||||
struct ath9k_htc_target_sta {
|
||||
u16 associd;
|
||||
u16 txpower;
|
||||
u32 ucastkey;
|
||||
u8 macaddr[ETH_ALEN];
|
||||
u8 bssid[ETH_ALEN];
|
||||
u8 sta_index;
|
||||
u8 vif_index;
|
||||
u8 vif_sta;
|
||||
u16 flags; /* ATH_HTC_STA_* */
|
||||
u16 htcap;
|
||||
u8 valid;
|
||||
u16 capinfo;
|
||||
struct ath9k_htc_target_hw *hw;
|
||||
struct ath9k_htc_target_vif *vif;
|
||||
u16 txseqmgmt;
|
||||
u8 is_vif_sta;
|
||||
u16 maxampdu;
|
||||
u16 iv16;
|
||||
u32 iv32;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_target_aggr {
|
||||
u8 sta_index;
|
||||
u8 tidno;
|
||||
u8 aggr_enable;
|
||||
u8 padding;
|
||||
} __packed;
|
||||
|
||||
#define ATH_HTC_RATE_MAX 30
|
||||
|
||||
#define WLAN_RC_DS_FLAG 0x01
|
||||
#define WLAN_RC_40_FLAG 0x02
|
||||
#define WLAN_RC_SGI_FLAG 0x04
|
||||
#define WLAN_RC_HT_FLAG 0x08
|
||||
|
||||
struct ath9k_htc_rateset {
|
||||
u8 rs_nrates;
|
||||
u8 rs_rates[ATH_HTC_RATE_MAX];
|
||||
};
|
||||
|
||||
struct ath9k_htc_rate {
|
||||
struct ath9k_htc_rateset legacy_rates;
|
||||
struct ath9k_htc_rateset ht_rates;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_target_rate {
|
||||
u8 sta_index;
|
||||
u8 isnew;
|
||||
u32 capflags;
|
||||
struct ath9k_htc_rate rates;
|
||||
};
|
||||
|
||||
struct ath9k_htc_target_stats {
|
||||
u32 tx_shortretry;
|
||||
u32 tx_longretry;
|
||||
u32 tx_xretries;
|
||||
u32 ht_txunaggr_xretry;
|
||||
u32 ht_tx_xretries;
|
||||
} __packed;
|
||||
|
||||
struct ath9k_htc_vif {
|
||||
u8 index;
|
||||
};
|
||||
|
||||
#define ATH9K_HTC_MAX_STA 8
|
||||
#define ATH9K_HTC_MAX_TID 8
|
||||
|
||||
enum tid_aggr_state {
|
||||
AGGR_STOP = 0,
|
||||
AGGR_PROGRESS,
|
||||
AGGR_START,
|
||||
AGGR_OPERATIONAL
|
||||
};
|
||||
|
||||
struct ath9k_htc_sta {
|
||||
u8 index;
|
||||
enum tid_aggr_state tid_state[ATH9K_HTC_MAX_TID];
|
||||
};
|
||||
|
||||
struct ath9k_htc_aggr_work {
|
||||
u16 tid;
|
||||
u8 sta_addr[ETH_ALEN];
|
||||
struct ieee80211_hw *hw;
|
||||
struct ieee80211_vif *vif;
|
||||
enum ieee80211_ampdu_mlme_action action;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
#define ATH9K_HTC_RXBUF 256
|
||||
#define HTC_RX_FRAME_HEADER_SIZE 40
|
||||
|
||||
struct ath9k_htc_rxbuf {
|
||||
bool in_process;
|
||||
struct sk_buff *skb;
|
||||
struct ath_htc_rx_status rxstatus;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct ath9k_htc_rx {
|
||||
int last_rssi; /* FIXME: per-STA */
|
||||
struct list_head rxbuf;
|
||||
spinlock_t rxbuflock;
|
||||
};
|
||||
|
||||
struct ath9k_htc_tx_ctl {
|
||||
u8 type; /* ATH9K_HTC_* */
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
||||
|
||||
#define TX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.tx_stats.c++)
|
||||
#define RX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.rx_stats.c++)
|
||||
|
||||
struct ath_tx_stats {
|
||||
u32 buf_queued;
|
||||
u32 buf_completed;
|
||||
u32 skb_queued;
|
||||
u32 skb_completed;
|
||||
};
|
||||
|
||||
struct ath_rx_stats {
|
||||
u32 skb_allocated;
|
||||
u32 skb_completed;
|
||||
u32 skb_dropped;
|
||||
};
|
||||
|
||||
struct ath9k_debug {
|
||||
struct dentry *debugfs_phy;
|
||||
struct dentry *debugfs_tgt_stats;
|
||||
struct dentry *debugfs_xmit;
|
||||
struct dentry *debugfs_recv;
|
||||
struct ath_tx_stats tx_stats;
|
||||
struct ath_rx_stats rx_stats;
|
||||
u32 txrate;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#define TX_STAT_INC(c) do { } while (0)
|
||||
#define RX_STAT_INC(c) do { } while (0)
|
||||
|
||||
#endif /* CONFIG_ATH9K_HTC_DEBUGFS */
|
||||
|
||||
#define ATH_LED_PIN_DEF 1
|
||||
#define ATH_LED_PIN_9287 8
|
||||
#define ATH_LED_PIN_9271 15
|
||||
#define ATH_LED_ON_DURATION_IDLE 350 /* in msecs */
|
||||
#define ATH_LED_OFF_DURATION_IDLE 250 /* in msecs */
|
||||
|
||||
enum ath_led_type {
|
||||
ATH_LED_RADIO,
|
||||
ATH_LED_ASSOC,
|
||||
ATH_LED_TX,
|
||||
ATH_LED_RX
|
||||
};
|
||||
|
||||
struct ath_led {
|
||||
struct ath9k_htc_priv *priv;
|
||||
struct led_classdev led_cdev;
|
||||
enum ath_led_type led_type;
|
||||
struct delayed_work brightness_work;
|
||||
char name[32];
|
||||
bool registered;
|
||||
int brightness;
|
||||
};
|
||||
|
||||
#define OP_INVALID BIT(0)
|
||||
#define OP_SCANNING BIT(1)
|
||||
#define OP_FULL_RESET BIT(2)
|
||||
#define OP_LED_ASSOCIATED BIT(3)
|
||||
#define OP_LED_ON BIT(4)
|
||||
#define OP_PREAMBLE_SHORT BIT(5)
|
||||
#define OP_PROTECT_ENABLE BIT(6)
|
||||
#define OP_TXAGGR BIT(7)
|
||||
#define OP_ASSOCIATED BIT(8)
|
||||
#define OP_ENABLE_BEACON BIT(9)
|
||||
#define OP_LED_DEINIT BIT(10)
|
||||
|
||||
struct ath9k_htc_priv {
|
||||
struct device *dev;
|
||||
struct ieee80211_hw *hw;
|
||||
struct ath_hw *ah;
|
||||
struct htc_target *htc;
|
||||
struct wmi *wmi;
|
||||
|
||||
enum htc_endpoint_id wmi_cmd_ep;
|
||||
enum htc_endpoint_id beacon_ep;
|
||||
enum htc_endpoint_id cab_ep;
|
||||
enum htc_endpoint_id uapsd_ep;
|
||||
enum htc_endpoint_id mgmt_ep;
|
||||
enum htc_endpoint_id data_be_ep;
|
||||
enum htc_endpoint_id data_bk_ep;
|
||||
enum htc_endpoint_id data_vi_ep;
|
||||
enum htc_endpoint_id data_vo_ep;
|
||||
|
||||
u16 op_flags;
|
||||
u16 curtxpow;
|
||||
u16 txpowlimit;
|
||||
u16 nvifs;
|
||||
u16 nstations;
|
||||
u16 seq_no;
|
||||
u32 bmiss_cnt;
|
||||
|
||||
struct sk_buff *beacon;
|
||||
spinlock_t beacon_lock;
|
||||
|
||||
struct ieee80211_vif *vif;
|
||||
unsigned int rxfilter;
|
||||
struct tasklet_struct wmi_tasklet;
|
||||
struct tasklet_struct rx_tasklet;
|
||||
struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS];
|
||||
struct ath9k_htc_rx rx;
|
||||
struct tasklet_struct tx_tasklet;
|
||||
struct sk_buff_head tx_queue;
|
||||
struct ath9k_htc_aggr_work aggr_work;
|
||||
struct delayed_work ath9k_aggr_work;
|
||||
struct delayed_work ath9k_ani_work;
|
||||
|
||||
struct ath_led radio_led;
|
||||
struct ath_led assoc_led;
|
||||
struct ath_led tx_led;
|
||||
struct ath_led rx_led;
|
||||
struct delayed_work ath9k_led_blink_work;
|
||||
int led_on_duration;
|
||||
int led_off_duration;
|
||||
int led_on_cnt;
|
||||
int led_off_cnt;
|
||||
int hwq_map[ATH9K_WME_AC_VO+1];
|
||||
|
||||
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
||||
struct ath9k_debug debug;
|
||||
#endif
|
||||
struct ath9k_htc_target_rate tgt_rate;
|
||||
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static inline void ath_read_cachesize(struct ath_common *common, int *csz)
|
||||
{
|
||||
common->bus_ops->read_cachesize(common, csz);
|
||||
}
|
||||
|
||||
void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *bss_conf);
|
||||
void ath9k_htc_swba(struct ath9k_htc_priv *priv, u8 beacon_pending);
|
||||
void ath9k_htc_beacon_update(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_vif *vif);
|
||||
|
||||
void ath9k_htc_rxep(void *priv, struct sk_buff *skb,
|
||||
enum htc_endpoint_id ep_id);
|
||||
void ath9k_htc_txep(void *priv, struct sk_buff *skb, enum htc_endpoint_id ep_id,
|
||||
bool txok);
|
||||
|
||||
void ath9k_htc_station_work(struct work_struct *work);
|
||||
void ath9k_htc_aggr_work(struct work_struct *work);
|
||||
void ath9k_ani_work(struct work_struct *work);;
|
||||
|
||||
int ath9k_tx_init(struct ath9k_htc_priv *priv);
|
||||
void ath9k_tx_tasklet(unsigned long data);
|
||||
int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, struct sk_buff *skb);
|
||||
void ath9k_tx_cleanup(struct ath9k_htc_priv *priv);
|
||||
bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv,
|
||||
enum ath9k_tx_queue_subtype qtype);
|
||||
int get_hw_qnum(u16 queue, int *hwq_map);
|
||||
int ath_txq_update(struct ath9k_htc_priv *priv, int qnum,
|
||||
struct ath9k_tx_queue_info *qinfo);
|
||||
|
||||
int ath9k_rx_init(struct ath9k_htc_priv *priv);
|
||||
void ath9k_rx_cleanup(struct ath9k_htc_priv *priv);
|
||||
void ath9k_host_rx_init(struct ath9k_htc_priv *priv);
|
||||
void ath9k_rx_tasklet(unsigned long data);
|
||||
|
||||
void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv);
|
||||
void ath9k_init_leds(struct ath9k_htc_priv *priv);
|
||||
void ath9k_deinit_leds(struct ath9k_htc_priv *priv);
|
||||
|
||||
int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,
|
||||
u16 devid);
|
||||
void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug);
|
||||
#ifdef CONFIG_PM
|
||||
int ath9k_htc_resume(struct htc_target *htc_handle);
|
||||
#endif
|
||||
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
||||
int ath9k_debug_create_root(void);
|
||||
void ath9k_debug_remove_root(void);
|
||||
int ath9k_init_debug(struct ath_hw *ah);
|
||||
void ath9k_exit_debug(struct ath_hw *ah);
|
||||
#else
|
||||
static inline int ath9k_debug_create_root(void) { return 0; };
|
||||
static inline void ath9k_debug_remove_root(void) {};
|
||||
static inline int ath9k_init_debug(struct ath_hw *ah) { return 0; };
|
||||
static inline void ath9k_exit_debug(struct ath_hw *ah) {};
|
||||
#endif /* CONFIG_ATH9K_HTC_DEBUGFS */
|
||||
|
||||
#endif /* HTC_H */
|
260
drivers/net/wireless/ath/ath9k/htc_drv_beacon.c
Normal file
260
drivers/net/wireless/ath/ath9k/htc_drv_beacon.c
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
#define FUDGE 2
|
||||
|
||||
static void ath9k_htc_beacon_config_sta(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_bss_conf *bss_conf)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
struct ath9k_beacon_state bs;
|
||||
enum ath9k_int imask = 0;
|
||||
int dtimperiod, dtimcount, sleepduration;
|
||||
int cfpperiod, cfpcount, bmiss_timeout;
|
||||
u32 nexttbtt = 0, intval, tsftu, htc_imask = 0;
|
||||
u64 tsf;
|
||||
int num_beacons, offset, dtim_dec_count, cfp_dec_count;
|
||||
int ret;
|
||||
u8 cmd_rsp;
|
||||
|
||||
memset(&bs, 0, sizeof(bs));
|
||||
|
||||
intval = bss_conf->beacon_int & ATH9K_BEACON_PERIOD;
|
||||
bmiss_timeout = (ATH_DEFAULT_BMISS_LIMIT * bss_conf->beacon_int);
|
||||
|
||||
/*
|
||||
* Setup dtim and cfp parameters according to
|
||||
* last beacon we received (which may be none).
|
||||
*/
|
||||
dtimperiod = bss_conf->dtim_period;
|
||||
if (dtimperiod <= 0) /* NB: 0 if not known */
|
||||
dtimperiod = 1;
|
||||
dtimcount = 1;
|
||||
if (dtimcount >= dtimperiod) /* NB: sanity check */
|
||||
dtimcount = 0;
|
||||
cfpperiod = 1; /* NB: no PCF support yet */
|
||||
cfpcount = 0;
|
||||
|
||||
sleepduration = intval;
|
||||
if (sleepduration <= 0)
|
||||
sleepduration = intval;
|
||||
|
||||
/*
|
||||
* Pull nexttbtt forward to reflect the current
|
||||
* TSF and calculate dtim+cfp state for the result.
|
||||
*/
|
||||
tsf = ath9k_hw_gettsf64(priv->ah);
|
||||
tsftu = TSF_TO_TU(tsf>>32, tsf) + FUDGE;
|
||||
|
||||
num_beacons = tsftu / intval + 1;
|
||||
offset = tsftu % intval;
|
||||
nexttbtt = tsftu - offset;
|
||||
if (offset)
|
||||
nexttbtt += intval;
|
||||
|
||||
/* DTIM Beacon every dtimperiod Beacon */
|
||||
dtim_dec_count = num_beacons % dtimperiod;
|
||||
/* CFP every cfpperiod DTIM Beacon */
|
||||
cfp_dec_count = (num_beacons / dtimperiod) % cfpperiod;
|
||||
if (dtim_dec_count)
|
||||
cfp_dec_count++;
|
||||
|
||||
dtimcount -= dtim_dec_count;
|
||||
if (dtimcount < 0)
|
||||
dtimcount += dtimperiod;
|
||||
|
||||
cfpcount -= cfp_dec_count;
|
||||
if (cfpcount < 0)
|
||||
cfpcount += cfpperiod;
|
||||
|
||||
bs.bs_intval = intval;
|
||||
bs.bs_nexttbtt = nexttbtt;
|
||||
bs.bs_dtimperiod = dtimperiod*intval;
|
||||
bs.bs_nextdtim = bs.bs_nexttbtt + dtimcount*intval;
|
||||
bs.bs_cfpperiod = cfpperiod*bs.bs_dtimperiod;
|
||||
bs.bs_cfpnext = bs.bs_nextdtim + cfpcount*bs.bs_dtimperiod;
|
||||
bs.bs_cfpmaxduration = 0;
|
||||
|
||||
/*
|
||||
* Calculate the number of consecutive beacons to miss* before taking
|
||||
* a BMISS interrupt. The configuration is specified in TU so we only
|
||||
* need calculate based on the beacon interval. Note that we clamp the
|
||||
* result to at most 15 beacons.
|
||||
*/
|
||||
if (sleepduration > intval) {
|
||||
bs.bs_bmissthreshold = ATH_DEFAULT_BMISS_LIMIT / 2;
|
||||
} else {
|
||||
bs.bs_bmissthreshold = DIV_ROUND_UP(bmiss_timeout, intval);
|
||||
if (bs.bs_bmissthreshold > 15)
|
||||
bs.bs_bmissthreshold = 15;
|
||||
else if (bs.bs_bmissthreshold <= 0)
|
||||
bs.bs_bmissthreshold = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate sleep duration. The configuration is given in ms.
|
||||
* We ensure a multiple of the beacon period is used. Also, if the sleep
|
||||
* duration is greater than the DTIM period then it makes senses
|
||||
* to make it a multiple of that.
|
||||
*
|
||||
* XXX fixed at 100ms
|
||||
*/
|
||||
|
||||
bs.bs_sleepduration = roundup(IEEE80211_MS_TO_TU(100), sleepduration);
|
||||
if (bs.bs_sleepduration > bs.bs_dtimperiod)
|
||||
bs.bs_sleepduration = bs.bs_dtimperiod;
|
||||
|
||||
/* TSF out of range threshold fixed at 1 second */
|
||||
bs.bs_tsfoor_threshold = ATH9K_TSFOOR_THRESHOLD;
|
||||
|
||||
ath_print(common, ATH_DBG_BEACON, "tsf: %llu tsftu: %u\n", tsf, tsftu);
|
||||
ath_print(common, ATH_DBG_BEACON,
|
||||
"bmiss: %u sleep: %u cfp-period: %u maxdur: %u next: %u\n",
|
||||
bs.bs_bmissthreshold, bs.bs_sleepduration,
|
||||
bs.bs_cfpperiod, bs.bs_cfpmaxduration, bs.bs_cfpnext);
|
||||
|
||||
/* Set the computed STA beacon timers */
|
||||
|
||||
WMI_CMD(WMI_DISABLE_INTR_CMDID);
|
||||
ath9k_hw_set_sta_beacon_timers(priv->ah, &bs);
|
||||
imask |= ATH9K_INT_BMISS;
|
||||
htc_imask = cpu_to_be32(imask);
|
||||
WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask);
|
||||
}
|
||||
|
||||
static void ath9k_htc_beacon_config_adhoc(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_bss_conf *bss_conf)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
enum ath9k_int imask = 0;
|
||||
u32 nexttbtt, intval, htc_imask = 0;
|
||||
int ret;
|
||||
u8 cmd_rsp;
|
||||
|
||||
intval = bss_conf->beacon_int & ATH9K_BEACON_PERIOD;
|
||||
nexttbtt = intval;
|
||||
intval |= ATH9K_BEACON_ENA;
|
||||
if (priv->op_flags & OP_ENABLE_BEACON)
|
||||
imask |= ATH9K_INT_SWBA;
|
||||
|
||||
ath_print(common, ATH_DBG_BEACON,
|
||||
"IBSS Beacon config, intval: %d, imask: 0x%x\n",
|
||||
bss_conf->beacon_int, imask);
|
||||
|
||||
WMI_CMD(WMI_DISABLE_INTR_CMDID);
|
||||
ath9k_hw_beaconinit(priv->ah, nexttbtt, intval);
|
||||
priv->bmiss_cnt = 0;
|
||||
htc_imask = cpu_to_be32(imask);
|
||||
WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask);
|
||||
}
|
||||
|
||||
void ath9k_htc_beacon_update(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_vif *vif)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
|
||||
spin_lock_bh(&priv->beacon_lock);
|
||||
|
||||
if (priv->beacon)
|
||||
dev_kfree_skb_any(priv->beacon);
|
||||
|
||||
priv->beacon = ieee80211_beacon_get(priv->hw, vif);
|
||||
if (!priv->beacon)
|
||||
ath_print(common, ATH_DBG_BEACON,
|
||||
"Unable to allocate beacon\n");
|
||||
|
||||
spin_unlock_bh(&priv->beacon_lock);
|
||||
}
|
||||
|
||||
void ath9k_htc_swba(struct ath9k_htc_priv *priv, u8 beacon_pending)
|
||||
{
|
||||
struct ath9k_htc_vif *avp = (void *)priv->vif->drv_priv;
|
||||
struct tx_beacon_header beacon_hdr;
|
||||
struct ath9k_htc_tx_ctl tx_ctl;
|
||||
struct ieee80211_tx_info *info;
|
||||
u8 *tx_fhdr;
|
||||
|
||||
memset(&beacon_hdr, 0, sizeof(struct tx_beacon_header));
|
||||
memset(&tx_ctl, 0, sizeof(struct ath9k_htc_tx_ctl));
|
||||
|
||||
/* FIXME: Handle BMISS */
|
||||
if (beacon_pending != 0) {
|
||||
priv->bmiss_cnt++;
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_bh(&priv->beacon_lock);
|
||||
|
||||
if (unlikely(priv->op_flags & OP_SCANNING)) {
|
||||
spin_unlock_bh(&priv->beacon_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (unlikely(priv->beacon == NULL)) {
|
||||
spin_unlock_bh(&priv->beacon_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Free the old SKB first */
|
||||
dev_kfree_skb_any(priv->beacon);
|
||||
|
||||
/* Get a new beacon */
|
||||
priv->beacon = ieee80211_beacon_get(priv->hw, priv->vif);
|
||||
if (!priv->beacon) {
|
||||
spin_unlock_bh(&priv->beacon_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
info = IEEE80211_SKB_CB(priv->beacon);
|
||||
if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) {
|
||||
struct ieee80211_hdr *hdr =
|
||||
(struct ieee80211_hdr *) priv->beacon->data;
|
||||
priv->seq_no += 0x10;
|
||||
hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG);
|
||||
hdr->seq_ctrl |= cpu_to_le16(priv->seq_no);
|
||||
}
|
||||
|
||||
tx_ctl.type = ATH9K_HTC_NORMAL;
|
||||
beacon_hdr.vif_index = avp->index;
|
||||
tx_fhdr = skb_push(priv->beacon, sizeof(beacon_hdr));
|
||||
memcpy(tx_fhdr, (u8 *) &beacon_hdr, sizeof(beacon_hdr));
|
||||
|
||||
htc_send(priv->htc, priv->beacon, priv->beacon_ep, &tx_ctl);
|
||||
|
||||
spin_unlock_bh(&priv->beacon_lock);
|
||||
}
|
||||
|
||||
void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_bss_conf *bss_conf)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
|
||||
switch (vif->type) {
|
||||
case NL80211_IFTYPE_STATION:
|
||||
ath9k_htc_beacon_config_sta(priv, bss_conf);
|
||||
break;
|
||||
case NL80211_IFTYPE_ADHOC:
|
||||
ath9k_htc_beacon_config_adhoc(priv, bss_conf);
|
||||
break;
|
||||
default:
|
||||
ath_print(common, ATH_DBG_CONFIG,
|
||||
"Unsupported beaconing mode\n");
|
||||
return;
|
||||
}
|
||||
}
|
713
drivers/net/wireless/ath/ath9k/htc_drv_init.c
Normal file
713
drivers/net/wireless/ath/ath9k/htc_drv_init.c
Normal file
@ -0,0 +1,713 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
MODULE_AUTHOR("Atheros Communications");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("Atheros driver 802.11n HTC based wireless devices");
|
||||
|
||||
static unsigned int ath9k_debug = ATH_DBG_DEFAULT;
|
||||
module_param_named(debug, ath9k_debug, uint, 0);
|
||||
MODULE_PARM_DESC(debug, "Debugging mask");
|
||||
|
||||
int modparam_nohwcrypt;
|
||||
module_param_named(nohwcrypt, modparam_nohwcrypt, int, 0444);
|
||||
MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption");
|
||||
|
||||
#define CHAN2G(_freq, _idx) { \
|
||||
.center_freq = (_freq), \
|
||||
.hw_value = (_idx), \
|
||||
.max_power = 20, \
|
||||
}
|
||||
|
||||
static struct ieee80211_channel ath9k_2ghz_channels[] = {
|
||||
CHAN2G(2412, 0), /* Channel 1 */
|
||||
CHAN2G(2417, 1), /* Channel 2 */
|
||||
CHAN2G(2422, 2), /* Channel 3 */
|
||||
CHAN2G(2427, 3), /* Channel 4 */
|
||||
CHAN2G(2432, 4), /* Channel 5 */
|
||||
CHAN2G(2437, 5), /* Channel 6 */
|
||||
CHAN2G(2442, 6), /* Channel 7 */
|
||||
CHAN2G(2447, 7), /* Channel 8 */
|
||||
CHAN2G(2452, 8), /* Channel 9 */
|
||||
CHAN2G(2457, 9), /* Channel 10 */
|
||||
CHAN2G(2462, 10), /* Channel 11 */
|
||||
CHAN2G(2467, 11), /* Channel 12 */
|
||||
CHAN2G(2472, 12), /* Channel 13 */
|
||||
CHAN2G(2484, 13), /* Channel 14 */
|
||||
};
|
||||
|
||||
/* Atheros hardware rate code addition for short premble */
|
||||
#define SHPCHECK(__hw_rate, __flags) \
|
||||
((__flags & IEEE80211_RATE_SHORT_PREAMBLE) ? (__hw_rate | 0x04) : 0)
|
||||
|
||||
#define RATE(_bitrate, _hw_rate, _flags) { \
|
||||
.bitrate = (_bitrate), \
|
||||
.flags = (_flags), \
|
||||
.hw_value = (_hw_rate), \
|
||||
.hw_value_short = (SHPCHECK(_hw_rate, _flags)) \
|
||||
}
|
||||
|
||||
static struct ieee80211_rate ath9k_legacy_rates[] = {
|
||||
RATE(10, 0x1b, 0),
|
||||
RATE(20, 0x1a, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp : 0x1e */
|
||||
RATE(55, 0x19, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp: 0x1d */
|
||||
RATE(110, 0x18, IEEE80211_RATE_SHORT_PREAMBLE), /* short: 0x1c */
|
||||
RATE(60, 0x0b, 0),
|
||||
RATE(90, 0x0f, 0),
|
||||
RATE(120, 0x0a, 0),
|
||||
RATE(180, 0x0e, 0),
|
||||
RATE(240, 0x09, 0),
|
||||
RATE(360, 0x0d, 0),
|
||||
RATE(480, 0x08, 0),
|
||||
RATE(540, 0x0c, 0),
|
||||
};
|
||||
|
||||
static int ath9k_htc_wait_for_target(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
int time_left;
|
||||
|
||||
/* Firmware can take up to 50ms to get ready, to be safe use 1 second */
|
||||
time_left = wait_for_completion_timeout(&priv->htc->target_wait, HZ);
|
||||
if (!time_left) {
|
||||
dev_err(priv->dev, "ath9k_htc: Target is unresponsive\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ath9k_deinit_priv(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
ath9k_exit_debug(priv->ah);
|
||||
ath9k_hw_deinit(priv->ah);
|
||||
tasklet_kill(&priv->wmi_tasklet);
|
||||
tasklet_kill(&priv->rx_tasklet);
|
||||
tasklet_kill(&priv->tx_tasklet);
|
||||
kfree(priv->ah);
|
||||
priv->ah = NULL;
|
||||
}
|
||||
|
||||
static void ath9k_deinit_device(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ieee80211_hw *hw = priv->hw;
|
||||
|
||||
wiphy_rfkill_stop_polling(hw->wiphy);
|
||||
ath9k_deinit_leds(priv);
|
||||
ieee80211_unregister_hw(hw);
|
||||
ath9k_rx_cleanup(priv);
|
||||
ath9k_tx_cleanup(priv);
|
||||
ath9k_deinit_priv(priv);
|
||||
}
|
||||
|
||||
static inline int ath9k_htc_connect_svc(struct ath9k_htc_priv *priv,
|
||||
u16 service_id,
|
||||
void (*tx) (void *,
|
||||
struct sk_buff *,
|
||||
enum htc_endpoint_id,
|
||||
bool txok),
|
||||
enum htc_endpoint_id *ep_id)
|
||||
{
|
||||
struct htc_service_connreq req;
|
||||
|
||||
memset(&req, 0, sizeof(struct htc_service_connreq));
|
||||
|
||||
req.service_id = service_id;
|
||||
req.ep_callbacks.priv = priv;
|
||||
req.ep_callbacks.rx = ath9k_htc_rxep;
|
||||
req.ep_callbacks.tx = tx;
|
||||
|
||||
return htc_connect_service(priv->htc, &req, ep_id);
|
||||
}
|
||||
|
||||
static int ath9k_init_htc_services(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* WMI CMD*/
|
||||
ret = ath9k_wmi_connect(priv->htc, priv->wmi, &priv->wmi_cmd_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Beacon */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_BEACON_SVC, NULL,
|
||||
&priv->beacon_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* CAB */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_CAB_SVC, ath9k_htc_txep,
|
||||
&priv->cab_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
|
||||
/* UAPSD */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_UAPSD_SVC, ath9k_htc_txep,
|
||||
&priv->uapsd_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* MGMT */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_MGMT_SVC, ath9k_htc_txep,
|
||||
&priv->mgmt_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* DATA BE */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_DATA_BE_SVC, ath9k_htc_txep,
|
||||
&priv->data_be_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* DATA BK */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_DATA_BK_SVC, ath9k_htc_txep,
|
||||
&priv->data_bk_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* DATA VI */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_DATA_VI_SVC, ath9k_htc_txep,
|
||||
&priv->data_vi_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* DATA VO */
|
||||
ret = ath9k_htc_connect_svc(priv, WMI_DATA_VO_SVC, ath9k_htc_txep,
|
||||
&priv->data_vo_ep);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = htc_init(priv->htc);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
dev_err(priv->dev, "ath9k_htc: Unable to initialize HTC services\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ath9k_reg_notifier(struct wiphy *wiphy,
|
||||
struct regulatory_request *request)
|
||||
{
|
||||
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
|
||||
struct ath9k_htc_priv *priv = hw->priv;
|
||||
|
||||
return ath_reg_notifier_apply(wiphy, request,
|
||||
ath9k_hw_regulatory(priv->ah));
|
||||
}
|
||||
|
||||
static unsigned int ath9k_ioread32(void *hw_priv, u32 reg_offset)
|
||||
{
|
||||
struct ath_hw *ah = (struct ath_hw *) hw_priv;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
|
||||
__be32 val, reg = cpu_to_be32(reg_offset);
|
||||
int r;
|
||||
|
||||
r = ath9k_wmi_cmd(priv->wmi, WMI_REG_READ_CMDID,
|
||||
(u8 *) ®, sizeof(reg),
|
||||
(u8 *) &val, sizeof(val),
|
||||
100);
|
||||
if (unlikely(r)) {
|
||||
ath_print(common, ATH_DBG_WMI,
|
||||
"REGISTER READ FAILED: (0x%04x, %d)\n",
|
||||
reg_offset, r);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return be32_to_cpu(val);
|
||||
}
|
||||
|
||||
static void ath9k_iowrite32(void *hw_priv, u32 val, u32 reg_offset)
|
||||
{
|
||||
struct ath_hw *ah = (struct ath_hw *) hw_priv;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv;
|
||||
__be32 buf[2] = {
|
||||
cpu_to_be32(reg_offset),
|
||||
cpu_to_be32(val),
|
||||
};
|
||||
int r;
|
||||
|
||||
r = ath9k_wmi_cmd(priv->wmi, WMI_REG_WRITE_CMDID,
|
||||
(u8 *) &buf, sizeof(buf),
|
||||
(u8 *) &val, sizeof(val),
|
||||
100);
|
||||
if (unlikely(r)) {
|
||||
ath_print(common, ATH_DBG_WMI,
|
||||
"REGISTER WRITE FAILED:(0x%04x, %d)\n",
|
||||
reg_offset, r);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ath_ops ath9k_common_ops = {
|
||||
.read = ath9k_ioread32,
|
||||
.write = ath9k_iowrite32,
|
||||
};
|
||||
|
||||
static void ath_usb_read_cachesize(struct ath_common *common, int *csz)
|
||||
{
|
||||
*csz = L1_CACHE_BYTES >> 2;
|
||||
}
|
||||
|
||||
static bool ath_usb_eeprom_read(struct ath_common *common, u32 off, u16 *data)
|
||||
{
|
||||
struct ath_hw *ah = (struct ath_hw *) common->ah;
|
||||
|
||||
(void)REG_READ(ah, AR5416_EEPROM_OFFSET + (off << AR5416_EEPROM_S));
|
||||
|
||||
if (!ath9k_hw_wait(ah,
|
||||
AR_EEPROM_STATUS_DATA,
|
||||
AR_EEPROM_STATUS_DATA_BUSY |
|
||||
AR_EEPROM_STATUS_DATA_PROT_ACCESS, 0,
|
||||
AH_WAIT_TIMEOUT))
|
||||
return false;
|
||||
|
||||
*data = MS(REG_READ(ah, AR_EEPROM_STATUS_DATA),
|
||||
AR_EEPROM_STATUS_DATA_VAL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct ath_bus_ops ath9k_usb_bus_ops = {
|
||||
.read_cachesize = ath_usb_read_cachesize,
|
||||
.eeprom_read = ath_usb_eeprom_read,
|
||||
};
|
||||
|
||||
static void setup_ht_cap(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_sta_ht_cap *ht_info)
|
||||
{
|
||||
ht_info->ht_supported = true;
|
||||
ht_info->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
|
||||
IEEE80211_HT_CAP_SM_PS |
|
||||
IEEE80211_HT_CAP_SGI_40 |
|
||||
IEEE80211_HT_CAP_DSSSCCK40;
|
||||
|
||||
ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
|
||||
ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_8;
|
||||
|
||||
memset(&ht_info->mcs, 0, sizeof(ht_info->mcs));
|
||||
ht_info->mcs.rx_mask[0] = 0xff;
|
||||
ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED;
|
||||
}
|
||||
|
||||
static int ath9k_init_queues(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(priv->hwq_map); i++)
|
||||
priv->hwq_map[i] = -1;
|
||||
|
||||
if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_BE)) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to setup xmit queue for BE traffic\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_BK)) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to setup xmit queue for BK traffic\n");
|
||||
goto err;
|
||||
}
|
||||
if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_VI)) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to setup xmit queue for VI traffic\n");
|
||||
goto err;
|
||||
}
|
||||
if (!ath9k_htc_txq_setup(priv, ATH9K_WME_AC_VO)) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to setup xmit queue for VO traffic\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void ath9k_init_crypto(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
int i = 0;
|
||||
|
||||
/* Get the hardware key cache size. */
|
||||
common->keymax = priv->ah->caps.keycache_size;
|
||||
if (common->keymax > ATH_KEYMAX) {
|
||||
ath_print(common, ATH_DBG_ANY,
|
||||
"Warning, using only %u entries in %u key cache\n",
|
||||
ATH_KEYMAX, common->keymax);
|
||||
common->keymax = ATH_KEYMAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset the key cache since some parts do not
|
||||
* reset the contents on initial power up.
|
||||
*/
|
||||
for (i = 0; i < common->keymax; i++)
|
||||
ath9k_hw_keyreset(priv->ah, (u16) i);
|
||||
|
||||
if (ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
|
||||
ATH9K_CIPHER_TKIP, NULL)) {
|
||||
/*
|
||||
* Whether we should enable h/w TKIP MIC.
|
||||
* XXX: if we don't support WME TKIP MIC, then we wouldn't
|
||||
* report WMM capable, so it's always safe to turn on
|
||||
* TKIP MIC in this case.
|
||||
*/
|
||||
ath9k_hw_setcapability(priv->ah, ATH9K_CAP_TKIP_MIC, 0, 1, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the separate key cache entries
|
||||
* are required to handle both tx+rx MIC keys.
|
||||
* With split mic keys the number of stations is limited
|
||||
* to 27 otherwise 59.
|
||||
*/
|
||||
if (ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
|
||||
ATH9K_CIPHER_TKIP, NULL)
|
||||
&& ath9k_hw_getcapability(priv->ah, ATH9K_CAP_CIPHER,
|
||||
ATH9K_CIPHER_MIC, NULL)
|
||||
&& ath9k_hw_getcapability(priv->ah, ATH9K_CAP_TKIP_SPLIT,
|
||||
0, NULL))
|
||||
common->splitmic = 1;
|
||||
|
||||
/* turn on mcast key search if possible */
|
||||
if (!ath9k_hw_getcapability(priv->ah, ATH9K_CAP_MCAST_KEYSRCH, 0, NULL))
|
||||
(void)ath9k_hw_setcapability(priv->ah, ATH9K_CAP_MCAST_KEYSRCH,
|
||||
1, 1, NULL);
|
||||
}
|
||||
|
||||
static void ath9k_init_channels_rates(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes)) {
|
||||
priv->sbands[IEEE80211_BAND_2GHZ].channels =
|
||||
ath9k_2ghz_channels;
|
||||
priv->sbands[IEEE80211_BAND_2GHZ].band = IEEE80211_BAND_2GHZ;
|
||||
priv->sbands[IEEE80211_BAND_2GHZ].n_channels =
|
||||
ARRAY_SIZE(ath9k_2ghz_channels);
|
||||
priv->sbands[IEEE80211_BAND_2GHZ].bitrates = ath9k_legacy_rates;
|
||||
priv->sbands[IEEE80211_BAND_2GHZ].n_bitrates =
|
||||
ARRAY_SIZE(ath9k_legacy_rates);
|
||||
}
|
||||
}
|
||||
|
||||
static void ath9k_init_misc(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
|
||||
common->tx_chainmask = priv->ah->caps.tx_chainmask;
|
||||
common->rx_chainmask = priv->ah->caps.rx_chainmask;
|
||||
|
||||
if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK)
|
||||
memcpy(common->bssidmask, ath_bcast_mac, ETH_ALEN);
|
||||
|
||||
priv->op_flags |= OP_TXAGGR;
|
||||
}
|
||||
|
||||
static int ath9k_init_priv(struct ath9k_htc_priv *priv, u16 devid)
|
||||
{
|
||||
struct ath_hw *ah = NULL;
|
||||
struct ath_common *common;
|
||||
int ret = 0, csz = 0;
|
||||
|
||||
priv->op_flags |= OP_INVALID;
|
||||
|
||||
ah = kzalloc(sizeof(struct ath_hw), GFP_KERNEL);
|
||||
if (!ah)
|
||||
return -ENOMEM;
|
||||
|
||||
ah->hw_version.devid = devid;
|
||||
ah->hw_version.subsysid = 0; /* FIXME */
|
||||
priv->ah = ah;
|
||||
|
||||
common = ath9k_hw_common(ah);
|
||||
common->ops = &ath9k_common_ops;
|
||||
common->bus_ops = &ath9k_usb_bus_ops;
|
||||
common->ah = ah;
|
||||
common->hw = priv->hw;
|
||||
common->priv = priv;
|
||||
common->debug_mask = ath9k_debug;
|
||||
|
||||
spin_lock_init(&priv->wmi->wmi_lock);
|
||||
spin_lock_init(&priv->beacon_lock);
|
||||
mutex_init(&priv->mutex);
|
||||
mutex_init(&priv->aggr_work.mutex);
|
||||
tasklet_init(&priv->wmi_tasklet, ath9k_wmi_tasklet,
|
||||
(unsigned long)priv);
|
||||
tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet,
|
||||
(unsigned long)priv);
|
||||
tasklet_init(&priv->tx_tasklet, ath9k_tx_tasklet, (unsigned long)priv);
|
||||
INIT_DELAYED_WORK(&priv->ath9k_aggr_work, ath9k_htc_aggr_work);
|
||||
INIT_DELAYED_WORK(&priv->ath9k_ani_work, ath9k_ani_work);
|
||||
|
||||
/*
|
||||
* Cache line size is used to size and align various
|
||||
* structures used to communicate with the hardware.
|
||||
*/
|
||||
ath_read_cachesize(common, &csz);
|
||||
common->cachelsz = csz << 2; /* convert to bytes */
|
||||
|
||||
ret = ath9k_hw_init(ah);
|
||||
if (ret) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to initialize hardware; "
|
||||
"initialization status: %d\n", ret);
|
||||
goto err_hw;
|
||||
}
|
||||
|
||||
ret = ath9k_init_debug(ah);
|
||||
if (ret) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to create debugfs files\n");
|
||||
goto err_debug;
|
||||
}
|
||||
|
||||
ret = ath9k_init_queues(priv);
|
||||
if (ret)
|
||||
goto err_queues;
|
||||
|
||||
ath9k_init_crypto(priv);
|
||||
ath9k_init_channels_rates(priv);
|
||||
ath9k_init_misc(priv);
|
||||
|
||||
return 0;
|
||||
|
||||
err_queues:
|
||||
ath9k_exit_debug(ah);
|
||||
err_debug:
|
||||
ath9k_hw_deinit(ah);
|
||||
err_hw:
|
||||
|
||||
kfree(ah);
|
||||
priv->ah = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ath9k_set_hw_capab(struct ath9k_htc_priv *priv,
|
||||
struct ieee80211_hw *hw)
|
||||
{
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
|
||||
hw->flags = IEEE80211_HW_SIGNAL_DBM |
|
||||
IEEE80211_HW_AMPDU_AGGREGATION |
|
||||
IEEE80211_HW_SPECTRUM_MGMT |
|
||||
IEEE80211_HW_HAS_RATE_CONTROL;
|
||||
|
||||
hw->wiphy->interface_modes =
|
||||
BIT(NL80211_IFTYPE_STATION) |
|
||||
BIT(NL80211_IFTYPE_ADHOC);
|
||||
|
||||
hw->queues = 4;
|
||||
hw->channel_change_time = 5000;
|
||||
hw->max_listen_interval = 10;
|
||||
hw->vif_data_size = sizeof(struct ath9k_htc_vif);
|
||||
hw->sta_data_size = sizeof(struct ath9k_htc_sta);
|
||||
|
||||
/* tx_frame_hdr is larger than tx_mgmt_hdr anyway */
|
||||
hw->extra_tx_headroom = sizeof(struct tx_frame_hdr) +
|
||||
sizeof(struct htc_frame_hdr) + 4;
|
||||
|
||||
if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes))
|
||||
hw->wiphy->bands[IEEE80211_BAND_2GHZ] =
|
||||
&priv->sbands[IEEE80211_BAND_2GHZ];
|
||||
|
||||
if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_HT) {
|
||||
if (test_bit(ATH9K_MODE_11G, priv->ah->caps.wireless_modes))
|
||||
setup_ht_cap(priv,
|
||||
&priv->sbands[IEEE80211_BAND_2GHZ].ht_cap);
|
||||
}
|
||||
|
||||
SET_IEEE80211_PERM_ADDR(hw, common->macaddr);
|
||||
}
|
||||
|
||||
static int ath9k_init_device(struct ath9k_htc_priv *priv, u16 devid)
|
||||
{
|
||||
struct ieee80211_hw *hw = priv->hw;
|
||||
struct ath_common *common;
|
||||
struct ath_hw *ah;
|
||||
int error = 0;
|
||||
struct ath_regulatory *reg;
|
||||
|
||||
/* Bring up device */
|
||||
error = ath9k_init_priv(priv, devid);
|
||||
if (error != 0)
|
||||
goto err_init;
|
||||
|
||||
ah = priv->ah;
|
||||
common = ath9k_hw_common(ah);
|
||||
ath9k_set_hw_capab(priv, hw);
|
||||
|
||||
/* Initialize regulatory */
|
||||
error = ath_regd_init(&common->regulatory, priv->hw->wiphy,
|
||||
ath9k_reg_notifier);
|
||||
if (error)
|
||||
goto err_regd;
|
||||
|
||||
reg = &common->regulatory;
|
||||
|
||||
/* Setup TX */
|
||||
error = ath9k_tx_init(priv);
|
||||
if (error != 0)
|
||||
goto err_tx;
|
||||
|
||||
/* Setup RX */
|
||||
error = ath9k_rx_init(priv);
|
||||
if (error != 0)
|
||||
goto err_rx;
|
||||
|
||||
/* Register with mac80211 */
|
||||
error = ieee80211_register_hw(hw);
|
||||
if (error)
|
||||
goto err_register;
|
||||
|
||||
/* Handle world regulatory */
|
||||
if (!ath_is_world_regd(reg)) {
|
||||
error = regulatory_hint(hw->wiphy, reg->alpha2);
|
||||
if (error)
|
||||
goto err_world;
|
||||
}
|
||||
|
||||
ath9k_init_leds(priv);
|
||||
ath9k_start_rfkill_poll(priv);
|
||||
|
||||
return 0;
|
||||
|
||||
err_world:
|
||||
ieee80211_unregister_hw(hw);
|
||||
err_register:
|
||||
ath9k_rx_cleanup(priv);
|
||||
err_rx:
|
||||
ath9k_tx_cleanup(priv);
|
||||
err_tx:
|
||||
/* Nothing */
|
||||
err_regd:
|
||||
ath9k_deinit_priv(priv);
|
||||
err_init:
|
||||
return error;
|
||||
}
|
||||
|
||||
int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,
|
||||
u16 devid)
|
||||
{
|
||||
struct ieee80211_hw *hw;
|
||||
struct ath9k_htc_priv *priv;
|
||||
int ret;
|
||||
|
||||
hw = ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);
|
||||
if (!hw)
|
||||
return -ENOMEM;
|
||||
|
||||
priv = hw->priv;
|
||||
priv->hw = hw;
|
||||
priv->htc = htc_handle;
|
||||
priv->dev = dev;
|
||||
htc_handle->drv_priv = priv;
|
||||
SET_IEEE80211_DEV(hw, priv->dev);
|
||||
|
||||
ret = ath9k_htc_wait_for_target(priv);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
priv->wmi = ath9k_init_wmi(priv);
|
||||
if (!priv->wmi) {
|
||||
ret = -EINVAL;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = ath9k_init_htc_services(priv);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
ret = ath9k_init_device(priv, devid);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
return 0;
|
||||
|
||||
err_init:
|
||||
ath9k_deinit_wmi(priv);
|
||||
err_free:
|
||||
ieee80211_free_hw(hw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug)
|
||||
{
|
||||
if (htc_handle->drv_priv) {
|
||||
ath9k_deinit_device(htc_handle->drv_priv);
|
||||
ath9k_deinit_wmi(htc_handle->drv_priv);
|
||||
ieee80211_free_hw(htc_handle->drv_priv->hw);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int ath9k_htc_resume(struct htc_target *htc_handle)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ath9k_htc_wait_for_target(htc_handle->drv_priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ath9k_init_htc_services(htc_handle->drv_priv);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init ath9k_htc_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = ath9k_debug_create_root();
|
||||
if (error < 0) {
|
||||
printk(KERN_ERR
|
||||
"ath9k_htc: Unable to create debugfs root: %d\n",
|
||||
error);
|
||||
goto err_dbg;
|
||||
}
|
||||
|
||||
error = ath9k_hif_usb_init();
|
||||
if (error < 0) {
|
||||
printk(KERN_ERR
|
||||
"ath9k_htc: No USB devices found,"
|
||||
" driver not installed.\n");
|
||||
error = -ENODEV;
|
||||
goto err_usb;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_usb:
|
||||
ath9k_debug_remove_root();
|
||||
err_dbg:
|
||||
return error;
|
||||
}
|
||||
module_init(ath9k_htc_init);
|
||||
|
||||
static void __exit ath9k_htc_exit(void)
|
||||
{
|
||||
ath9k_hif_usb_exit();
|
||||
ath9k_debug_remove_root();
|
||||
printk(KERN_INFO "ath9k_htc: Driver unloaded\n");
|
||||
}
|
||||
module_exit(ath9k_htc_exit);
|
1626
drivers/net/wireless/ath/ath9k/htc_drv_main.c
Normal file
1626
drivers/net/wireless/ath/ath9k/htc_drv_main.c
Normal file
File diff suppressed because it is too large
Load Diff
604
drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
Normal file
604
drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
Normal file
@ -0,0 +1,604 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
/******/
|
||||
/* TX */
|
||||
/******/
|
||||
|
||||
int get_hw_qnum(u16 queue, int *hwq_map)
|
||||
{
|
||||
switch (queue) {
|
||||
case 0:
|
||||
return hwq_map[ATH9K_WME_AC_VO];
|
||||
case 1:
|
||||
return hwq_map[ATH9K_WME_AC_VI];
|
||||
case 2:
|
||||
return hwq_map[ATH9K_WME_AC_BE];
|
||||
case 3:
|
||||
return hwq_map[ATH9K_WME_AC_BK];
|
||||
default:
|
||||
return hwq_map[ATH9K_WME_AC_BE];
|
||||
}
|
||||
}
|
||||
|
||||
int ath_txq_update(struct ath9k_htc_priv *priv, int qnum,
|
||||
struct ath9k_tx_queue_info *qinfo)
|
||||
{
|
||||
struct ath_hw *ah = priv->ah;
|
||||
int error = 0;
|
||||
struct ath9k_tx_queue_info qi;
|
||||
|
||||
ath9k_hw_get_txq_props(ah, qnum, &qi);
|
||||
|
||||
qi.tqi_aifs = qinfo->tqi_aifs;
|
||||
qi.tqi_cwmin = qinfo->tqi_cwmin / 2; /* XXX */
|
||||
qi.tqi_cwmax = qinfo->tqi_cwmax;
|
||||
qi.tqi_burstTime = qinfo->tqi_burstTime;
|
||||
qi.tqi_readyTime = qinfo->tqi_readyTime;
|
||||
|
||||
if (!ath9k_hw_set_txq_props(ah, qnum, &qi)) {
|
||||
ath_print(ath9k_hw_common(ah), ATH_DBG_FATAL,
|
||||
"Unable to update hardware queue %u!\n", qnum);
|
||||
error = -EIO;
|
||||
} else {
|
||||
ath9k_hw_resettxqueue(ah, qnum);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, struct sk_buff *skb)
|
||||
{
|
||||
struct ieee80211_hdr *hdr;
|
||||
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
||||
struct ieee80211_sta *sta = tx_info->control.sta;
|
||||
struct ath9k_htc_sta *ista;
|
||||
struct ath9k_htc_vif *avp;
|
||||
struct ath9k_htc_tx_ctl tx_ctl;
|
||||
enum htc_endpoint_id epid;
|
||||
u16 qnum, hw_qnum;
|
||||
__le16 fc;
|
||||
u8 *tx_fhdr;
|
||||
u8 sta_idx;
|
||||
|
||||
hdr = (struct ieee80211_hdr *) skb->data;
|
||||
fc = hdr->frame_control;
|
||||
|
||||
avp = (struct ath9k_htc_vif *) tx_info->control.vif->drv_priv;
|
||||
if (sta) {
|
||||
ista = (struct ath9k_htc_sta *) sta->drv_priv;
|
||||
sta_idx = ista->index;
|
||||
} else {
|
||||
sta_idx = 0;
|
||||
}
|
||||
|
||||
memset(&tx_ctl, 0, sizeof(struct ath9k_htc_tx_ctl));
|
||||
|
||||
if (ieee80211_is_data(fc)) {
|
||||
struct tx_frame_hdr tx_hdr;
|
||||
u8 *qc;
|
||||
|
||||
memset(&tx_hdr, 0, sizeof(struct tx_frame_hdr));
|
||||
|
||||
tx_hdr.node_idx = sta_idx;
|
||||
tx_hdr.vif_idx = avp->index;
|
||||
|
||||
if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
|
||||
tx_ctl.type = ATH9K_HTC_AMPDU;
|
||||
tx_hdr.data_type = ATH9K_HTC_AMPDU;
|
||||
} else {
|
||||
tx_ctl.type = ATH9K_HTC_NORMAL;
|
||||
tx_hdr.data_type = ATH9K_HTC_NORMAL;
|
||||
}
|
||||
|
||||
if (ieee80211_is_data(fc)) {
|
||||
qc = ieee80211_get_qos_ctl(hdr);
|
||||
tx_hdr.tidno = qc[0] & IEEE80211_QOS_CTL_TID_MASK;
|
||||
}
|
||||
|
||||
/* Check for RTS protection */
|
||||
if (priv->hw->wiphy->rts_threshold != (u32) -1)
|
||||
if (skb->len > priv->hw->wiphy->rts_threshold)
|
||||
tx_hdr.flags |= ATH9K_HTC_TX_RTSCTS;
|
||||
|
||||
/* CTS-to-self */
|
||||
if (!(tx_hdr.flags & ATH9K_HTC_TX_RTSCTS) &&
|
||||
(priv->op_flags & OP_PROTECT_ENABLE))
|
||||
tx_hdr.flags |= ATH9K_HTC_TX_CTSONLY;
|
||||
|
||||
tx_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb);
|
||||
if (tx_hdr.key_type == ATH9K_KEY_TYPE_CLEAR)
|
||||
tx_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID;
|
||||
else
|
||||
tx_hdr.keyix = tx_info->control.hw_key->hw_key_idx;
|
||||
|
||||
tx_fhdr = skb_push(skb, sizeof(tx_hdr));
|
||||
memcpy(tx_fhdr, (u8 *) &tx_hdr, sizeof(tx_hdr));
|
||||
|
||||
qnum = skb_get_queue_mapping(skb);
|
||||
hw_qnum = get_hw_qnum(qnum, priv->hwq_map);
|
||||
|
||||
switch (hw_qnum) {
|
||||
case 0:
|
||||
epid = priv->data_be_ep;
|
||||
break;
|
||||
case 2:
|
||||
epid = priv->data_vi_ep;
|
||||
break;
|
||||
case 3:
|
||||
epid = priv->data_vo_ep;
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
epid = priv->data_bk_ep;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
struct tx_mgmt_hdr mgmt_hdr;
|
||||
|
||||
memset(&mgmt_hdr, 0, sizeof(struct tx_mgmt_hdr));
|
||||
|
||||
tx_ctl.type = ATH9K_HTC_NORMAL;
|
||||
|
||||
mgmt_hdr.node_idx = sta_idx;
|
||||
mgmt_hdr.vif_idx = avp->index;
|
||||
mgmt_hdr.tidno = 0;
|
||||
mgmt_hdr.flags = 0;
|
||||
|
||||
mgmt_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb);
|
||||
if (mgmt_hdr.key_type == ATH9K_KEY_TYPE_CLEAR)
|
||||
mgmt_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID;
|
||||
else
|
||||
mgmt_hdr.keyix = tx_info->control.hw_key->hw_key_idx;
|
||||
|
||||
tx_fhdr = skb_push(skb, sizeof(mgmt_hdr));
|
||||
memcpy(tx_fhdr, (u8 *) &mgmt_hdr, sizeof(mgmt_hdr));
|
||||
epid = priv->mgmt_ep;
|
||||
}
|
||||
|
||||
return htc_send(priv->htc, skb, epid, &tx_ctl);
|
||||
}
|
||||
|
||||
void ath9k_tx_tasklet(unsigned long data)
|
||||
{
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
|
||||
struct ieee80211_sta *sta;
|
||||
struct ieee80211_hdr *hdr;
|
||||
struct ieee80211_tx_info *tx_info;
|
||||
struct sk_buff *skb = NULL;
|
||||
__le16 fc;
|
||||
|
||||
while ((skb = skb_dequeue(&priv->tx_queue)) != NULL) {
|
||||
|
||||
hdr = (struct ieee80211_hdr *) skb->data;
|
||||
fc = hdr->frame_control;
|
||||
tx_info = IEEE80211_SKB_CB(skb);
|
||||
sta = tx_info->control.sta;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
if (sta && conf_is_ht(&priv->hw->conf) &&
|
||||
(priv->op_flags & OP_TXAGGR)
|
||||
&& !(skb->protocol == cpu_to_be16(ETH_P_PAE))) {
|
||||
if (ieee80211_is_data_qos(fc)) {
|
||||
u8 *qc, tid;
|
||||
struct ath9k_htc_sta *ista;
|
||||
|
||||
qc = ieee80211_get_qos_ctl(hdr);
|
||||
tid = qc[0] & 0xf;
|
||||
ista = (struct ath9k_htc_sta *)sta->drv_priv;
|
||||
|
||||
if ((tid < ATH9K_HTC_MAX_TID) &&
|
||||
ista->tid_state[tid] == AGGR_STOP) {
|
||||
ieee80211_start_tx_ba_session(sta, tid);
|
||||
ista->tid_state[tid] = AGGR_PROGRESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
memset(&tx_info->status, 0, sizeof(tx_info->status));
|
||||
ieee80211_tx_status(priv->hw, skb);
|
||||
}
|
||||
}
|
||||
|
||||
void ath9k_htc_txep(void *drv_priv, struct sk_buff *skb,
|
||||
enum htc_endpoint_id ep_id, bool txok)
|
||||
{
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) drv_priv;
|
||||
struct ieee80211_tx_info *tx_info;
|
||||
|
||||
if (!skb)
|
||||
return;
|
||||
|
||||
if (ep_id == priv->mgmt_ep)
|
||||
skb_pull(skb, sizeof(struct tx_mgmt_hdr));
|
||||
else
|
||||
/* TODO: Check for cab/uapsd/data */
|
||||
skb_pull(skb, sizeof(struct tx_frame_hdr));
|
||||
|
||||
tx_info = IEEE80211_SKB_CB(skb);
|
||||
|
||||
if (txok)
|
||||
tx_info->flags |= IEEE80211_TX_STAT_ACK;
|
||||
|
||||
skb_queue_tail(&priv->tx_queue, skb);
|
||||
tasklet_schedule(&priv->tx_tasklet);
|
||||
}
|
||||
|
||||
int ath9k_tx_init(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
skb_queue_head_init(&priv->tx_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ath9k_tx_cleanup(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv,
|
||||
enum ath9k_tx_queue_subtype subtype)
|
||||
{
|
||||
struct ath_hw *ah = priv->ah;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
struct ath9k_tx_queue_info qi;
|
||||
int qnum;
|
||||
|
||||
memset(&qi, 0, sizeof(qi));
|
||||
|
||||
qi.tqi_subtype = subtype;
|
||||
qi.tqi_aifs = ATH9K_TXQ_USEDEFAULT;
|
||||
qi.tqi_cwmin = ATH9K_TXQ_USEDEFAULT;
|
||||
qi.tqi_cwmax = ATH9K_TXQ_USEDEFAULT;
|
||||
qi.tqi_physCompBuf = 0;
|
||||
qi.tqi_qflags = TXQ_FLAG_TXEOLINT_ENABLE | TXQ_FLAG_TXDESCINT_ENABLE;
|
||||
|
||||
qnum = ath9k_hw_setuptxqueue(priv->ah, ATH9K_TX_QUEUE_DATA, &qi);
|
||||
if (qnum == -1)
|
||||
return false;
|
||||
|
||||
if (qnum >= ARRAY_SIZE(priv->hwq_map)) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"qnum %u out of range, max %u!\n",
|
||||
qnum, (unsigned int)ARRAY_SIZE(priv->hwq_map));
|
||||
ath9k_hw_releasetxqueue(ah, qnum);
|
||||
return false;
|
||||
}
|
||||
|
||||
priv->hwq_map[subtype] = qnum;
|
||||
return true;
|
||||
}
|
||||
|
||||
/******/
|
||||
/* RX */
|
||||
/******/
|
||||
|
||||
void ath9k_host_rx_init(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
ath9k_hw_rxena(priv->ah);
|
||||
ath9k_cmn_opmode_init(priv->hw, priv->ah, priv->rxfilter);
|
||||
ath9k_hw_startpcureceive(priv->ah);
|
||||
priv->rx.last_rssi = ATH_RSSI_DUMMY_MARKER;
|
||||
}
|
||||
|
||||
static void ath9k_process_rate(struct ieee80211_hw *hw,
|
||||
struct ieee80211_rx_status *rxs,
|
||||
u8 rx_rate, u8 rs_flags)
|
||||
{
|
||||
struct ieee80211_supported_band *sband;
|
||||
enum ieee80211_band band;
|
||||
unsigned int i = 0;
|
||||
|
||||
if (rx_rate & 0x80) {
|
||||
/* HT rate */
|
||||
rxs->flag |= RX_FLAG_HT;
|
||||
if (rs_flags & ATH9K_RX_2040)
|
||||
rxs->flag |= RX_FLAG_40MHZ;
|
||||
if (rs_flags & ATH9K_RX_GI)
|
||||
rxs->flag |= RX_FLAG_SHORT_GI;
|
||||
rxs->rate_idx = rx_rate & 0x7f;
|
||||
return;
|
||||
}
|
||||
|
||||
band = hw->conf.channel->band;
|
||||
sband = hw->wiphy->bands[band];
|
||||
|
||||
for (i = 0; i < sband->n_bitrates; i++) {
|
||||
if (sband->bitrates[i].hw_value == rx_rate) {
|
||||
rxs->rate_idx = i;
|
||||
return;
|
||||
}
|
||||
if (sband->bitrates[i].hw_value_short == rx_rate) {
|
||||
rxs->rate_idx = i;
|
||||
rxs->flag |= RX_FLAG_SHORTPRE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool ath9k_rx_prepare(struct ath9k_htc_priv *priv,
|
||||
struct ath9k_htc_rxbuf *rxbuf,
|
||||
struct ieee80211_rx_status *rx_status)
|
||||
|
||||
{
|
||||
struct ieee80211_hdr *hdr;
|
||||
struct ieee80211_hw *hw = priv->hw;
|
||||
struct sk_buff *skb = rxbuf->skb;
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
int hdrlen, padpos, padsize;
|
||||
int last_rssi = ATH_RSSI_DUMMY_MARKER;
|
||||
__le16 fc;
|
||||
|
||||
hdr = (struct ieee80211_hdr *)skb->data;
|
||||
fc = hdr->frame_control;
|
||||
hdrlen = ieee80211_get_hdrlen_from_skb(skb);
|
||||
|
||||
padpos = ath9k_cmn_padpos(fc);
|
||||
|
||||
padsize = padpos & 3;
|
||||
if (padsize && skb->len >= padpos+padsize) {
|
||||
memmove(skb->data + padsize, skb->data, padpos);
|
||||
skb_pull(skb, padsize);
|
||||
}
|
||||
|
||||
memset(rx_status, 0, sizeof(struct ieee80211_rx_status));
|
||||
|
||||
if (rxbuf->rxstatus.rs_status != 0) {
|
||||
if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_CRC)
|
||||
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
|
||||
if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_PHY)
|
||||
goto rx_next;
|
||||
|
||||
if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT) {
|
||||
/* FIXME */
|
||||
} else if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_MIC) {
|
||||
if (ieee80211_is_ctl(fc))
|
||||
/*
|
||||
* Sometimes, we get invalid
|
||||
* MIC failures on valid control frames.
|
||||
* Remove these mic errors.
|
||||
*/
|
||||
rxbuf->rxstatus.rs_status &= ~ATH9K_RXERR_MIC;
|
||||
else
|
||||
rx_status->flag |= RX_FLAG_MMIC_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reject error frames with the exception of
|
||||
* decryption and MIC failures. For monitor mode,
|
||||
* we also ignore the CRC error.
|
||||
*/
|
||||
if (priv->ah->opmode == NL80211_IFTYPE_MONITOR) {
|
||||
if (rxbuf->rxstatus.rs_status &
|
||||
~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC |
|
||||
ATH9K_RXERR_CRC))
|
||||
goto rx_next;
|
||||
} else {
|
||||
if (rxbuf->rxstatus.rs_status &
|
||||
~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC)) {
|
||||
goto rx_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT)) {
|
||||
u8 keyix;
|
||||
keyix = rxbuf->rxstatus.rs_keyix;
|
||||
if (keyix != ATH9K_RXKEYIX_INVALID) {
|
||||
rx_status->flag |= RX_FLAG_DECRYPTED;
|
||||
} else if (ieee80211_has_protected(fc) &&
|
||||
skb->len >= hdrlen + 4) {
|
||||
keyix = skb->data[hdrlen + 3] >> 6;
|
||||
if (test_bit(keyix, common->keymap))
|
||||
rx_status->flag |= RX_FLAG_DECRYPTED;
|
||||
}
|
||||
}
|
||||
|
||||
ath9k_process_rate(hw, rx_status, rxbuf->rxstatus.rs_rate,
|
||||
rxbuf->rxstatus.rs_flags);
|
||||
|
||||
if (priv->op_flags & OP_ASSOCIATED) {
|
||||
if (rxbuf->rxstatus.rs_rssi != ATH9K_RSSI_BAD &&
|
||||
!rxbuf->rxstatus.rs_moreaggr)
|
||||
ATH_RSSI_LPF(priv->rx.last_rssi,
|
||||
rxbuf->rxstatus.rs_rssi);
|
||||
|
||||
last_rssi = priv->rx.last_rssi;
|
||||
|
||||
if (likely(last_rssi != ATH_RSSI_DUMMY_MARKER))
|
||||
rxbuf->rxstatus.rs_rssi = ATH_EP_RND(last_rssi,
|
||||
ATH_RSSI_EP_MULTIPLIER);
|
||||
|
||||
if (rxbuf->rxstatus.rs_rssi < 0)
|
||||
rxbuf->rxstatus.rs_rssi = 0;
|
||||
|
||||
if (ieee80211_is_beacon(fc))
|
||||
priv->ah->stats.avgbrssi = rxbuf->rxstatus.rs_rssi;
|
||||
}
|
||||
|
||||
rx_status->mactime = rxbuf->rxstatus.rs_tstamp;
|
||||
rx_status->band = hw->conf.channel->band;
|
||||
rx_status->freq = hw->conf.channel->center_freq;
|
||||
rx_status->signal = rxbuf->rxstatus.rs_rssi + ATH_DEFAULT_NOISE_FLOOR;
|
||||
rx_status->antenna = rxbuf->rxstatus.rs_antenna;
|
||||
rx_status->flag |= RX_FLAG_TSFT;
|
||||
|
||||
return true;
|
||||
|
||||
rx_next:
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: Handle FLUSH later on.
|
||||
*/
|
||||
void ath9k_rx_tasklet(unsigned long data)
|
||||
{
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
|
||||
struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL;
|
||||
struct ieee80211_rx_status rx_status;
|
||||
struct sk_buff *skb;
|
||||
unsigned long flags;
|
||||
|
||||
|
||||
do {
|
||||
spin_lock_irqsave(&priv->rx.rxbuflock, flags);
|
||||
list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
|
||||
if (tmp_buf->in_process) {
|
||||
rxbuf = tmp_buf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rxbuf == NULL) {
|
||||
spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rxbuf->skb)
|
||||
goto requeue;
|
||||
|
||||
if (!ath9k_rx_prepare(priv, rxbuf, &rx_status)) {
|
||||
dev_kfree_skb_any(rxbuf->skb);
|
||||
goto requeue;
|
||||
}
|
||||
|
||||
memcpy(IEEE80211_SKB_RXCB(rxbuf->skb), &rx_status,
|
||||
sizeof(struct ieee80211_rx_status));
|
||||
skb = rxbuf->skb;
|
||||
spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
|
||||
|
||||
ieee80211_rx(priv->hw, skb);
|
||||
|
||||
spin_lock_irqsave(&priv->rx.rxbuflock, flags);
|
||||
requeue:
|
||||
rxbuf->in_process = false;
|
||||
rxbuf->skb = NULL;
|
||||
list_move_tail(&rxbuf->list, &priv->rx.rxbuf);
|
||||
rxbuf = NULL;
|
||||
spin_unlock_irqrestore(&priv->rx.rxbuflock, flags);
|
||||
} while (1);
|
||||
|
||||
}
|
||||
|
||||
void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb,
|
||||
enum htc_endpoint_id ep_id)
|
||||
{
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)drv_priv;
|
||||
struct ath_hw *ah = priv->ah;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL;
|
||||
struct ath_htc_rx_status *rxstatus;
|
||||
u32 len = 0;
|
||||
|
||||
spin_lock(&priv->rx.rxbuflock);
|
||||
list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
|
||||
if (!tmp_buf->in_process) {
|
||||
rxbuf = tmp_buf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&priv->rx.rxbuflock);
|
||||
|
||||
if (rxbuf == NULL) {
|
||||
ath_print(common, ATH_DBG_ANY,
|
||||
"No free RX buffer\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
len = skb->len;
|
||||
if (len <= HTC_RX_FRAME_HEADER_SIZE) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Corrupted RX frame, dropping\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
rxstatus = (struct ath_htc_rx_status *)skb->data;
|
||||
|
||||
rxstatus->rs_tstamp = be64_to_cpu(rxstatus->rs_tstamp);
|
||||
rxstatus->rs_datalen = be16_to_cpu(rxstatus->rs_datalen);
|
||||
rxstatus->evm0 = be32_to_cpu(rxstatus->evm0);
|
||||
rxstatus->evm1 = be32_to_cpu(rxstatus->evm1);
|
||||
rxstatus->evm2 = be32_to_cpu(rxstatus->evm2);
|
||||
|
||||
if (rxstatus->rs_datalen - (len - HTC_RX_FRAME_HEADER_SIZE) != 0) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Corrupted RX data len, dropping "
|
||||
"(epid: %d, dlen: %d, skblen: %d)\n",
|
||||
ep_id, rxstatus->rs_datalen, len);
|
||||
goto err;
|
||||
}
|
||||
|
||||
spin_lock(&priv->rx.rxbuflock);
|
||||
memcpy(&rxbuf->rxstatus, rxstatus, HTC_RX_FRAME_HEADER_SIZE);
|
||||
skb_pull(skb, HTC_RX_FRAME_HEADER_SIZE);
|
||||
skb->len = rxstatus->rs_datalen;
|
||||
rxbuf->skb = skb;
|
||||
rxbuf->in_process = true;
|
||||
spin_unlock(&priv->rx.rxbuflock);
|
||||
|
||||
tasklet_schedule(&priv->rx_tasklet);
|
||||
return;
|
||||
err:
|
||||
dev_kfree_skb_any(skb);
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: Locking for cleanup/init */
|
||||
|
||||
void ath9k_rx_cleanup(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ath9k_htc_rxbuf *rxbuf, *tbuf;
|
||||
|
||||
list_for_each_entry_safe(rxbuf, tbuf, &priv->rx.rxbuf, list) {
|
||||
list_del(&rxbuf->list);
|
||||
if (rxbuf->skb)
|
||||
dev_kfree_skb_any(rxbuf->skb);
|
||||
kfree(rxbuf);
|
||||
}
|
||||
}
|
||||
|
||||
int ath9k_rx_init(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct ath_hw *ah = priv->ah;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
struct ath9k_htc_rxbuf *rxbuf;
|
||||
int i = 0;
|
||||
|
||||
INIT_LIST_HEAD(&priv->rx.rxbuf);
|
||||
spin_lock_init(&priv->rx.rxbuflock);
|
||||
|
||||
for (i = 0; i < ATH9K_HTC_RXBUF; i++) {
|
||||
rxbuf = kzalloc(sizeof(struct ath9k_htc_rxbuf), GFP_KERNEL);
|
||||
if (rxbuf == NULL) {
|
||||
ath_print(common, ATH_DBG_FATAL,
|
||||
"Unable to allocate RX buffers\n");
|
||||
goto err;
|
||||
}
|
||||
list_add_tail(&rxbuf->list, &priv->rx.rxbuf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
ath9k_rx_cleanup(priv);
|
||||
return -ENOMEM;
|
||||
}
|
463
drivers/net/wireless/ath/ath9k/htc_hst.c
Normal file
463
drivers/net/wireless/ath/ath9k/htc_hst.c
Normal file
@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
static int htc_issue_send(struct htc_target *target, struct sk_buff* skb,
|
||||
u16 len, u8 flags, u8 epid,
|
||||
struct ath9k_htc_tx_ctl *tx_ctl)
|
||||
{
|
||||
struct htc_frame_hdr *hdr;
|
||||
struct htc_endpoint *endpoint = &target->endpoint[epid];
|
||||
int status;
|
||||
|
||||
hdr = (struct htc_frame_hdr *)
|
||||
skb_push(skb, sizeof(struct htc_frame_hdr));
|
||||
hdr->endpoint_id = epid;
|
||||
hdr->flags = flags;
|
||||
hdr->payload_len = cpu_to_be16(len);
|
||||
|
||||
status = target->hif->send(target->hif_dev, endpoint->ul_pipeid, skb,
|
||||
tx_ctl);
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct htc_endpoint *get_next_avail_ep(struct htc_endpoint *endpoint)
|
||||
{
|
||||
enum htc_endpoint_id avail_epid;
|
||||
|
||||
for (avail_epid = ENDPOINT_MAX; avail_epid > ENDPOINT0; avail_epid--)
|
||||
if (endpoint[avail_epid].service_id == 0)
|
||||
return &endpoint[avail_epid];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static u8 service_to_ulpipe(u16 service_id)
|
||||
{
|
||||
switch (service_id) {
|
||||
case WMI_CONTROL_SVC:
|
||||
return 4;
|
||||
case WMI_BEACON_SVC:
|
||||
case WMI_CAB_SVC:
|
||||
case WMI_UAPSD_SVC:
|
||||
case WMI_MGMT_SVC:
|
||||
case WMI_DATA_VO_SVC:
|
||||
case WMI_DATA_VI_SVC:
|
||||
case WMI_DATA_BE_SVC:
|
||||
case WMI_DATA_BK_SVC:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 service_to_dlpipe(u16 service_id)
|
||||
{
|
||||
switch (service_id) {
|
||||
case WMI_CONTROL_SVC:
|
||||
return 3;
|
||||
case WMI_BEACON_SVC:
|
||||
case WMI_CAB_SVC:
|
||||
case WMI_UAPSD_SVC:
|
||||
case WMI_MGMT_SVC:
|
||||
case WMI_DATA_VO_SVC:
|
||||
case WMI_DATA_VI_SVC:
|
||||
case WMI_DATA_BE_SVC:
|
||||
case WMI_DATA_BK_SVC:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void htc_process_target_rdy(struct htc_target *target,
|
||||
void *buf)
|
||||
{
|
||||
struct htc_endpoint *endpoint;
|
||||
struct htc_ready_msg *htc_ready_msg = (struct htc_ready_msg *) buf;
|
||||
|
||||
target->credits = be16_to_cpu(htc_ready_msg->credits);
|
||||
target->credit_size = be16_to_cpu(htc_ready_msg->credit_size);
|
||||
|
||||
endpoint = &target->endpoint[ENDPOINT0];
|
||||
endpoint->service_id = HTC_CTRL_RSVD_SVC;
|
||||
endpoint->max_msglen = HTC_MAX_CONTROL_MESSAGE_LENGTH;
|
||||
complete(&target->target_wait);
|
||||
}
|
||||
|
||||
static void htc_process_conn_rsp(struct htc_target *target,
|
||||
struct htc_frame_hdr *htc_hdr)
|
||||
{
|
||||
struct htc_conn_svc_rspmsg *svc_rspmsg;
|
||||
struct htc_endpoint *endpoint, *tmp_endpoint = NULL;
|
||||
u16 service_id;
|
||||
u16 max_msglen;
|
||||
enum htc_endpoint_id epid, tepid;
|
||||
|
||||
svc_rspmsg = (struct htc_conn_svc_rspmsg *)
|
||||
((void *) htc_hdr + sizeof(struct htc_frame_hdr));
|
||||
|
||||
if (svc_rspmsg->status == HTC_SERVICE_SUCCESS) {
|
||||
epid = svc_rspmsg->endpoint_id;
|
||||
service_id = be16_to_cpu(svc_rspmsg->service_id);
|
||||
max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len);
|
||||
endpoint = &target->endpoint[epid];
|
||||
|
||||
for (tepid = ENDPOINT_MAX; tepid > ENDPOINT0; tepid--) {
|
||||
tmp_endpoint = &target->endpoint[tepid];
|
||||
if (tmp_endpoint->service_id == service_id) {
|
||||
tmp_endpoint->service_id = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tmp_endpoint)
|
||||
return;
|
||||
|
||||
endpoint->service_id = service_id;
|
||||
endpoint->max_txqdepth = tmp_endpoint->max_txqdepth;
|
||||
endpoint->ep_callbacks = tmp_endpoint->ep_callbacks;
|
||||
endpoint->ul_pipeid = tmp_endpoint->ul_pipeid;
|
||||
endpoint->dl_pipeid = tmp_endpoint->dl_pipeid;
|
||||
endpoint->max_msglen = max_msglen;
|
||||
target->conn_rsp_epid = epid;
|
||||
complete(&target->cmd_wait);
|
||||
} else {
|
||||
target->conn_rsp_epid = ENDPOINT_UNUSED;
|
||||
}
|
||||
}
|
||||
|
||||
static int htc_config_pipe_credits(struct htc_target *target)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct htc_config_pipe_msg *cp_msg;
|
||||
int ret, time_left;
|
||||
|
||||
skb = dev_alloc_skb(50 + sizeof(struct htc_frame_hdr));
|
||||
if (!skb) {
|
||||
dev_err(target->dev, "failed to allocate send buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
skb_reserve(skb, sizeof(struct htc_frame_hdr));
|
||||
|
||||
cp_msg = (struct htc_config_pipe_msg *)
|
||||
skb_put(skb, sizeof(struct htc_config_pipe_msg));
|
||||
|
||||
cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID);
|
||||
cp_msg->pipe_id = USB_WLAN_TX_PIPE;
|
||||
cp_msg->credits = 28;
|
||||
|
||||
target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS;
|
||||
|
||||
ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
|
||||
if (!time_left) {
|
||||
dev_err(target->dev, "HTC credit config timeout\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
dev_kfree_skb(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int htc_setup_complete(struct htc_target *target)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct htc_comp_msg *comp_msg;
|
||||
int ret = 0, time_left;
|
||||
|
||||
skb = dev_alloc_skb(50 + sizeof(struct htc_frame_hdr));
|
||||
if (!skb) {
|
||||
dev_err(target->dev, "failed to allocate send buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
skb_reserve(skb, sizeof(struct htc_frame_hdr));
|
||||
|
||||
comp_msg = (struct htc_comp_msg *)
|
||||
skb_put(skb, sizeof(struct htc_comp_msg));
|
||||
comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID);
|
||||
|
||||
target->htc_flags |= HTC_OP_START_WAIT;
|
||||
|
||||
ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
|
||||
if (!time_left) {
|
||||
dev_err(target->dev, "HTC start timeout\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
dev_kfree_skb(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* HTC APIs */
|
||||
|
||||
int htc_init(struct htc_target *target)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = htc_config_pipe_credits(target);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return htc_setup_complete(target);
|
||||
}
|
||||
|
||||
int htc_connect_service(struct htc_target *target,
|
||||
struct htc_service_connreq *service_connreq,
|
||||
enum htc_endpoint_id *conn_rsp_epid)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct htc_endpoint *endpoint;
|
||||
struct htc_conn_svc_msg *conn_msg;
|
||||
int ret, time_left;
|
||||
|
||||
/* Find an available endpoint */
|
||||
endpoint = get_next_avail_ep(target->endpoint);
|
||||
if (!endpoint) {
|
||||
dev_err(target->dev, "Endpoint is not available for"
|
||||
"service %d\n", service_connreq->service_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
endpoint->service_id = service_connreq->service_id;
|
||||
endpoint->max_txqdepth = service_connreq->max_send_qdepth;
|
||||
endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id);
|
||||
endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id);
|
||||
endpoint->ep_callbacks = service_connreq->ep_callbacks;
|
||||
|
||||
skb = dev_alloc_skb(sizeof(struct htc_conn_svc_msg) +
|
||||
sizeof(struct htc_frame_hdr));
|
||||
if (!skb) {
|
||||
dev_err(target->dev, "Failed to allocate buf to send"
|
||||
"service connect req\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
skb_reserve(skb, sizeof(struct htc_frame_hdr));
|
||||
|
||||
conn_msg = (struct htc_conn_svc_msg *)
|
||||
skb_put(skb, sizeof(struct htc_conn_svc_msg));
|
||||
conn_msg->service_id = cpu_to_be16(service_connreq->service_id);
|
||||
conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID);
|
||||
conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags);
|
||||
conn_msg->dl_pipeid = endpoint->dl_pipeid;
|
||||
conn_msg->ul_pipeid = endpoint->ul_pipeid;
|
||||
|
||||
ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0, NULL);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
time_left = wait_for_completion_timeout(&target->cmd_wait, HZ);
|
||||
if (!time_left) {
|
||||
dev_err(target->dev, "Service connection timeout for: %d\n",
|
||||
service_connreq->service_id);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
*conn_rsp_epid = target->conn_rsp_epid;
|
||||
return 0;
|
||||
err:
|
||||
dev_kfree_skb(skb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int htc_send(struct htc_target *target, struct sk_buff *skb,
|
||||
enum htc_endpoint_id epid, struct ath9k_htc_tx_ctl *tx_ctl)
|
||||
{
|
||||
return htc_issue_send(target, skb, skb->len, 0, epid, tx_ctl);
|
||||
}
|
||||
|
||||
void htc_stop(struct htc_target *target)
|
||||
{
|
||||
enum htc_endpoint_id epid;
|
||||
struct htc_endpoint *endpoint;
|
||||
|
||||
for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) {
|
||||
endpoint = &target->endpoint[epid];
|
||||
if (endpoint->service_id != 0)
|
||||
target->hif->stop(target->hif_dev, endpoint->ul_pipeid);
|
||||
}
|
||||
}
|
||||
|
||||
void htc_start(struct htc_target *target)
|
||||
{
|
||||
enum htc_endpoint_id epid;
|
||||
struct htc_endpoint *endpoint;
|
||||
|
||||
for (epid = ENDPOINT0; epid <= ENDPOINT_MAX; epid++) {
|
||||
endpoint = &target->endpoint[epid];
|
||||
if (endpoint->service_id != 0)
|
||||
target->hif->start(target->hif_dev,
|
||||
endpoint->ul_pipeid);
|
||||
}
|
||||
}
|
||||
|
||||
void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle,
|
||||
struct sk_buff *skb, bool txok)
|
||||
{
|
||||
struct htc_endpoint *endpoint;
|
||||
struct htc_frame_hdr *htc_hdr;
|
||||
|
||||
if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) {
|
||||
complete(&htc_handle->cmd_wait);
|
||||
htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS;
|
||||
}
|
||||
|
||||
if (htc_handle->htc_flags & HTC_OP_START_WAIT) {
|
||||
complete(&htc_handle->cmd_wait);
|
||||
htc_handle->htc_flags &= ~HTC_OP_START_WAIT;
|
||||
}
|
||||
|
||||
if (skb) {
|
||||
htc_hdr = (struct htc_frame_hdr *) skb->data;
|
||||
endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id];
|
||||
skb_pull(skb, sizeof(struct htc_frame_hdr));
|
||||
|
||||
if (endpoint->ep_callbacks.tx) {
|
||||
endpoint->ep_callbacks.tx(htc_handle->drv_priv, skb,
|
||||
htc_hdr->endpoint_id, txok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* HTC Messages are handled directly here and the obtained SKB
|
||||
* is freed.
|
||||
*
|
||||
* Sevice messages (Data, WMI) passed to the corresponding
|
||||
* endpoint RX handlers, which have to free the SKB.
|
||||
*/
|
||||
void ath9k_htc_rx_msg(struct htc_target *htc_handle,
|
||||
struct sk_buff *skb, u32 len, u8 pipe_id)
|
||||
{
|
||||
struct htc_frame_hdr *htc_hdr;
|
||||
enum htc_endpoint_id epid;
|
||||
struct htc_endpoint *endpoint;
|
||||
u16 *msg_id;
|
||||
|
||||
if (!htc_handle || !skb)
|
||||
return;
|
||||
|
||||
htc_hdr = (struct htc_frame_hdr *) skb->data;
|
||||
epid = htc_hdr->endpoint_id;
|
||||
|
||||
if (epid >= ENDPOINT_MAX) {
|
||||
dev_kfree_skb_any(skb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (epid == ENDPOINT0) {
|
||||
|
||||
/* Handle trailer */
|
||||
if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) {
|
||||
if (be32_to_cpu(*(u32 *) skb->data) == 0x00C60000)
|
||||
/* Move past the Watchdog pattern */
|
||||
htc_hdr = (struct htc_frame_hdr *) skb->data + 4;
|
||||
}
|
||||
|
||||
/* Get the message ID */
|
||||
msg_id = (u16 *) ((void *) htc_hdr +
|
||||
sizeof(struct htc_frame_hdr));
|
||||
|
||||
/* Now process HTC messages */
|
||||
switch (be16_to_cpu(*msg_id)) {
|
||||
case HTC_MSG_READY_ID:
|
||||
htc_process_target_rdy(htc_handle, htc_hdr);
|
||||
break;
|
||||
case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID:
|
||||
htc_process_conn_rsp(htc_handle, htc_hdr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_kfree_skb_any(skb);
|
||||
|
||||
} else {
|
||||
if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER)
|
||||
skb_trim(skb, len - htc_hdr->control[0]);
|
||||
|
||||
skb_pull(skb, sizeof(struct htc_frame_hdr));
|
||||
|
||||
endpoint = &htc_handle->endpoint[epid];
|
||||
if (endpoint->ep_callbacks.rx)
|
||||
endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv,
|
||||
skb, epid);
|
||||
}
|
||||
}
|
||||
|
||||
struct htc_target *ath9k_htc_hw_alloc(void *hif_handle)
|
||||
{
|
||||
struct htc_target *target;
|
||||
|
||||
target = kzalloc(sizeof(struct htc_target), GFP_KERNEL);
|
||||
if (!target)
|
||||
printk(KERN_ERR "Unable to allocate memory for"
|
||||
"target device\n");
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
void ath9k_htc_hw_free(struct htc_target *htc)
|
||||
{
|
||||
kfree(htc);
|
||||
}
|
||||
|
||||
int ath9k_htc_hw_init(struct ath9k_htc_hif *hif, struct htc_target *target,
|
||||
void *hif_handle, struct device *dev, u16 devid,
|
||||
enum ath9k_hif_transports transport)
|
||||
{
|
||||
struct htc_endpoint *endpoint;
|
||||
int err = 0;
|
||||
|
||||
init_completion(&target->target_wait);
|
||||
init_completion(&target->cmd_wait);
|
||||
|
||||
target->hif = hif;
|
||||
target->hif_dev = hif_handle;
|
||||
target->dev = dev;
|
||||
|
||||
/* Assign control endpoint pipe IDs */
|
||||
endpoint = &target->endpoint[ENDPOINT0];
|
||||
endpoint->ul_pipeid = hif->control_ul_pipe;
|
||||
endpoint->dl_pipeid = hif->control_dl_pipe;
|
||||
|
||||
err = ath9k_htc_probe_device(target, dev, devid);
|
||||
if (err) {
|
||||
printk(KERN_ERR "Failed to initialize the device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug)
|
||||
{
|
||||
if (target)
|
||||
ath9k_htc_disconnect_device(target, hot_unplug);
|
||||
}
|
246
drivers/net/wireless/ath/ath9k/htc_hst.h
Normal file
246
drivers/net/wireless/ath/ath9k/htc_hst.h
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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_HST_H
|
||||
#define HTC_HST_H
|
||||
|
||||
struct ath9k_htc_priv;
|
||||
struct htc_target;
|
||||
struct ath9k_htc_tx_ctl;
|
||||
|
||||
enum ath9k_hif_transports {
|
||||
ATH9K_HIF_USB,
|
||||
};
|
||||
|
||||
struct ath9k_htc_hif {
|
||||
struct list_head list;
|
||||
const enum ath9k_hif_transports transport;
|
||||
const char *name;
|
||||
|
||||
u8 control_dl_pipe;
|
||||
u8 control_ul_pipe;
|
||||
|
||||
void (*start) (void *hif_handle, u8 pipe);
|
||||
void (*stop) (void *hif_handle, u8 pipe);
|
||||
int (*send) (void *hif_handle, u8 pipe, struct sk_buff *buf,
|
||||
struct ath9k_htc_tx_ctl *tx_ctl);
|
||||
};
|
||||
|
||||
enum htc_endpoint_id {
|
||||
ENDPOINT_UNUSED = -1,
|
||||
ENDPOINT0 = 0,
|
||||
ENDPOINT1 = 1,
|
||||
ENDPOINT2 = 2,
|
||||
ENDPOINT3 = 3,
|
||||
ENDPOINT4 = 4,
|
||||
ENDPOINT5 = 5,
|
||||
ENDPOINT6 = 6,
|
||||
ENDPOINT7 = 7,
|
||||
ENDPOINT8 = 8,
|
||||
ENDPOINT_MAX = 22
|
||||
};
|
||||
|
||||
/* Htc frame hdr flags */
|
||||
#define HTC_FLAGS_RECV_TRAILER (1 << 1)
|
||||
|
||||
struct htc_frame_hdr {
|
||||
u8 endpoint_id;
|
||||
u8 flags;
|
||||
u16 payload_len;
|
||||
u8 control[4];
|
||||
} __packed;
|
||||
|
||||
struct htc_ready_msg {
|
||||
u16 message_id;
|
||||
u16 credits;
|
||||
u16 credit_size;
|
||||
u8 max_endpoints;
|
||||
u8 pad;
|
||||
} __packed;
|
||||
|
||||
struct htc_config_pipe_msg {
|
||||
u16 message_id;
|
||||
u8 pipe_id;
|
||||
u8 credits;
|
||||
} __packed;
|
||||
|
||||
struct htc_packet {
|
||||
void *pktcontext;
|
||||
u8 *buf;
|
||||
u8 *buf_payload;
|
||||
u32 buflen;
|
||||
u32 payload_len;
|
||||
|
||||
int endpoint;
|
||||
int status;
|
||||
|
||||
void *context;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct htc_ep_callbacks {
|
||||
void *priv;
|
||||
void (*tx) (void *, struct sk_buff *, enum htc_endpoint_id, bool txok);
|
||||
void (*rx) (void *, struct sk_buff *, enum htc_endpoint_id);
|
||||
};
|
||||
|
||||
#define HTC_TX_QUEUE_SIZE 256
|
||||
|
||||
struct htc_txq {
|
||||
struct sk_buff *buf[HTC_TX_QUEUE_SIZE];
|
||||
u32 txqdepth;
|
||||
u16 txbuf_cnt;
|
||||
u16 txq_head;
|
||||
u16 txq_tail;
|
||||
};
|
||||
|
||||
struct htc_endpoint {
|
||||
u16 service_id;
|
||||
|
||||
struct htc_ep_callbacks ep_callbacks;
|
||||
struct htc_txq htc_txq;
|
||||
u32 max_txqdepth;
|
||||
int max_msglen;
|
||||
|
||||
u8 ul_pipeid;
|
||||
u8 dl_pipeid;
|
||||
};
|
||||
|
||||
#define HTC_MAX_CONTROL_MESSAGE_LENGTH 255
|
||||
#define HTC_CONTROL_BUFFER_SIZE \
|
||||
(HTC_MAX_CONTROL_MESSAGE_LENGTH + sizeof(struct htc_frame_hdr))
|
||||
|
||||
#define NUM_CONTROL_BUFFERS 8
|
||||
#define HST_ENDPOINT_MAX 8
|
||||
|
||||
struct htc_control_buf {
|
||||
struct htc_packet htc_pkt;
|
||||
u8 buf[HTC_CONTROL_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
#define HTC_OP_START_WAIT BIT(0)
|
||||
#define HTC_OP_CONFIG_PIPE_CREDITS BIT(1)
|
||||
|
||||
struct htc_target {
|
||||
void *hif_dev;
|
||||
struct ath9k_htc_priv *drv_priv;
|
||||
struct device *dev;
|
||||
struct ath9k_htc_hif *hif;
|
||||
struct htc_endpoint endpoint[HST_ENDPOINT_MAX];
|
||||
struct completion target_wait;
|
||||
struct completion cmd_wait;
|
||||
struct list_head list;
|
||||
enum htc_endpoint_id conn_rsp_epid;
|
||||
u16 credits;
|
||||
u16 credit_size;
|
||||
u8 htc_flags;
|
||||
};
|
||||
|
||||
enum htc_msg_id {
|
||||
HTC_MSG_READY_ID = 1,
|
||||
HTC_MSG_CONNECT_SERVICE_ID,
|
||||
HTC_MSG_CONNECT_SERVICE_RESPONSE_ID,
|
||||
HTC_MSG_SETUP_COMPLETE_ID,
|
||||
HTC_MSG_CONFIG_PIPE_ID,
|
||||
HTC_MSG_CONFIG_PIPE_RESPONSE_ID,
|
||||
};
|
||||
|
||||
struct htc_service_connreq {
|
||||
u16 service_id;
|
||||
u16 con_flags;
|
||||
u32 max_send_qdepth;
|
||||
struct htc_ep_callbacks ep_callbacks;
|
||||
};
|
||||
|
||||
/* Current service IDs */
|
||||
|
||||
enum htc_service_group_ids{
|
||||
RSVD_SERVICE_GROUP = 0,
|
||||
WMI_SERVICE_GROUP = 1,
|
||||
|
||||
HTC_SERVICE_GROUP_LAST = 255
|
||||
};
|
||||
|
||||
#define MAKE_SERVICE_ID(group, index) \
|
||||
(int)(((int)group << 8) | (int)(index))
|
||||
|
||||
/* NOTE: service ID of 0x0000 is reserved and should never be used */
|
||||
#define HTC_CTRL_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 1)
|
||||
#define HTC_LOOPBACK_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 2)
|
||||
|
||||
#define WMI_CONTROL_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 0)
|
||||
#define WMI_BEACON_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 1)
|
||||
#define WMI_CAB_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 2)
|
||||
#define WMI_UAPSD_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 3)
|
||||
#define WMI_MGMT_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 4)
|
||||
#define WMI_DATA_VO_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 5)
|
||||
#define WMI_DATA_VI_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 6)
|
||||
#define WMI_DATA_BE_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 7)
|
||||
#define WMI_DATA_BK_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 8)
|
||||
|
||||
struct htc_conn_svc_msg {
|
||||
u16 msg_id;
|
||||
u16 service_id;
|
||||
u16 con_flags;
|
||||
u8 dl_pipeid;
|
||||
u8 ul_pipeid;
|
||||
u8 svc_meta_len;
|
||||
u8 pad;
|
||||
} __packed;
|
||||
|
||||
/* connect response status codes */
|
||||
#define HTC_SERVICE_SUCCESS 0
|
||||
#define HTC_SERVICE_NOT_FOUND 1
|
||||
#define HTC_SERVICE_FAILED 2
|
||||
#define HTC_SERVICE_NO_RESOURCES 3
|
||||
#define HTC_SERVICE_NO_MORE_EP 4
|
||||
|
||||
struct htc_conn_svc_rspmsg {
|
||||
u16 msg_id;
|
||||
u16 service_id;
|
||||
u8 status;
|
||||
u8 endpoint_id;
|
||||
u16 max_msg_len;
|
||||
u8 svc_meta_len;
|
||||
u8 pad;
|
||||
} __packed;
|
||||
|
||||
struct htc_comp_msg {
|
||||
u16 msg_id;
|
||||
} __packed;
|
||||
|
||||
int htc_init(struct htc_target *target);
|
||||
int htc_connect_service(struct htc_target *target,
|
||||
struct htc_service_connreq *service_connreq,
|
||||
enum htc_endpoint_id *conn_rsp_eid);
|
||||
int htc_send(struct htc_target *target, struct sk_buff *skb,
|
||||
enum htc_endpoint_id eid, struct ath9k_htc_tx_ctl *tx_ctl);
|
||||
void htc_stop(struct htc_target *target);
|
||||
void htc_start(struct htc_target *target);
|
||||
|
||||
void ath9k_htc_rx_msg(struct htc_target *htc_handle,
|
||||
struct sk_buff *skb, u32 len, u8 pipe_id);
|
||||
void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle,
|
||||
struct sk_buff *skb, bool txok);
|
||||
|
||||
struct htc_target *ath9k_htc_hw_alloc(void *hif_handle);
|
||||
void ath9k_htc_hw_free(struct htc_target *htc);
|
||||
int ath9k_htc_hw_init(struct ath9k_htc_hif *hif, struct htc_target *target,
|
||||
void *hif_handle, struct device *dev, u16 devid,
|
||||
enum ath9k_hif_transports transport);
|
||||
void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug);
|
||||
|
||||
#endif /* HTC_HST_H */
|
@ -150,6 +150,32 @@ struct ath_rx_status {
|
||||
u32 evm2;
|
||||
};
|
||||
|
||||
struct ath_htc_rx_status {
|
||||
u64 rs_tstamp;
|
||||
u16 rs_datalen;
|
||||
u8 rs_status;
|
||||
u8 rs_phyerr;
|
||||
int8_t rs_rssi;
|
||||
int8_t rs_rssi_ctl0;
|
||||
int8_t rs_rssi_ctl1;
|
||||
int8_t rs_rssi_ctl2;
|
||||
int8_t rs_rssi_ext0;
|
||||
int8_t rs_rssi_ext1;
|
||||
int8_t rs_rssi_ext2;
|
||||
u8 rs_keyix;
|
||||
u8 rs_rate;
|
||||
u8 rs_antenna;
|
||||
u8 rs_more;
|
||||
u8 rs_isaggr;
|
||||
u8 rs_moreaggr;
|
||||
u8 rs_num_delims;
|
||||
u8 rs_flags;
|
||||
u8 rs_dummy;
|
||||
u32 evm0;
|
||||
u32 evm1;
|
||||
u32 evm2;
|
||||
};
|
||||
|
||||
#define ATH9K_RXERR_CRC 0x01
|
||||
#define ATH9K_RXERR_PHY 0x02
|
||||
#define ATH9K_RXERR_FIFO 0x04
|
||||
|
319
drivers/net/wireless/ath/ath9k/wmi.c
Normal file
319
drivers/net/wireless/ath/ath9k/wmi.c
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 "htc.h"
|
||||
|
||||
static const char *wmi_cmd_to_name(enum wmi_cmd_id wmi_cmd)
|
||||
{
|
||||
switch (wmi_cmd) {
|
||||
case WMI_ECHO_CMDID:
|
||||
return "WMI_ECHO_CMDID";
|
||||
case WMI_ACCESS_MEMORY_CMDID:
|
||||
return "WMI_ACCESS_MEMORY_CMDID";
|
||||
case WMI_DISABLE_INTR_CMDID:
|
||||
return "WMI_DISABLE_INTR_CMDID";
|
||||
case WMI_ENABLE_INTR_CMDID:
|
||||
return "WMI_ENABLE_INTR_CMDID";
|
||||
case WMI_RX_LINK_CMDID:
|
||||
return "WMI_RX_LINK_CMDID";
|
||||
case WMI_ATH_INIT_CMDID:
|
||||
return "WMI_ATH_INIT_CMDID";
|
||||
case WMI_ABORT_TXQ_CMDID:
|
||||
return "WMI_ABORT_TXQ_CMDID";
|
||||
case WMI_STOP_TX_DMA_CMDID:
|
||||
return "WMI_STOP_TX_DMA_CMDID";
|
||||
case WMI_STOP_DMA_RECV_CMDID:
|
||||
return "WMI_STOP_DMA_RECV_CMDID";
|
||||
case WMI_ABORT_TX_DMA_CMDID:
|
||||
return "WMI_ABORT_TX_DMA_CMDID";
|
||||
case WMI_DRAIN_TXQ_CMDID:
|
||||
return "WMI_DRAIN_TXQ_CMDID";
|
||||
case WMI_DRAIN_TXQ_ALL_CMDID:
|
||||
return "WMI_DRAIN_TXQ_ALL_CMDID";
|
||||
case WMI_START_RECV_CMDID:
|
||||
return "WMI_START_RECV_CMDID";
|
||||
case WMI_STOP_RECV_CMDID:
|
||||
return "WMI_STOP_RECV_CMDID";
|
||||
case WMI_FLUSH_RECV_CMDID:
|
||||
return "WMI_FLUSH_RECV_CMDID";
|
||||
case WMI_SET_MODE_CMDID:
|
||||
return "WMI_SET_MODE_CMDID";
|
||||
case WMI_RESET_CMDID:
|
||||
return "WMI_RESET_CMDID";
|
||||
case WMI_NODE_CREATE_CMDID:
|
||||
return "WMI_NODE_CREATE_CMDID";
|
||||
case WMI_NODE_REMOVE_CMDID:
|
||||
return "WMI_NODE_REMOVE_CMDID";
|
||||
case WMI_VAP_REMOVE_CMDID:
|
||||
return "WMI_VAP_REMOVE_CMDID";
|
||||
case WMI_VAP_CREATE_CMDID:
|
||||
return "WMI_VAP_CREATE_CMDID";
|
||||
case WMI_BEACON_UPDATE_CMDID:
|
||||
return "WMI_BEACON_UPDATE_CMDID";
|
||||
case WMI_REG_READ_CMDID:
|
||||
return "WMI_REG_READ_CMDID";
|
||||
case WMI_REG_WRITE_CMDID:
|
||||
return "WMI_REG_WRITE_CMDID";
|
||||
case WMI_RC_STATE_CHANGE_CMDID:
|
||||
return "WMI_RC_STATE_CHANGE_CMDID";
|
||||
case WMI_RC_RATE_UPDATE_CMDID:
|
||||
return "WMI_RC_RATE_UPDATE_CMDID";
|
||||
case WMI_DEBUG_INFO_CMDID:
|
||||
return "WMI_DEBUG_INFO_CMDID";
|
||||
case WMI_HOST_ATTACH:
|
||||
return "WMI_HOST_ATTACH";
|
||||
case WMI_TARGET_IC_UPDATE_CMDID:
|
||||
return "WMI_TARGET_IC_UPDATE_CMDID";
|
||||
case WMI_TGT_STATS_CMDID:
|
||||
return "WMI_TGT_STATS_CMDID";
|
||||
case WMI_TX_AGGR_ENABLE_CMDID:
|
||||
return "WMI_TX_AGGR_ENABLE_CMDID";
|
||||
case WMI_TGT_DETACH_CMDID:
|
||||
return "WMI_TGT_DETACH_CMDID";
|
||||
case WMI_TGT_TXQ_ENABLE_CMDID:
|
||||
return "WMI_TGT_TXQ_ENABLE_CMDID";
|
||||
}
|
||||
|
||||
return "Bogus";
|
||||
}
|
||||
|
||||
struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct wmi *wmi;
|
||||
|
||||
wmi = kzalloc(sizeof(struct wmi), GFP_KERNEL);
|
||||
if (!wmi)
|
||||
return NULL;
|
||||
|
||||
wmi->drv_priv = priv;
|
||||
wmi->stopped = false;
|
||||
mutex_init(&wmi->op_mutex);
|
||||
init_completion(&wmi->cmd_wait);
|
||||
|
||||
return wmi;
|
||||
}
|
||||
|
||||
void ath9k_deinit_wmi(struct ath9k_htc_priv *priv)
|
||||
{
|
||||
struct wmi *wmi = priv->wmi;
|
||||
|
||||
mutex_lock(&wmi->op_mutex);
|
||||
wmi->stopped = true;
|
||||
mutex_unlock(&wmi->op_mutex);
|
||||
|
||||
kfree(priv->wmi);
|
||||
}
|
||||
|
||||
void ath9k_wmi_tasklet(unsigned long data)
|
||||
{
|
||||
struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
|
||||
struct ath_common *common = ath9k_hw_common(priv->ah);
|
||||
struct wmi_cmd_hdr *hdr;
|
||||
struct wmi_swba *swba_hdr;
|
||||
enum wmi_event_id event;
|
||||
struct sk_buff *skb;
|
||||
void *wmi_event;
|
||||
unsigned long flags;
|
||||
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
||||
u32 txrate;
|
||||
#endif
|
||||
|
||||
spin_lock_irqsave(&priv->wmi->wmi_lock, flags);
|
||||
skb = priv->wmi->wmi_skb;
|
||||
spin_unlock_irqrestore(&priv->wmi->wmi_lock, flags);
|
||||
|
||||
hdr = (struct wmi_cmd_hdr *) skb->data;
|
||||
event = be16_to_cpu(hdr->command_id);
|
||||
wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr));
|
||||
|
||||
ath_print(common, ATH_DBG_WMI,
|
||||
"WMI Event: 0x%x\n", event);
|
||||
|
||||
switch (event) {
|
||||
case WMI_TGT_RDY_EVENTID:
|
||||
break;
|
||||
case WMI_SWBA_EVENTID:
|
||||
swba_hdr = (struct wmi_swba *) wmi_event;
|
||||
ath9k_htc_swba(priv, swba_hdr->beacon_pending);
|
||||
break;
|
||||
case WMI_FATAL_EVENTID:
|
||||
break;
|
||||
case WMI_TXTO_EVENTID:
|
||||
break;
|
||||
case WMI_BMISS_EVENTID:
|
||||
break;
|
||||
case WMI_WLAN_TXCOMP_EVENTID:
|
||||
break;
|
||||
case WMI_DELBA_EVENTID:
|
||||
break;
|
||||
case WMI_TXRATE_EVENTID:
|
||||
#ifdef CONFIG_ATH9K_HTC_DEBUGFS
|
||||
txrate = ((struct wmi_event_txrate *)wmi_event)->txrate;
|
||||
priv->debug.txrate = be32_to_cpu(txrate);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb)
|
||||
{
|
||||
skb_pull(skb, sizeof(struct wmi_cmd_hdr));
|
||||
|
||||
if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0)
|
||||
memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len);
|
||||
|
||||
complete(&wmi->cmd_wait);
|
||||
}
|
||||
|
||||
static void ath9k_wmi_ctrl_rx(void *priv, struct sk_buff *skb,
|
||||
enum htc_endpoint_id epid)
|
||||
{
|
||||
struct wmi *wmi = (struct wmi *) priv;
|
||||
struct wmi_cmd_hdr *hdr;
|
||||
u16 cmd_id;
|
||||
|
||||
if (unlikely(wmi->stopped))
|
||||
goto free_skb;
|
||||
|
||||
hdr = (struct wmi_cmd_hdr *) skb->data;
|
||||
cmd_id = be16_to_cpu(hdr->command_id);
|
||||
|
||||
if (cmd_id & 0x1000) {
|
||||
spin_lock(&wmi->wmi_lock);
|
||||
wmi->wmi_skb = skb;
|
||||
spin_unlock(&wmi->wmi_lock);
|
||||
tasklet_schedule(&wmi->drv_priv->wmi_tasklet);
|
||||
return;
|
||||
}
|
||||
|
||||
/* WMI command response */
|
||||
ath9k_wmi_rsp_callback(wmi, skb);
|
||||
|
||||
free_skb:
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
static void ath9k_wmi_ctrl_tx(void *priv, struct sk_buff *skb,
|
||||
enum htc_endpoint_id epid, bool txok)
|
||||
{
|
||||
dev_kfree_skb_any(skb);
|
||||
}
|
||||
|
||||
int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi,
|
||||
enum htc_endpoint_id *wmi_ctrl_epid)
|
||||
{
|
||||
struct htc_service_connreq connect;
|
||||
int ret;
|
||||
|
||||
wmi->htc = htc;
|
||||
|
||||
memset(&connect, 0, sizeof(connect));
|
||||
|
||||
connect.ep_callbacks.priv = wmi;
|
||||
connect.ep_callbacks.tx = ath9k_wmi_ctrl_tx;
|
||||
connect.ep_callbacks.rx = ath9k_wmi_ctrl_rx;
|
||||
connect.service_id = WMI_CONTROL_SVC;
|
||||
|
||||
ret = htc_connect_service(htc, &connect, &wmi->ctrl_epid);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*wmi_ctrl_epid = wmi->ctrl_epid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ath9k_wmi_cmd_issue(struct wmi *wmi,
|
||||
struct sk_buff *skb,
|
||||
enum wmi_cmd_id cmd, u16 len)
|
||||
{
|
||||
struct wmi_cmd_hdr *hdr;
|
||||
|
||||
hdr = (struct wmi_cmd_hdr *) skb_push(skb, sizeof(struct wmi_cmd_hdr));
|
||||
hdr->command_id = cpu_to_be16(cmd);
|
||||
hdr->seq_no = cpu_to_be16(++wmi->tx_seq_id);
|
||||
|
||||
return htc_send(wmi->htc, skb, wmi->ctrl_epid, NULL);
|
||||
}
|
||||
|
||||
int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id,
|
||||
u8 *cmd_buf, u32 cmd_len,
|
||||
u8 *rsp_buf, u32 rsp_len,
|
||||
u32 timeout)
|
||||
{
|
||||
struct ath_hw *ah = wmi->drv_priv->ah;
|
||||
struct ath_common *common = ath9k_hw_common(ah);
|
||||
u16 headroom = sizeof(struct htc_frame_hdr) +
|
||||
sizeof(struct wmi_cmd_hdr);
|
||||
struct sk_buff *skb;
|
||||
u8 *data;
|
||||
int time_left, ret = 0;
|
||||
|
||||
if (!wmi)
|
||||
return -EINVAL;
|
||||
|
||||
skb = dev_alloc_skb(headroom + cmd_len);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
skb_reserve(skb, headroom);
|
||||
|
||||
if (cmd_len != 0 && cmd_buf != NULL) {
|
||||
data = (u8 *) skb_put(skb, cmd_len);
|
||||
memcpy(data, cmd_buf, cmd_len);
|
||||
}
|
||||
|
||||
mutex_lock(&wmi->op_mutex);
|
||||
|
||||
/* check if wmi stopped flag is set */
|
||||
if (unlikely(wmi->stopped)) {
|
||||
ret = -EPROTO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* record the rsp buffer and length */
|
||||
wmi->cmd_rsp_buf = rsp_buf;
|
||||
wmi->cmd_rsp_len = rsp_len;
|
||||
|
||||
ret = ath9k_wmi_cmd_issue(wmi, skb, cmd_id, cmd_len);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
time_left = wait_for_completion_timeout(&wmi->cmd_wait, timeout);
|
||||
if (!time_left) {
|
||||
ath_print(common, ATH_DBG_WMI,
|
||||
"Timeout waiting for WMI command: %s\n",
|
||||
wmi_cmd_to_name(cmd_id));
|
||||
mutex_unlock(&wmi->op_mutex);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
mutex_unlock(&wmi->op_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
ath_print(common, ATH_DBG_WMI,
|
||||
"WMI failure for: %s\n", wmi_cmd_to_name(cmd_id));
|
||||
mutex_unlock(&wmi->op_mutex);
|
||||
dev_kfree_skb_any(skb);
|
||||
|
||||
return ret;
|
||||
}
|
126
drivers/net/wireless/ath/ath9k/wmi.h
Normal file
126
drivers/net/wireless/ath/ath9k/wmi.h
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2010 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 WMI_H
|
||||
#define WMI_H
|
||||
|
||||
|
||||
struct wmi_event_txrate {
|
||||
u32 txrate;
|
||||
struct {
|
||||
u8 rssi_thresh;
|
||||
u8 per;
|
||||
} rc_stats;
|
||||
} __packed;
|
||||
|
||||
struct wmi_cmd_hdr {
|
||||
u16 command_id;
|
||||
u16 seq_no;
|
||||
} __packed;
|
||||
|
||||
struct wmi_swba {
|
||||
u8 beacon_pending;
|
||||
} __packed;
|
||||
|
||||
enum wmi_cmd_id {
|
||||
WMI_ECHO_CMDID = 0x0001,
|
||||
WMI_ACCESS_MEMORY_CMDID,
|
||||
|
||||
/* Commands to Target */
|
||||
WMI_DISABLE_INTR_CMDID,
|
||||
WMI_ENABLE_INTR_CMDID,
|
||||
WMI_RX_LINK_CMDID,
|
||||
WMI_ATH_INIT_CMDID,
|
||||
WMI_ABORT_TXQ_CMDID,
|
||||
WMI_STOP_TX_DMA_CMDID,
|
||||
WMI_STOP_DMA_RECV_CMDID,
|
||||
WMI_ABORT_TX_DMA_CMDID,
|
||||
WMI_DRAIN_TXQ_CMDID,
|
||||
WMI_DRAIN_TXQ_ALL_CMDID,
|
||||
WMI_START_RECV_CMDID,
|
||||
WMI_STOP_RECV_CMDID,
|
||||
WMI_FLUSH_RECV_CMDID,
|
||||
WMI_SET_MODE_CMDID,
|
||||
WMI_RESET_CMDID,
|
||||
WMI_NODE_CREATE_CMDID,
|
||||
WMI_NODE_REMOVE_CMDID,
|
||||
WMI_VAP_REMOVE_CMDID,
|
||||
WMI_VAP_CREATE_CMDID,
|
||||
WMI_BEACON_UPDATE_CMDID,
|
||||
WMI_REG_READ_CMDID,
|
||||
WMI_REG_WRITE_CMDID,
|
||||
WMI_RC_STATE_CHANGE_CMDID,
|
||||
WMI_RC_RATE_UPDATE_CMDID,
|
||||
WMI_DEBUG_INFO_CMDID,
|
||||
WMI_HOST_ATTACH,
|
||||
WMI_TARGET_IC_UPDATE_CMDID,
|
||||
WMI_TGT_STATS_CMDID,
|
||||
WMI_TX_AGGR_ENABLE_CMDID,
|
||||
WMI_TGT_DETACH_CMDID,
|
||||
WMI_TGT_TXQ_ENABLE_CMDID,
|
||||
};
|
||||
|
||||
enum wmi_event_id {
|
||||
WMI_TGT_RDY_EVENTID = 0x1001,
|
||||
WMI_SWBA_EVENTID,
|
||||
WMI_FATAL_EVENTID,
|
||||
WMI_TXTO_EVENTID,
|
||||
WMI_BMISS_EVENTID,
|
||||
WMI_WLAN_TXCOMP_EVENTID,
|
||||
WMI_DELBA_EVENTID,
|
||||
WMI_TXRATE_EVENTID,
|
||||
};
|
||||
|
||||
struct wmi {
|
||||
struct ath9k_htc_priv *drv_priv;
|
||||
struct htc_target *htc;
|
||||
enum htc_endpoint_id ctrl_epid;
|
||||
struct mutex op_mutex;
|
||||
struct completion cmd_wait;
|
||||
u16 tx_seq_id;
|
||||
u8 *cmd_rsp_buf;
|
||||
u32 cmd_rsp_len;
|
||||
bool stopped;
|
||||
|
||||
struct sk_buff *wmi_skb;
|
||||
spinlock_t wmi_lock;
|
||||
};
|
||||
|
||||
struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv);
|
||||
void ath9k_deinit_wmi(struct ath9k_htc_priv *priv);
|
||||
int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi,
|
||||
enum htc_endpoint_id *wmi_ctrl_epid);
|
||||
int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id,
|
||||
u8 *cmd_buf, u32 cmd_len,
|
||||
u8 *rsp_buf, u32 rsp_len,
|
||||
u32 timeout);
|
||||
void ath9k_wmi_tasklet(unsigned long data);
|
||||
|
||||
#define WMI_CMD(_wmi_cmd) \
|
||||
do { \
|
||||
ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd, NULL, 0, \
|
||||
(u8 *) &cmd_rsp, \
|
||||
sizeof(cmd_rsp), HZ); \
|
||||
} while (0)
|
||||
|
||||
#define WMI_CMD_BUF(_wmi_cmd, _buf) \
|
||||
do { \
|
||||
ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd, \
|
||||
(u8 *) _buf, sizeof(*_buf), \
|
||||
&cmd_rsp, sizeof(cmd_rsp), HZ); \
|
||||
} while (0)
|
||||
|
||||
#endif /* WMI_H */
|
@ -59,6 +59,7 @@ enum ATH_DEBUG {
|
||||
ATH_DBG_PS = 0x00000800,
|
||||
ATH_DBG_HWTIMER = 0x00001000,
|
||||
ATH_DBG_BTCOEX = 0x00002000,
|
||||
ATH_DBG_WMI = 0x00004000,
|
||||
ATH_DBG_ANY = 0xffffffff
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user