/*
* Copyright (c) 2008 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.
*/
/* mac80211 and PCI callbacks */
#include <linux/nl80211.h>
#include "core.h"
#define ATH_PCI_VERSION "0.1"
#define IEEE80211_HTCAP_MAXRXAMPDU_FACTOR 13
#define IEEE80211_ACTION_CAT_HT 7
#define IEEE80211_ACTION_HT_TXCHWIDTH 0
static char *dev_info = "ath9k";
MODULE_AUTHOR("Atheros Communications");
MODULE_DESCRIPTION("Support for Atheros 802.11n wireless LAN cards.");
MODULE_SUPPORTED_DEVICE("Atheros 802.11n WLAN cards");
MODULE_LICENSE("Dual BSD/GPL");
static struct pci_device_id ath_pci_id_table[] __devinitdata = {
{ PCI_VDEVICE(ATHEROS, 0x0023) }, /* PCI */
{ PCI_VDEVICE(ATHEROS, 0x0024) }, /* PCI-E */
{ PCI_VDEVICE(ATHEROS, 0x0027) }, /* PCI */
{ PCI_VDEVICE(ATHEROS, 0x0029) }, /* PCI */
{ PCI_VDEVICE(ATHEROS, 0x002A) }, /* PCI-E */
{ 0 }
};
static int ath_get_channel(struct ath_softc *sc,
struct ieee80211_channel *chan)
{
int i;
for (i = 0; i < sc->sc_ah->ah_nchan; i++) {
if (sc->sc_ah->ah_channels[i].channel == chan->center_freq)
return i;
}
return -1;
}
static u32 ath_get_extchanmode(struct ath_softc *sc,
struct ieee80211_channel *chan)
{
u32 chanmode = 0;
u8 ext_chan_offset = sc->sc_ht_info.ext_chan_offset;
enum ath9k_ht_macmode tx_chan_width = sc->sc_ht_info.tx_chan_width;
switch (chan->band) {
case IEEE80211_BAND_2GHZ:
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_NONE) &&
(tx_chan_width == ATH9K_HT_MACMODE_20))
chanmode = CHANNEL_G_HT20;
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_ABOVE) &&
(tx_chan_width == ATH9K_HT_MACMODE_2040))
chanmode = CHANNEL_G_HT40PLUS;
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_BELOW) &&
(tx_chan_width == ATH9K_HT_MACMODE_2040))
chanmode = CHANNEL_G_HT40MINUS;
break;
case IEEE80211_BAND_5GHZ:
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_NONE) &&
(tx_chan_width == ATH9K_HT_MACMODE_20))
chanmode = CHANNEL_A_HT20;
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_ABOVE) &&
(tx_chan_width == ATH9K_HT_MACMODE_2040))
chanmode = CHANNEL_A_HT40PLUS;
if ((ext_chan_offset == IEEE80211_HT_IE_CHA_SEC_BELOW) &&
(tx_chan_width == ATH9K_HT_MACMODE_2040))
chanmode = CHANNEL_A_HT40MINUS;
break;
default:
break;
}
return chanmode;
}
static int ath_setkey_tkip(struct ath_softc *sc,
struct ieee80211_key_conf *key,
struct ath9k_keyval *hk,
const u8 *addr)
{
u8 *key_rxmic = NULL;
u8 *key_txmic = NULL;
key_txmic = key->key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY;
key_rxmic = key->key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY;
if (addr == NULL) {
/* Group key installation */
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
return ath_keyset(sc, key->keyidx, hk, addr);
}
if (!sc->sc_splitmic) {
/*
* data key goes at first index,
* the hal handles the MIC keys at index+64.
*/
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_txmic));
return ath_keyset(sc, key->keyidx, hk, addr);
}
/*
* TX key goes at first index, RX key at +32.
* The hal handles the MIC keys at index+64.
*/
memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic));
if (!ath_keyset(sc, key->keyidx, hk, NULL)) {
/* Txmic entry failed. No need to proceed further */
DPRINTF(sc, ATH_DBG_KEYCACHE,
"%s Setting TX MIC Key Failed\n", __func__);
return 0;
}
memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic));
/* XXX delete tx key on failure? */
return ath_keyset(sc, key->keyidx+32, hk, addr);
}
static int ath_key_config(struct ath_softc *sc,
const u8 *addr,
struct ieee80211_key_conf *key)
{
struct ieee80211_vif *vif;
struct ath9k_keyval hk;
const u8 *mac = NULL;
int ret = 0;
enum ieee80211_if_types opmode;
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 -EINVAL;
}
hk.kv_len = key->keylen;
memcpy(hk.kv_val, key->key, key->keylen);
if (!sc->sc_vaps[0])
return -EIO;
vif = sc->sc_vaps[0]->av_if_data;
opmode = vif->type;
/*
* Strategy:
* For _M_STA mc tx, we will not setup a key at all since we never
* tx mc.
* _M_STA mc rx, we will use the keyID.
* for _M_IBSS mc tx, we will use the keyID, and no macaddr.
* for _M_IBSS mc rx, we will alloc a slot and plumb the mac of the
* peer node. BUT we will plumb a cleartext key so that we can do
* perSta default key table lookup in software.
*/
if (is_broadcast_ether_addr(addr)) {
switch (opmode) {
case IEEE80211_IF_TYPE_STA:
/* default key: could be group WPA key
* or could be static WEP key */
mac = NULL;
break;
case IEEE80211_IF_TYPE_IBSS:
break;
case IEEE80211_IF_TYPE_AP:
break;
default:
ASSERT(0);
break;
}
} else {
mac = addr;
}
if (key->alg == ALG_TKIP)
ret = ath_setkey_tkip(sc, key, &hk, mac);
else
ret = ath_keyset(sc, key->keyidx, &hk, mac);
if (!ret)
return -EIO;
sc->sc_keytype = hk.kv_type;
return 0;
}
static void ath_key_delete(struct ath_softc *sc, struct ieee80211_key_conf *key)
{
#define ATH_MAX_NUM_KEYS 4
int freeslot;
freeslot = (key->keyidx >= ATH_MAX_NUM_KEYS) ? 1 : 0;
ath_key_reset(sc, key->keyidx, freeslot);
#undef ATH_MAX_NUM_KEYS
}
static void setup_ht_cap(struct ieee80211_ht_info *ht_info)
{
/* Until mac80211 includes these fields */
#define IEEE80211_HT_CAP_DSSSCCK40 0x1000
#define IEEE80211_HT_CAP_MAXRXAMPDU_65536 0x3 /* 2 ^ 16 */
#define IEEE80211_HT_CAP_MPDUDENSITY_8 0x6 /* 8 usec */
ht_info->ht_supported = 1;
ht_info->cap = (u16)IEEE80211_HT_CAP_SUP_WIDTH
|(u16)IEEE80211_HT_CAP_MIMO_PS
|(u16)IEEE80211_HT_CAP_SGI_40
|(u16)IEEE80211_HT_CAP_DSSSCCK40;
ht_info->ampdu_factor = IEEE80211_HT_CAP_MAXRXAMPDU_65536;
ht_info->ampdu_density = IEEE80211_HT_CAP_MPDUDENSITY_8;
/* setup supported mcs set */
memset(ht_info->supp_mcs_set, 0, 16);
ht_info->supp_mcs_set[0] = 0xff;
ht_info->supp_mcs_set[1] = 0xff;
ht_info->supp_mcs_set[12] = IEEE80211_HT_CAP_MCS_TX_DEFINED;
}
static int ath_rate2idx(struct ath_softc *sc, int rate)
{
int i = 0, cur_band, n_rates;
struct ieee80211_hw *hw = sc->hw;
cur_band = hw->conf.channel->band;
n_rates = sc->sbands[cur_band].n_bitrates;
for (i = 0; i < n_rates; i++) {
if (sc->sbands[cur_band].bitrates[i].bitrate == rate)
break;
}
/*
* NB:mac80211 validates rx rate index against the supported legacy rate
* index only (should be done against ht rates also), return the highest
* legacy rate index for rx rate which does not match any one of the
* supported basic and extended rates to make mac80211 happy.
* The following hack will be cleaned up once the issue with
* the rx rate index validation in mac80211 is fixed.
*/
if (i == n_rates)
return n_rates - 1;
return i;
}
static void ath9k_rx_prepare(struct ath_softc *sc,
struct sk_buff *skb,
struct ath_recv_status *status,
struct ieee80211_rx_status *rx_status)
{
struct ieee80211_hw *hw = sc->hw;
struct ieee80211_channel *curchan = hw->conf.channel;
memset(rx_status, 0, sizeof(struct ieee80211_rx_status));
rx_status->mactime = status->tsf;
rx_status->band = curchan->band;
rx_status->freq = curchan->center_freq;
rx_status->noise = ATH_DEFAULT_NOISE_FLOOR;
rx_status->signal = rx_status->noise + status->rssi;
rx_status->rate_idx = ath_rate2idx(sc, (status->rateKbps / 100));
rx_status->antenna = status->antenna;
rx_status->qual = status->rssi * 100 / 64;
if (status->flags & ATH_RX_MIC_ERROR)
rx_status->flag |= RX_FLAG_MMIC_ERROR;
if (status->flags & ATH_RX_FCS_ERROR)
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
rx_status->flag |= RX_FLAG_TSFT;
}
static u8 parse_mpdudensity(u8 mpdudensity)
{
/*
* 802.11n D2.0 defined values for "Minimum MPDU Start Spacing":
* 0 for no restriction
* 1 for 1/4 us
* 2 for 1/2 us
* 3 for 1 us
* 4 for 2 us
* 5 for 4 us
* 6 for 8 us
* 7 for 16 us
*/
switch (mpdudensity) {
case 0:
return 0;
case 1:
case 2:
case 3:
/* Our lower layer calculations limit our precision to
1 microsecond */
return 1;
case 4:
return 2;
case 5:
return 4;
case 6:
return 8;
case 7:
return 16;
default:
return 0;
}
}
static int ath9k_start(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
struct ieee80211_channel *curchan = hw->conf.channel;
int error = 0, pos;
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Starting driver with "
"initial channel: %d MHz\n", __func__, curchan->center_freq);
/* setup initial channel */
pos = ath_get_channel(sc, curchan);
if (pos == -1) {
DPRINTF(sc, ATH_DBG_FATAL, "%s: Invalid channel\n", __func__);
return -EINVAL;
}
sc->sc_ah->ah_channels[pos].chanmode =
(curchan->band == IEEE80211_BAND_2GHZ) ? CHANNEL_G : CHANNEL_A;
/* open ath_dev */
error = ath_open(sc, &sc->sc_ah->ah_channels[pos]);
if (error) {
DPRINTF(sc, ATH_DBG_FATAL,
"%s: Unable to complete ath_open\n", __func__);
return error;
}
ieee80211_wake_queues(hw);
return 0;
}
static int ath9k_tx(struct ieee80211_hw *hw,
struct sk_buff *skb)
{
struct ath_softc *sc = hw->priv;
int hdrlen, padsize;
/* Add the padding after the header if this is not already done */
hdrlen = ieee80211_get_hdrlen_from_skb(skb);
if (hdrlen & 3) {
padsize = hdrlen % 4;
if (skb_headroom(skb) < padsize)
return -1;
skb_push(skb, padsize);
memmove(skb->data, skb->data + padsize, hdrlen);
}
DPRINTF(sc, ATH_DBG_XMIT, "%s: transmitting packet, skb: %p\n",
__func__,
skb);
if (ath_tx_start(sc, skb) != 0) {
DPRINTF(sc, ATH_DBG_XMIT, "%s: TX failed\n", __func__);
dev_kfree_skb_any(skb);
/* FIXME: Check for proper return value from ATH_DEV */
return 0;
}
return 0;
}
static void ath9k_stop(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
int error;
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Driver halt\n", __func__);
error = ath_suspend(sc);
if (error)
DPRINTF(sc, ATH_DBG_CONFIG,
"%s: Device is no longer present\n", __func__);
ieee80211_stop_queues(hw);
}
static int ath9k_add_interface(struct ieee80211_hw *hw,
struct ieee80211_if_init_conf *conf)
{
struct ath_softc *sc = hw->priv;
int error, ic_opmode = 0;
/* Support only vap for now */
if (sc->sc_nvaps)
return -ENOBUFS;
switch (conf->type) {
case IEEE80211_IF_TYPE_STA:
ic_opmode = ATH9K_M_STA;
break;
case IEEE80211_IF_TYPE_IBSS:
ic_opmode = ATH9K_M_IBSS;
break;
default:
DPRINTF(sc, ATH_DBG_FATAL,
"%s: Only STA and IBSS are supported currently\n",
__func__);
return -EOPNOTSUPP;
}
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Attach a VAP of type: %d\n",
__func__,
ic_opmode);
error = ath_vap_attach(sc, 0, conf->vif, ic_opmode);
if (error) {
DPRINTF(sc, ATH_DBG_FATAL,
"%s: Unable to attach vap, error: %d\n",
__func__, error);
return error;
}
return 0;
}
static void ath9k_remove_interface(struct ieee80211_hw *hw,
struct ieee80211_if_init_conf *conf)
{
struct ath_softc *sc = hw->priv;
struct ath_vap *avp;
int error;
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Detach VAP\n", __func__);
avp = sc->sc_vaps[0];
if (avp == NULL) {
DPRINTF(sc, ATH_DBG_FATAL, "%s: Invalid interface\n",
__func__);
return;
}
#ifdef CONFIG_SLOW_ANT_DIV
ath_slow_ant_div_stop(&sc->sc_antdiv);
#endif
/* Update ratectrl */
ath_rate_newstate(sc, avp);
/* Reclaim beacon resources */
if (sc->sc_opmode == ATH9K_M_HOSTAP || sc->sc_opmode == ATH9K_M_IBSS) {
ath9k_hw_stoptxdma(sc->sc_ah, sc->sc_bhalq);
ath_beacon_return(sc, avp);
}
/* Set interrupt mask */
sc->sc_imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
ath9k_hw_set_interrupts(sc->sc_ah, sc->sc_imask & ~ATH9K_INT_GLOBAL);
sc->sc_beacons = 0;
error = ath_vap_detach(sc, 0);
if (error)
DPRINTF(sc, ATH_DBG_FATAL,
"%s: Unable to detach vap, error: %d\n",
__func__, error);
}
static int ath9k_config(struct ieee80211_hw *hw,
struct ieee80211_conf *conf)
{
struct ath_softc *sc = hw->priv;
struct ieee80211_channel *curchan = hw->conf.channel;
int pos;
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Set channel: %d MHz\n",
__func__,
curchan->center_freq);
pos = ath_get_channel(sc, curchan);
if (pos == -1) {
DPRINTF(sc, ATH_DBG_FATAL, "%s: Invalid channel\n", __func__);
return -EINVAL;
}
sc->sc_ah->ah_channels[pos].chanmode =
(curchan->band == IEEE80211_BAND_2GHZ) ?
CHANNEL_G : CHANNEL_A;
if (sc->sc_curaid && hw->conf.ht_conf.ht_supported)
sc->sc_ah->ah_channels[pos].chanmode =
ath_get_extchanmode(sc, curchan);
sc->sc_config.txpowlimit = 2 * conf->power_level;
/* set h/w channel */
if (ath_set_channel(sc, &sc->sc_ah->ah_channels[pos]) < 0)
DPRINTF(sc, ATH_DBG_FATAL, "%s: Unable to set channel\n",
__func__);
return 0;
}
static int ath9k_config_interface(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_if_conf *conf)
{
struct ath_softc *sc = hw->priv;
struct ath_vap *avp;
u32 rfilt = 0;
int error, i;
DECLARE_MAC_BUF(mac);
avp = sc->sc_vaps[0];
if (avp == NULL) {
DPRINTF(sc, ATH_DBG_FATAL, "%s: Invalid interface\n",
__func__);
return -EINVAL;
}
if ((conf->changed & IEEE80211_IFCC_BSSID) &&
!is_zero_ether_addr(conf->bssid)) {
switch (vif->type) {
case IEEE80211_IF_TYPE_STA:
case IEEE80211_IF_TYPE_IBSS:
/* Update ratectrl about the new state */
ath_rate_newstate(sc, avp);
/* Set rx filter */
rfilt = ath_calcrxfilter(sc);
ath9k_hw_setrxfilter(sc->sc_ah, rfilt);
/* Set BSSID */
memcpy(sc->sc_curbssid, conf->bssid, ETH_ALEN);
sc->sc_curaid = 0;
ath9k_hw_write_associd(sc->sc_ah, sc->sc_curbssid,
sc->sc_curaid);
/* Set aggregation protection mode parameters */
sc->sc_config.ath_aggr_prot = 0;
/*
* Reset our TSF so that its value is lower than the
* beacon that we are trying to catch.
* Only then hw will update its TSF register with the
* new beacon. Reset the TSF before setting the BSSID
* to avoid allowing in any frames that would update
* our TSF only to have us clear it
* immediately thereafter.
*/
ath9k_hw_reset_tsf(sc->sc_ah);
/* Disable BMISS interrupt when we're not associated */
ath9k_hw_set_interrupts(sc->sc_ah,
sc->sc_imask &
~(ATH9K_INT_SWBA | ATH9K_INT_BMISS));
sc->sc_imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS);
DPRINTF(sc, ATH_DBG_CONFIG,
"%s: RX filter 0x%x bssid %s aid 0x%x\n",
__func__, rfilt,
print_mac(mac, sc->sc_curbssid), sc->sc_curaid);
/* need to reconfigure the beacon */
sc->sc_beacons = 0;
break;
default:
break;
}
}
if ((conf->changed & IEEE80211_IFCC_BEACON) &&
(vif->type == IEEE80211_IF_TYPE_IBSS)) {
/*
* Allocate and setup the beacon frame.
*
* Stop any previous beacon DMA. This may be
* necessary, for example, when an ibss merge
* causes reconfiguration; we may be called
* with beacon transmission active.
*/
ath9k_hw_stoptxdma(sc->sc_ah, sc->sc_bhalq);
error = ath_beacon_alloc(sc, 0);
if (error != 0)
return error;
ath_beacon_sync(sc, 0);
}
/* Check for WLAN_CAPABILITY_PRIVACY ? */
if ((avp->av_opmode != IEEE80211_IF_TYPE_STA)) {
for (i = 0; i < IEEE80211_WEP_NKID; i++)
if (ath9k_hw_keyisvalid(sc->sc_ah, (u16)i))
ath9k_hw_keysetmac(sc->sc_ah,
(u16)i,
sc->sc_curbssid);
}
/* Only legacy IBSS for now */
if (vif->type == IEEE80211_IF_TYPE_IBSS)
ath_update_chainmask(sc, 0);
return 0;
}
#define SUPPORTED_FILTERS \
(FIF_PROMISC_IN_BSS | \
FIF_ALLMULTI | \
FIF_CONTROL | \
FIF_OTHER_BSS | \
FIF_BCN_PRBRESP_PROMISC | \
FIF_FCSFAIL)
/* Accept unicast, bcast and mcast frames */
static void ath9k_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *total_flags,
int mc_count,
struct dev_mc_list *mclist)
{
struct ath_softc *sc = hw->priv;
changed_flags &= SUPPORTED_FILTERS;
*total_flags &= SUPPORTED_FILTERS;
if (changed_flags & FIF_BCN_PRBRESP_PROMISC) {
if (*total_flags & FIF_BCN_PRBRESP_PROMISC)
ath_scan_start(sc);
else
ath_scan_end(sc);
}
}
static void ath9k_sta_notify(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum sta_notify_cmd cmd,
const u8 *addr)
{
struct ath_softc *sc = hw->priv;
struct ath_node *an;
unsigned long flags;
DECLARE_MAC_BUF(mac);
spin_lock_irqsave(&sc->node_lock, flags);
an = ath_node_find(sc, (u8 *) addr);
spin_unlock_irqrestore(&sc->node_lock, flags);
switch (cmd) {
case STA_NOTIFY_ADD:
spin_lock_irqsave(&sc->node_lock, flags);
if (!an) {
ath_node_attach(sc, (u8 *)addr, 0);
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Attach a node: %s\n",
__func__,
print_mac(mac, addr));
} else {
ath_node_get(sc, (u8 *)addr);
}
spin_unlock_irqrestore(&sc->node_lock, flags);
break;
case STA_NOTIFY_REMOVE:
if (!an)
DPRINTF(sc, ATH_DBG_FATAL,
"%s: Removal of a non-existent node\n",
__func__);
else {
ath_node_put(sc, an, ATH9K_BH_STATUS_INTACT);
DPRINTF(sc, ATH_DBG_CONFIG, "%s: Put a node: %s\n",
__func__,
print_mac(mac, addr));
}
break;
default:
break;
}
}
static int ath9k_conf_tx(struct ieee80211_hw *hw,
u16 queue,
const struct ieee80211_tx_queue_params *params)
{
struct ath_softc *sc = hw->priv;
struct ath9k_tx_queue_info qi;
int ret = 0, qnum;
if (queue >= WME_NUM_AC)
return 0;
qi.tqi_aifs = params->aifs;
qi.tqi_cwmin = params->cw_min;
qi.tqi_cwmax = params->cw_max;
qi.tqi_burstTime = params->txop;
qnum = ath_get_hal_qnum(queue, sc);
DPRINTF(sc, ATH_DBG_CONFIG,
"%s: Configure tx [queue/halq] [%d/%d], "
"aifs: %d, cw_min: %d, cw_max: %d, txop: %d\n",
__func__,
queue,
qnum,
params->aifs,
params->cw_min,