diff options
Diffstat (limited to 'drivers/net/wireless/digiPiper/digiMac80211.c')
-rw-r--r-- | drivers/net/wireless/digiPiper/digiMac80211.c | 1000 |
1 files changed, 1000 insertions, 0 deletions
diff --git a/drivers/net/wireless/digiPiper/digiMac80211.c b/drivers/net/wireless/digiPiper/digiMac80211.c new file mode 100644 index 000000000000..b9ca3ef10230 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiMac80211.c @@ -0,0 +1,1000 @@ +/* + * digiMac80211.c + * + * Copyright (C) 2009 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * This file contains the routines that interface with the mac80211 + * library. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/etherdevice.h> +#include <net/mac80211.h> +#include <crypto/aes.h> +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define MAC_DEBUG (1) + +#if MAC_DEBUG +static int dlevel = DWARNING; +#define dprintk(level, fmt, arg...) if (level >= dlevel) \ + printk(KERN_ERR PIPER_DRIVER_NAME \ + ": %s - " fmt, __func__, ##arg) +#else +#define dprintk(level, fmt, arg...) do {} while (0) +#endif + +/* + * This constant determines how many times per second the led_timer_fn + * function will be called. (HZ >> 3) means 8 times a second. + */ +#define LED_TIMER_RATE (HZ >> 3) +#define LED_MAX_COUNT (15) + +/* + * This function is called from a timer to blink an LED in order to + * indicate what are current state is. + */ +static void led_timer_fn(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *) context; + static unsigned int count = 0; + + if(!piperp->pdata->set_led) + return; + + switch (piperp->led_state) { + case led_shutdown: + /* Turn LED off if we are shut down */ + piperp->pdata->set_led(piperp, STATUS_LED, 0); + break; + case led_adhoc: + /* Blink LED slowly in ad-hoc mode */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 8) ? 0 : 1); + break; + case led_not_associated: + /* Blink LED rapidly when not associated with an AP */ + piperp->pdata->set_led(piperp, STATUS_LED, (count & 1) ? 0 : 1); + break; + case led_associated: + /* LED steadily on when associated */ + piperp->pdata->set_led(piperp, STATUS_LED, 1); + break; + default: + /* Oops. How did we get here? */ + break; + } + count++; + if (count > LED_MAX_COUNT) { + count = 0; + } + + piperp->led_timer.expires += LED_TIMER_RATE; + add_timer(&piperp->led_timer); +} + +/* + * This function sets the current LED state. + */ +static int piper_set_status_led(struct ieee80211_hw *hw, enum led_states state) +{ + struct piper_priv *piperp = (struct piper_priv *)hw->priv; + + piperp->led_state = state; + + if(!piperp->pdata->set_led) + return -ENOSYS; + + if (state == led_shutdown) + piperp->pdata->set_led(piperp, STATUS_LED, 0); + + return 0; +} + +/* + * This routine is called to enable IBSS support whenever we receive + * configuration commands from mac80211 related to IBSS support. The + * routine examines the configuration settings to determine if IBSS + * support should be enabled and, if so, turns on automatic beacon + * generation. + * + * TODO: This code may need to be moved into piper.c since other + * H/W does not implement automatic generate of beacons. + */ +static void piper_enable_ibss(struct piper_priv *piperp, enum nl80211_iftype iftype) +{ + dprintk(DVVERBOSE, "\n"); + + if (((iftype == NL80211_IFTYPE_ADHOC) || (iftype == NL80211_IFTYPE_MESH_POINT)) + && (piperp->beacon.loaded) && (piperp->beacon.enabled) + && ((piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & MAC_BEACON_INTERVAL_MASK) != 0)) { + /* + * If we come here, then we are running in IBSS mode, beacons are enabled, + * and we have the information we need, so start sending beacons. + */ + /* TODO: Handle non-zero ATIM period. mac80211 currently has no way to + * tell us what the ATIM period is, but someday they might fix that.*/ + + u32 reg = piperp->ac->rd_reg(piperp, MAC_BEACON_FILT) & ~MAC_BEACON_BACKOFF_MASK; + piperp->ac->wr_reg(piperp, MAC_BEACON_FILT, + reg | piperp->get_next_beacon_backoff(), op_write); + /* enable beacon interrupts*/ + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + piperp->ac->wr_reg(piperp, + MAC_CTL, MAC_CTL_BEACON_TX | MAC_CTL_IBSS, op_or); + dprintk(DVERBOSE, "IBSS turned ON\n"); + piper_set_status_led(piperp->hw, led_adhoc); + } else { + /* + * If we come here, then either we are not suppose to transmit beacons, + * or we do not yet have all the information we need to transmit + * beacons. Make sure the automatic beacon function is disabled. + */ + /* shut down beacons */ + piperp->ac->wr_reg(piperp, MAC_CTL, + ~(MAC_CTL_BEACON_TX | MAC_CTL_IBSS), op_and); + piperp->set_irq_mask_bit(piperp, BB_IRQ_MASK_TBTT); + dprintk(DVERBOSE, "IBSS turned OFF\n"); + } +} + +/* + * Set the transmit power level. The real work is done in the + * transceiver code. + */ +static int piper_set_tx_power(struct ieee80211_hw *hw, int power) +{ + struct piper_priv *digi = hw->priv; + int err; + int oldTxPower = digi->tx_power; + + dprintk(DVVERBOSE, "\n"); + + if (power == digi->tx_power) + return 0; + + digi->tx_power = power; + err = digi->rf->set_pwr(hw, power); + if (err) + digi->tx_power = oldTxPower; + + return err; +} + +/* + * Utility routine that sets a sequence number for a data packet. + */ +static void assign_seq_number(struct sk_buff *skb, bool increment) +{ +#define SEQUENCE_NUMBER_MASK (0xfff0) + static u16 seq_number = 0; + _80211HeaderType *header = (_80211HeaderType *)skb->data; + + dprintk(DVVERBOSE, "\n"); + + if (skb->len >= sizeof(*header)) { + u16 seq_field; + + /* + * TODO: memcpy's are here because I am concerned we may get + * an unaligned frame. Is this a real possibility? Or + * am I just wasting CPU time? + */ + memcpy(&seq_field, &header->squ.sq16, sizeof(header->squ.sq16)); + seq_field &= ~SEQUENCE_NUMBER_MASK; + seq_field |= (SEQUENCE_NUMBER_MASK & (seq_number << 4)); + memcpy(&header->squ.sq16, &seq_field, sizeof(header->squ.sq16)); + if (increment) + seq_number++; + } +} + +#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +/* Get 16 bits at byte pointer */ +#define GET16(bp) ((bp)[0] | ((bp)[1] << 8)) +/* Get 32 bits at byte pointer */ +#define GET32(bp) ((bp)[0] | ((bp)[1] << 8) | ((bp)[2] << 16) | ((bp)[3] << 24)) +/* Store 16 bits at byte pointer */ +#define SET16(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; } +/* Store 32 bits at byte pointer */ +#define SET32(bp, data) { (bp)[0] = (data); \ + (bp)[1] = (data) >> 8; \ + (bp)[2] = (data) >> 16; \ + (bp)[3] = (data) >> 24; } + +static inline void dw_inc_48(u48* n) +{ + (*n)++; + *n &= ((u64) 1 << 48) - 1; +} + +/* + * This function prepares a blob of data we will have to send to the AES + * H/W encryption engine. The data consists of the AES initialization + * vector and 2 16 byte headers. + * + * Returns true if successful, or false if something goes wrong + */ +bool piper_prepare_aes_datablob(struct piper_priv *piperp, unsigned int keyIndex, + u8 *aesBlob, u8 *frame, u32 length, bool isTransmit) +{ + _80211HeaderType *header = (_80211HeaderType *)frame; + u8 *body = &frame[sizeof(*header)]; + int dlen = length - (_80211_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + dprintk(DVVERBOSE, "\n"); + + if (keyIndex >= PIPER_MAX_KEYS) { + dprintk(DWARNING, "encryption key index %d is out of range.\n", + keyIndex); + return false; + } + + if (piperp->key[keyIndex].valid == false) + return false; + + /* Set up CCM initial block for MIC IV */ + memset(aesBlob, 0, AES_BLOB_LENGTH); + aesBlob[0] = 0x59; + aesBlob[1] = 0; + memcpy (&aesBlob[2], header->addr2, ETH_ALEN); + aesBlob[8] = body[7]; + aesBlob[9] = body[6]; + aesBlob[10] = body[5]; + aesBlob[11] = body[4]; + aesBlob[12] = body[1]; + aesBlob[13] = body[0]; + aesBlob[14] = dlen >> 8; + aesBlob[15] = dlen; + + /* Set up MIC header blocks */ +#define AES_HEADER_0_OFFSET (16) +#define AES_HEADER_1_OFFSET (32) + aesBlob[AES_HEADER_0_OFFSET+0] = 0; + aesBlob[AES_HEADER_0_OFFSET+1] = 22; + aesBlob[AES_HEADER_0_OFFSET+2] = frame[0] & 0xcf; + aesBlob[AES_HEADER_0_OFFSET+3] = frame[1] & 0xd7; + /* + * This memcpy writes data into the last 12 bytes of the first header + * and the first 6 bytes of the 2nd header. I did it as one memcpy + * call for efficiency. + */ + memcpy(&aesBlob[AES_HEADER_0_OFFSET+4], header->addr1, 3*ETH_ALEN); + aesBlob[AES_HEADER_1_OFFSET+6] = header->squ.sq.frag; + aesBlob[AES_HEADER_1_OFFSET+7] = 0; + memset (&aesBlob[AES_HEADER_1_OFFSET+8], 0, 8); /* clear vector location in data */ + + return true; +} + +/* + * mac80211 calls this routine to transmit a frame. We set up + * up the information the trasmit tasklet will need, and then + * schedule the tasklet. + */ +int piper_hw_tx_private(struct ieee80211_hw *hw, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(skb); + unsigned long flags; + + dprintk(DVVERBOSE, "\n"); + + if (piperp->is_radio_on == false) { + dprintk(DERROR, "called with radio off\n"); + return -EBUSY; + } + + /* + * Our H/W can only transmit a single packet at a time. mac80211 + * already maintains a queue of packets, so there is no reason + * for us to set up another one. We stop the mac80211 queue everytime + * we get a transmit request and restart it when the transmit + * operation completes. + */ + ieee80211_stop_queues(hw); + + if (txInfo->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + assign_seq_number(skb, + !!(txInfo->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)); + } + + piperp->use_hw_aes = false; + if (txInfo->control.hw_key != NULL) { + /* + * We've been passed an encryption key, so mac80211 must want us + * to encrypt the packet with our fancy H/W encryptor. Let's get + * set up for that now. + */ + piperp->tx_aes_key = txInfo->control.hw_key->hw_key_idx; + piperp->use_hw_aes = + piper_prepare_aes_datablob(piperp, + txInfo->control.hw_key->hw_key_idx, + (u8 *)piperp->tx_aes_blob, skb->data, + skb->len, true); + } + piper_ps_set_header_flag(piperp, ((_80211HeaderType *) skb->data)); /* set power management bit as appropriate*/ + + /* + * Add space at the start of the frame for the H/W level transmit header. + * We can't generate the header now. It must be generated everytime we + * transmit because the transmit rate changes when we do retries. + */ + skb_push(skb, TX_HEADER_LENGTH); + + piperp->pstats.tx_retry_index = 0; + piperp->pstats.tx_total_tetries = 0; + memset(piperp->pstats.tx_retry_count, 0, sizeof(piperp->pstats.tx_retry_count)); + txInfo->flags &= ~(IEEE80211_TX_STAT_TX_FILTERED | + IEEE80211_TX_STAT_ACK | + IEEE80211_TX_STAT_AMPDU | + IEEE80211_TX_STAT_AMPDU_NO_BACK); + piperp->pstats.tx_queue.len++; + piperp->pstats.tx_queue.count++; + + if (piper_tx_enqueue(piperp, skb, skb_return_cb) == -1) { + skb_pull(skb, TX_HEADER_LENGTH); /* undo the skb_push above */ + return -EBUSY; + } + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + if (!piperp->tx_tasklet_running) { + piperp->tx_tasklet_running = true; + tasklet_hi_schedule(&piperp->tx_tasklet); + } + + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + + piperp->pstats.tx_start_count++; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_hw_tx_private); + + +int piper_hw_tx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + return piper_hw_tx_private(hw, skb, ieee80211_tx_status_irqsafe); +} + + +/* + * mac80211 calls this routine to initialize the H/W. + */ +static int piper_hw_start(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + piperp->if_type = __NL80211_IFTYPE_AFTER_LAST; + + /* Initialize the MAC H/W */ + if ((ret = piperp->init_hw(piperp, IEEE80211_BAND_2GHZ)) != 0) { + dprintk(DERROR, "unable to initialize piper HW (%d)\n", ret); + return ret; + } + + piperp->is_radio_on = true; + + /* + * Initialize the antenna with the defualt setting defined in the + * probe function. This can be changed, currently, through a sysfs + * entry in the device directory + */ + if ((ret = piperp->set_antenna(piperp, piperp->antenna)) != 0) { + dprintk(DERROR, "piper_set_antenna_div() failed (%d)\n", ret); + return ret; + } + + /* set status led to link off */ + piper_set_status_led(hw, led_shutdown); + + /* Get the tasklets ready to roll */ + piperp->tx_result = TX_NOT_DONE; + tasklet_enable(&piperp->rx_tasklet); + tasklet_enable(&piperp->tx_tasklet); + + /* + * Enable receive interrupts, but leave the transmit interrupts + * and beacon interrupts off for now. + */ + piperp->clear_irq_mask_bit(piperp, 0xffffffff); + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_RX_OVERRUN | BB_IRQ_MASK_RX_FIFO); + enable_irq(piperp->irq); + + memset(piperp->bssid, 0, ETH_ALEN); + + return 0; +} + +/* + * mac80211 calls this routine to shut us down. + */ +static void piper_hw_stop(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + + /* Initialize the MAC H/W */ + if (piperp->deinit_hw) + piperp->deinit_hw(piperp); + + /* set status led to link off */ + if (piper_set_status_led(hw, led_shutdown)) + return; /* hardware's probably gone, give up */ + + /* turn off phy */ + piperp->rf->stop(hw); + + /* Disable interrupts before turning off */ + disable_irq(piperp->irq); + + /* turn off MAX_GAIN, ADC clocks, and so on */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_RESET, op_and); + + /* turn off MAC control/mac filt/aes key */ + piperp->ac->wr_reg(piperp, MAC_CTL, 0, op_write); + + /* turn off interrupts */ + tasklet_disable(&piperp->rx_tasklet); + tasklet_disable(&piperp->tx_tasklet); + piperp->clear_irq_mask_bit(piperp, 0xffffffff); +} + +/* + * mac80211 calls this routine to really start the H/W. + * The device type is also set here. + */ +static int piper_hw_add_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "if type: %x\n", conf->type); + + /* __NL80211_IFTYPE_AFTER_LAST means no mode selected */ + if (piperp->if_type != __NL80211_IFTYPE_AFTER_LAST) { + dprintk(DERROR, "unsupported interface type %x, expected %x\n", + conf->type, __NL80211_IFTYPE_AFTER_LAST); + return -EOPNOTSUPP; + } + + switch (conf->type) { + case NL80211_IFTYPE_ADHOC: + piper_set_status_led(piperp->hw, led_adhoc); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_STATION: + piper_set_status_led(hw, led_not_associated); + piperp->if_type = conf->type; + break; + + case NL80211_IFTYPE_MESH_POINT: + piperp->if_type = conf->type; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * mac80211 calls this function to shut down us down. + */ +static void piper_hw_rm_intf(struct ieee80211_hw *hw, + struct ieee80211_if_init_conf *conf) +{ + struct piper_priv *digi = hw->priv; + + dprintk(DVVERBOSE, "\n"); + digi->if_type = __NL80211_IFTYPE_AFTER_LAST; +} + +/* + * mac80211 calls this function to pass us configuration information. + */ +static int piper_config(struct ieee80211_hw *hw, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + u32 tempval; + int err; + + dprintk(DVVERBOSE, "\n"); + + if (changed & IEEE80211_CONF_CHANGE_PS) { + /* + * Enable power save mode if bit set in flags, and if we are in station + * mode. Power save is not supported in ad-hoc/mesh mode. + */ + piper_ps_scan_event(piperp); + piper_ps_set(piperp, ( (conf->flags & IEEE80211_CONF_PS) + && (piperp->if_type == NL80211_IFTYPE_STATION) + && (piperp->areWeAssociated))); + } + /* Should we turn the radio off? */ + if ((piperp->is_radio_on = (conf->radio_enabled != 0)) != 0) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_RX_EN, op_or); + } else { + dprintk(DNORMAL, "Turning radio off\n"); + return piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_RX_EN, op_and); + } + + /* Adjust the beacon interval and listen interval */ + tempval = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_BEACON_INTERVAL_MASK; + tempval |= conf->beacon_int << MAC_BEACON_INTERVAL_SHIFT; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, tempval, op_write); + + tempval = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_LISTEN_INTERVAL_MASK; + tempval |= conf->listen_interval; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, tempval, op_write); + + /* Adjust the power level */ + if ((err = piper_set_tx_power(hw, conf->power_level)) != 0) { + dprintk(DERROR, "unable to set tx power to %d\n", + conf->power_level); + return err; + } + + /* Set channel */ + if (conf->channel->hw_value != piperp->channel) { + piper_ps_scan_event(piperp); + if ((err = piperp->rf->set_chan(hw, conf->channel->hw_value)) !=0) { + dprintk(DERROR, "unable to set ch to %d\n", + conf->channel->hw_value); + return err; + } + piperp->channel = conf->channel->hw_value; + } + + return 0; +} + +/* + * mac80211 calls this routine to set BSS related configuration settings. + */ +static int piper_hw_config_intf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_if_conf *conf) +{ + struct piper_priv *piperp = hw->priv; + u32 bssid[2]; + + dprintk(DVVERBOSE, "\n"); + + if (conf->changed & IEEE80211_IFCC_BSSID && + !is_zero_ether_addr(conf->bssid) && + !is_multicast_ether_addr(conf->bssid)) { + + piper_ps_scan_event(piperp); + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + dprintk(DVERBOSE, "BSSID: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + conf->bssid[0], conf->bssid[1], conf->bssid[2], + conf->bssid[3], conf->bssid[4], conf->bssid[5]); + + bssid[0] = conf->bssid[3] | conf->bssid[2] << 8 | + conf->bssid[1] << 16 | conf->bssid[0] << 24; + bssid[1] = conf->bssid[5] << 16 | conf->bssid[4] << 24; + + if ((bssid[0] == 0) && (bssid[1] == 0)) { + /* + * If we come here, then the MAC layer is telling us to set a 0 + * SSID. In this case, we really want to set the SSID to the + * broadcast address so that we receive broadcasts. + */ + bssid[0] = 0xffffffff; + bssid[1] = 0xffffffff; + } + piperp->ac->wr_reg(piperp, MAC_BSS_ID0, bssid[0], op_write); + piperp->ac->wr_reg(piperp, MAC_BSS_ID1, bssid[1], op_write); + memcpy(piperp->bssid, conf->bssid, ETH_ALEN); + break; + default: + break; + } + } + + if ((conf->changed & IEEE80211_IFCC_BEACON) && + (piperp->if_type == NL80211_IFTYPE_ADHOC)) { + struct sk_buff *beacon = ieee80211_beacon_get(hw, vif); + struct ieee80211_rate rate; + + if (!beacon) + return -ENOMEM; + + rate.bitrate = 10; /* beacons always sent at 1 Megabit*/ + skb_push(beacon, TX_HEADER_LENGTH); + phy_set_plcp(beacon->data, beacon->len - TX_HEADER_LENGTH, &rate, 0); + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_BEACON_TX, op_and); + piperp->load_beacon(piperp, beacon->data, beacon->len); + + /* TODO: digi->beacon.enabled should be set by IEEE80211_IFCC_BEACON_ENABLED + when we update to latest mac80211 */ + piperp->beacon.enabled = true; + piper_enable_ibss(piperp, vif->type); + dev_kfree_skb(beacon); /* we are responsible for freeing this buffer*/ + } + + return 0; +} + +/* + * mac80211 wants to change our frame filtering settings. We don't + * actually support this. + */ +static void piper_hw_config_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, unsigned int *total_flags, + int mc_count, struct dev_addr_list *mclist) +{ + dprintk(DVVERBOSE, "\n"); + + /* we don't support filtering so clear all flags; however, we also + * can't pass failed FCS/PLCP frames, so don't clear those. */ + *total_flags &= (FIF_FCSFAIL | FIF_PLCPFAIL); +} + +/* + * There are 1024 TU's (time units) to a second, and HZ jiffies to a + * second. This macro converts TUs to jiffies. + */ +#define TU_TO_JIFFIES(x) (((x*HZ) + 512) / 1024) + + +/* + * mac80211 calls this routine when something about our BSS has changed. + * Usually, this routine only gets called when we associate, or disassociate. + */ +static void piper_hw_bss_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, u32 changed) +{ + struct piper_priv *piperp = hw->priv; + u32 reg; + + dprintk(DVVERBOSE, " changed = 0x%08x\n", changed); + + if (changed & BSS_CHANGED_ASSOC) { + piper_ps_scan_event(piperp); + /* Our association status has changed */ + if (piperp->if_type == NL80211_IFTYPE_STATION) { + piper_set_status_led(hw, conf->assoc ? led_associated : led_not_associated); + } + piperp->areWeAssociated = conf->assoc; + piperp->ps.aid = conf->aid; + + digi_dbg(" AP %s\n", conf->assoc ? + "associated" : "disassociated"); + } + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + piperp->tx_cts = conf->use_cts_prot; + } + if (changed & BSS_CHANGED_ERP_PREAMBLE) { +#define WANT_SHORT_PREAMBLE_SUPPORT (1) +/* TODO: Determine if short preambles really hurt us, or if I'm just seeing things. */ +#if WANT_SHORT_PREAMBLE_SUPPORT + if (conf->use_short_preamble) { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + BB_GENERAL_CTL_SH_PRE, op_or); + } else { + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, + ~BB_GENERAL_CTL_SH_PRE, op_and); + } + piperp->use_short_preamble = conf->use_short_preamble; +#else + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_SH_PRE, op_or); +#endif + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + /* + * The list of transmit rates has changed. Update the + * rates we will receive at to match those the AP will + * transmit at. This should improve our receive performance + * since we won't listen to junk at the wrong rate. + */ + unsigned int ofdm = 0, psk = 0; + + reg = piperp->ac->rd_reg(piperp, MAC_SSID_LEN) & + ~(MAC_OFDM_BRS_MASK | MAC_PSK_BRS_MASK); + + piperp->rf->getOfdmBrs(piperp->channel, conf->basic_rates, &ofdm, &psk); + reg |= ofdm << MAC_OFDM_BRS_SHIFT; + reg |= psk << MAC_PSK_BRS_SHIFT; + piperp->ac->wr_reg(piperp, MAC_SSID_LEN, reg, op_write); + + dprintk(DVERBOSE, "BRS mask set to 0x%8.8X\n", reg); + + if (ofdm == 0) { + /* Disable ofdm receiver if no ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + ~BB_GENERAL_STAT_A_EN, op_and); + } else { + /* Enable ofdm receiver if any ofdm rates supported */ + piperp->ac->wr_reg(piperp, BB_GENERAL_STAT, + BB_GENERAL_STAT_A_EN, op_or); + } + } + + /* Save new DTIM period */ + reg = piperp->ac->rd_reg(piperp, MAC_DTIM_PERIOD) & ~MAC_DTIM_PERIOD_MASK; + reg |= conf->dtim_period << MAC_DTIM_PERIOD_SHIFT; + piperp->ac->wr_reg(piperp, MAC_DTIM_PERIOD, reg, op_write); + reg = piperp->ac->rd_reg(piperp, MAC_CFP_ATIM) & ~MAC_DTIM_CFP_MASK; + piperp->ps.beacon_int = conf->beacon_int; + reg |= conf->beacon_int << 16; + piperp->ac->wr_reg(piperp, MAC_CFP_ATIM, reg, op_write); +} + +/* + * Use the SSL library routines to expand the AES key. + */ +static int piper_expand_aes_key(struct ieee80211_key_conf *key, + u32 *expandedKey) +{ + struct crypto_aes_ctx aes; + int ret; + + dprintk(DVVERBOSE, "\n"); + + if (key->keylen != AES_KEYSIZE_128) + return -EOPNOTSUPP; + + ret = crypto_aes_expand_key(&aes, key->key, key->keylen); + if (ret) + return -EOPNOTSUPP; + + memcpy(expandedKey, aes.key_enc, EXPANDED_KEY_LENGTH); + + return 0; +} + +/* + * mac80211 calls this routine to set a new encryption key, or to + * retire an old one. We support H/W AES encryption/decryption, so + * save the AES related keys. + */ +static int piper_hw_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + const u8 *local_address, const u8 *address, + struct ieee80211_key_conf *key) +{ + struct piper_priv *piperp = hw->priv; + int ret = 0; + + dprintk(DVVERBOSE, "\n"); + + if ((key->alg != ALG_CCMP) || (key->keyidx >= PIPER_MAX_KEYS)) { + /* + * If we come here, then mac80211 was trying to set a key for some + * algorithm other than AES, or trying to set a key index greater + * than 3. We only support AES, and only 4 keys. + */ + ret = -EOPNOTSUPP; + goto set_key_error; + } + key->hw_key_idx = key->keyidx; + + if (cmd == SET_KEY) { + ret = piper_expand_aes_key(key, piperp->key[key->keyidx].expandedKey); + if (ret) + goto set_key_error; + + if (!piperp->key[key->keyidx].valid) + piperp->aes_key_count++; + piperp->key[key->keyidx].txPn = 0; + piperp->key[key->keyidx].rxPn = 0; + piperp->key[key->keyidx].valid = (ret == 0); + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } else { + /* disable key */ + if (piperp->key[key->keyidx].valid) + piperp->aes_key_count--; + piperp->key[key->keyidx].valid = false; + } + + if (piperp->aes_key_count > 0) + piperp->ac->wr_reg(piperp, MAC_CTL, ~MAC_CTL_AES_DISABLE, op_and); + else + piperp->ac->wr_reg(piperp, MAC_CTL, MAC_CTL_AES_DISABLE, op_or); + +set_key_error: + if (ret) + dprintk(DVERBOSE, "unable to set AES key\n"); + + return ret; +} + +/* + * mac80211 calls this routine to determine if we transmitted the + * last beacon. Unfortunately, we can't tell for sure. We give + * mac80211 our best guess. + */ +static int piper_hw_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + return piperp->beacon.weSentLastOne ? 1 : 0; +} + +static int piper_get_tx_stats(struct ieee80211_hw *hw, + struct ieee80211_tx_queue_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.tx_queue, sizeof(piperp->pstats.tx_queue)); + + return 0; +} + +static int piper_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct piper_priv *piperp = hw->priv; + + dprintk(DVVERBOSE, "\n"); + if (stats) + memcpy(stats, &piperp->pstats.ll_stats, sizeof(piperp->pstats.ll_stats)); + + return 0; +} + +const struct ieee80211_ops hw_ops = { + .tx = piper_hw_tx, + .start = piper_hw_start, + .stop = piper_hw_stop, + .add_interface = piper_hw_add_intf, + .remove_interface = piper_hw_rm_intf, + .config = piper_config, + .config_interface = piper_hw_config_intf, + .configure_filter = piper_hw_config_filter, + .bss_info_changed = piper_hw_bss_changed, + .tx_last_beacon = piper_hw_tx_last_beacon, + .set_key = piper_hw_set_key, + .get_tx_stats = piper_get_tx_stats, + .get_stats = piper_get_stats, +}; + +/* + * This routine is called by the probe routine to allocate the + * data structure we need to communicate with mac80211. + */ +int piper_alloc_hw(struct piper_priv **priv, size_t priv_sz) +{ + struct piper_priv *piperp; + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(priv_sz, &hw_ops); + if (!hw) + return -ENOMEM; + + hw->flags |= IEEE80211_HW_RX_INCLUDES_FCS + | IEEE80211_HW_SIGNAL_DBM + | IEEE80211_HW_NOISE_DBM + | IEEE80211_HW_SPECTRUM_MGMT + | IEEE80211_HW_NO_STACK_DYNAMIC_PS +#if !WANT_SHORT_PREAMBLE_SUPPORT + | IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE +#endif + /* | IEEE80211_HW_SPECTRUM_MGMT TODO: Turn this on when we are ready*/; + + hw->queues = 1; + hw->ampdu_queues = 0; + hw->extra_tx_headroom = 4 + sizeof(struct ofdm_hdr); + piperp = hw->priv; + *priv = piperp; + piperp->pstats.tx_queue.len = 0; + piperp->pstats.tx_queue.limit = 1; + piperp->pstats.tx_queue.count = 0; + piperp->areWeAssociated = false; + memset(&piperp->pstats.ll_stats, 0, sizeof(piperp->pstats.ll_stats)); + piperp->hw = hw; + + return 0; +} +EXPORT_SYMBOL_GPL(piper_alloc_hw); + +/* + * This routine is called by the remove function to free the memory + * allocated by piper_alloc_hw. + */ +void piper_free_hw(struct piper_priv *piperp) +{ + ieee80211_free_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_free_hw); + +/* + * This routine is called by the probe routine to register + * with mac80211. + */ +int piper_register_hw(struct piper_priv *priv, struct device *dev, + struct digi_rf_ops *rf) +{ + struct ieee80211_hw *hw = priv->hw; + u8 macaddr[8]; + u32 temp; + int i, ret; + + dprintk(DVVERBOSE, "\n"); + + priv->rf = rf; + for (i = 0; i < rf->n_bands; i++) { + enum ieee80211_band b = rf->bands[i].band; + hw->wiphy->bands[b] = &rf->bands[i]; + } + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_ADHOC) + | BIT(NL80211_IFTYPE_STATION) +/* TODO: Enable this | BIT(NL80211_IFTYPE_MESH_POINT) */ + ; + hw->channel_change_time = rf->channelChangeTime; + hw->vif_data_size = 0; + hw->sta_data_size = 0; + hw->max_rates = IEEE80211_TX_MAX_RATES; + hw->max_rate_tries = 5; /* completely arbitrary, and apparently ignored by the rate algorithm */ + hw->max_signal = rf->maxSignal; + hw->max_listen_interval = 10; /* I don't think APs will work with values larger than 4 actually */ + + SET_IEEE80211_DEV(hw, dev); + + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID0)); + memcpy(macaddr, &temp, sizeof(temp)); + temp = cpu_to_be32(priv->ac->rd_reg(priv, MAC_STA_ID1)); + memcpy(&macaddr[4], &temp, sizeof(temp)); + SET_IEEE80211_PERM_ADDR(hw, macaddr); + + if ((ret = ieee80211_register_hw(hw)) != 0) { + dprintk(DERROR, "unable to register ieee80211 hw\n"); + goto error; + } + + printk(KERN_INFO PIPER_DRIVER_NAME ": registered as %s\n", + wiphy_name(hw->wiphy)); + + init_timer(&priv->led_timer); + priv->led_state = led_shutdown; + priv->led_timer.function = led_timer_fn; + priv->led_timer.data = (unsigned long) priv; + priv->led_timer.expires = jiffies + LED_TIMER_RATE; + add_timer(&priv->led_timer); + +error: + return ret; +} +EXPORT_SYMBOL_GPL(piper_register_hw); + + +void piper_unregister_hw(struct piper_priv *piperp) +{ + dprintk(DVVERBOSE, "\n"); + del_timer_sync(&piperp->led_timer); + piper_set_status_led(piperp->hw, led_shutdown); + ieee80211_unregister_hw(piperp->hw); +} +EXPORT_SYMBOL_GPL(piper_unregister_hw); + + +MODULE_DESCRIPTION("Digi Piper WLAN core"); +MODULE_AUTHOR("contact support@digi.com for information about this code"); +MODULE_LICENSE("GPL"); |