diff options
Diffstat (limited to 'drivers/net/wireless/digiPiper/digiTx.c')
-rw-r--r-- | drivers/net/wireless/digiPiper/digiTx.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/drivers/net/wireless/digiPiper/digiTx.c b/drivers/net/wireless/digiPiper/digiTx.c new file mode 100644 index 000000000000..4bea846a42a4 --- /dev/null +++ b/drivers/net/wireless/digiPiper/digiTx.c @@ -0,0 +1,604 @@ +/* + * digiTx.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 are related to transmitting + * frames. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/timer.h> + +#include "pipermain.h" +#include "mac.h" +#include "phy.h" +#include "digiPs.h" + +#define FRAME_CONTROL_FIELD_OFFSET (sizeof(struct tx_frame_hdr) + sizeof(struct psk_cck_hdr)) +#define AES_TIMEOUT (200) + +#define TX_DEBUG (1) + +#if TX_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 + +/* + * Adds an entry into the tx queue. + */ +int piper_tx_enqueue(struct piper_priv *piperp, struct sk_buff *skb, tx_skb_return_cb_t skb_return_cb) +{ + unsigned long flags; + int result = -1; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head) != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_head].skb = skb; + piperp->tx_queue[piperp->tx_queue_head].skb_return_cb = skb_return_cb; + piperp->tx_queue_head = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_head); + piperp->tx_queue_count++; + result = 0; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); + + return result; +} +EXPORT_SYMBOL_GPL(piper_tx_enqueue); + + +/* + * Returns the skb for the current element. + */ +struct sk_buff *piper_tx_getqueue(struct piper_priv *piperp) +{ + if (piperp->tx_queue_head == piperp->tx_queue_tail) { + return NULL; + } else { + return piperp->tx_queue[piperp->tx_queue_tail].skb; + } +} +EXPORT_SYMBOL_GPL(piper_tx_getqueue); + +/* + * Returns the skb buffer call back for the current element. + */ +static inline tx_skb_return_cb_t piper_tx_getqueue_return_fn(struct piper_priv *piperp) +{ + return piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb; +} + + +/* + * Called to advance the queue tail. + */ +static inline void piper_tx_queue_next(struct piper_priv *piperp) +{ + unsigned long flags; + + spin_lock_irqsave(&piperp->tx_queue_lock, flags); + if (piperp->tx_queue_head != piperp->tx_queue_tail) { + piperp->tx_queue[piperp->tx_queue_tail].skb = NULL; + piperp->tx_queue[piperp->tx_queue_tail].skb_return_cb = NULL; + piperp->tx_queue_tail = NEXT_TX_QUEUE_INDEX(piperp->tx_queue_tail); + piperp->tx_queue_count--; + } + spin_unlock_irqrestore(&piperp->tx_queue_lock, flags); +} + +/* + * Called when we unload to clear any remaining queue entries. + */ +void piper_empty_tx_queue(struct piper_priv *piperp) +{ + while (piper_tx_getqueue(piperp)) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + + ieee80211_tx_info_clear_status(info); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, piper_tx_getqueue(piperp)); + piper_tx_queue_next(piperp); + } +} +EXPORT_SYMBOL_GPL(piper_empty_tx_queue); + + +bool piper_tx_queue_half_full(struct piper_priv *piperp) +{ + return (piperp->tx_queue_count >= (PIPER_TX_QUEUE_SIZE >> 1)); +} +EXPORT_SYMBOL_GPL(piper_tx_queue_half_full); + + + +/* + * This routine writes a frame using H/W AES encryption. + * + * Arguments + * digi context + * buffer pointer to start of frame + * length number of bytes in frame + * + * Return Values + * 0 success + * !0 transmit failed + */ +static int piper_write_aes(struct piper_priv *piperp, unsigned char *buffer, + unsigned int length) +{ + int result; + int timeout = AES_TIMEOUT; + unsigned long spinLockFlags; + + /* + * Step 1: Wait for AES to become ready. + */ + spin_lock_irqsave(&piperp->aesLock, spinLockFlags); + while (piperp->ac->rd_reg(piperp, BB_RSSI) & BB_RSSI_EAS_BUSY) { + timeout--; + if (timeout == 0) { + /* + * If we come here, then AES busy appears to be stuck high. It should only be + * high for a maximum of about 80 us when it is encrypting a transmit frame. + * Our timeout value is high enough to guarantee that the engine has had enough + * time to complete the transmit. Apparently there is data stuck in the FIFO + * from either a previous transmit or receive. + */ + digi_dbg("write AES, AES busy stuck on\n"); + digiWifiDumpRegisters(piperp, MAIN_REGS | MAC_REGS); + /* + * We recover by simply continuing on. Step 3 writes to the AES control + * register. This will reset the AES engine and clear the error condition. + */ + break; + } + udelay(1); + } + + /* + * Step 2: Write the unencrypted part of the frame into the normal + * data FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, buffer, + _80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE); + + /* + * Step 3: Write to the AES control register. Writing to it puts + * AES H/W engine into transmit mode. We also make sure + * the AES mode is set correctly. + */ + piperp->ac->wr_reg(piperp, BB_AES_CTL, 0, op_write); + + /* + * Step 4: Write the expanded AES key into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + (unsigned char *)piperp->key[piperp->tx_aes_key].expandedKey, + EXPANDED_KEY_LENGTH); + + /* + * Step 5: Write the AES IV and headers into the AES FIFO. + */ + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, (unsigned char *)piperp->tx_aes_blob, + AES_BLOB_LENGTH); + + /* + * Step 6: Now, finally, write the part of the frame that needs to + * be encrypted into the AES FIFO. + */ + result = + piperp->ac->wr_fifo(piperp, BB_AES_FIFO, + &buffer[_80211_HEADER_LENGTH + TX_HEADER_LENGTH + PIPER_EXTIV_SIZE], + length - (_80211_HEADER_LENGTH + TX_HEADER_LENGTH + + PIPER_EXTIV_SIZE)); + + spin_unlock_irqrestore(&piperp->aesLock, spinLockFlags); + + return result; +} + +/* + * Determine what bit rate the next retry should be sent at. + * + * The mac80211 library passes us an array of tx bit rates. Each entry + * has a rate index and a limit (max number of retries at that rate). + * We use the rate index to build the H/W transmit header. The limit + * is decremented each time we retry. When it reaches zero, we try the + * next rate in the array. + */ +static struct ieee80211_rate *get_tx_rate(struct piper_priv *piperp, struct ieee80211_tx_info *info) +{ + struct ieee80211_rate *ret = NULL; + + if (piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index] >= + info->control.rates[piperp->pstats.tx_retry_index].count) { + piperp->pstats.tx_retry_index++; + } + + if (piperp->pstats.tx_retry_index >= IEEE80211_TX_MAX_RATES) { + return NULL; /* don't go beyond the end of the rates array */ + } + + if (piperp->pstats.tx_retry_index == 0) { + ret = ieee80211_get_tx_rate(piperp->hw, info); + } else { + ret = ieee80211_get_alt_retry_rate(piperp->hw, info, piperp->pstats.tx_retry_index - 1); + } + + if (ret != NULL) { + if (piperp->calibrationTxRate) { + ret = piperp->calibrationTxRate; + } + } + + ret = piper_ps_check_rate(piperp, ret); + + return ret; +} + +/* + * This function returns a value for the contention window in microseconds. We + * start with the contention window at CW_MIN and double it everytime we have to + * retry. + */ +static u16 piper_get_cw(struct piper_priv *piperp, bool isFirstTime) +{ + static u16 cw = DEFAULT_CW_MIN; + + if (isFirstTime) { + cw = DEFAULT_CW_MIN; + } else { + cw <<= 1; + if (cw > DEFAULT_CW_MAX) { + cw = DEFAULT_CW_MAX; + } + } + return (cw + (10 * (piperp->rand() & (cw - 1)))) & 0xffff; +} + +/* + * This function will prepend an RTS or CTS to self frame ahead of the current + * TX frame. This is done if the wantRts or wantCts flag is set. The mac80211 + * library determines if either of these flags is set. + * + * The RTS or CTS message is written into the transmit FIFO ahead of the + * data frame. Note that RTS and CTS messages are always sent in the clear + * so we do not have to worry about encryption. + * + * Our caller, the master transmit routine, is responsible for setting the + * transmit hold bit before calling us and clearing it after the data frame + * has been written into the FIFO. This ensures that the RTS/CTS frame is + * not transmitted until after the data frame is ready to go. + * + * Also note that if we are unable to send the RTS/CTS frame, then the H/W + * is smart enough to also about the data frame. So we will not send + * the data frame without the RTS/CTS frame. + */ +static void handle_rts_cts(struct piper_priv *piperp, + struct ieee80211_tx_info *txInfo, unsigned int frameType) +{ + piperp->tx_rts = false; + + if (frameType == TYPE_DATA) { + unsigned int header[2]; + struct ieee80211_rate *rate = NULL; + bool wantCts = (!!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_CTS_PROTECT) + | piperp->tx_cts); + bool wantRts = !!(txInfo->control.rates[piperp->pstats.tx_retry_index].flags + & IEEE80211_TX_RC_USE_RTS_CTS); + + if ((wantRts) || (wantCts)) { + /* + * If we are sending an RTS or a CTS, then get the rate information. + */ + if (piperp->calibrationTxRate) { + rate = piperp->calibrationTxRate; + } else { + rate = ieee80211_get_rts_cts_rate(piperp->hw, txInfo); + } + if (rate == NULL) { + digi_dbg + ("ieee80211_get_rts_cts_rate(digi->hw, txInfo) returned NULL!\n"); + } + } + if ((wantRts) && (rate)) { + /* + * We're sending an RTS, so load it into the FIFO. + */ + struct ieee80211_rts rtsFrame; + + ieee80211_rts_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, txInfo, + &rtsFrame); + /* + * If we come here, then we need to send an RTS frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_rts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&rtsFrame, + sizeof(rtsFrame)); + if (piperp->pstats.tx_total_tetries != 0) { + piperp->pstats.ll_stats.dot11RTSFailureCount++; + } + piperp->tx_rts = true; + } else if ((wantCts) && (rate)) { + /* + * We're sending a CTS, so load it into the FIFO. + */ + struct ieee80211_cts ctsFrame; + + ieee80211_ctstoself_get(piperp->hw, txInfo->control.vif, + piper_tx_getqueue(piperp)->data + TX_HEADER_LENGTH, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txInfo, &ctsFrame); + /* + * At the time this code was written, the mac80211 library had + * a bug in the ieee80211_ctstoself_get which caused it to copy + * the wrong MAC address into the cts frame. So we copy the + * right one (ours) in now. + */ + memcpy(piperp->ctsFrame.ra, piperp->hw->wiphy->perm_addr, ETH_ALEN); + + /* + * If we come here, then we need to send a CTS to self frame ahead of the + * current data frame. + */ + phy_set_plcp((unsigned char *)header, sizeof(struct ieee80211_cts), + rate, 0); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)header, + TX_HEADER_LENGTH); + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, (unsigned char *)&ctsFrame, + sizeof(ctsFrame)); + } + } +} + +/* + * This routine is called to report the result of a transmit operation to + * mac80211. It is used for both successful transmissions and failures. + * It sends the result to the stack, removes the current tx frame from the + * queue, and then wakes + * up the transmit queue. + */ +void packet_tx_done(struct piper_priv *piperp, tx_result_t result, + int signal_strength) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + int i; + struct sk_buff *skb; + unsigned long flags; + +#define WANT_TRANSMIT_RESULT (0) +#if WANT_TRANSMIT_RESULT + const char *resultText[] = + { + "RECEIVED_ACK", + "TX_COMPLETE", + "OUT_OF_RETRIES", + "TX_NOT_DONE" + }; +#endif + del_timer_sync(&piperp->tx_timer); + piperp->expectingAck = false; + +#if WANT_TRANSMIT_RESULT + printk(KERN_ERR "Transmit result %s\n", resultText[result]); +#endif + if (piperp->tx_calib_cb) + piperp->tx_calib_cb(piperp); + + if (piper_tx_getqueue(piperp) != NULL) { + skb_pull(piper_tx_getqueue(piperp), TX_HEADER_LENGTH); + + ieee80211_tx_info_clear_status(info); + + /* prepare statistics and pass them to the stack */ + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + info->status.rates[i].count = piperp->pstats.tx_retry_count[i]; + if (info->status.rates[i].count == 0) + info->status.rates[i].idx = -1; + } + + info->status.ack_signal = signal_strength; + info->flags |= (result == RECEIVED_ACK) ? IEEE80211_TX_STAT_ACK : 0; + piperp->pstats.tx_complete_count++; + piperp->pstats.tx_queue.len--; + if (piperp->tx_rts) + piperp->pstats.ll_stats.dot11RTSSuccessCount++; + + skb = piper_tx_getqueue(piperp); + piper_tx_getqueue_return_fn(piperp)(piperp->hw, skb); + + piper_tx_queue_next(piperp); + + if (piper_tx_getqueue(piperp) == NULL) { + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + if (piperp->ps.allowTransmits) { + ieee80211_wake_queues(piperp->hw); + } else { + /* + * Do not wake up the mac80211 Tx queue if we are trying to power + * down. Make sure we set the stopped_tx_queues flag so that we + * know to restart the queues. + */ + piperp->ps.stopped_tx_queues = true; + } + } else { + if (result == OUT_OF_RETRIES) { + /* + * If we come here, then we are being called from the middle of the + * transmit routine and we have to reschedule the transmit task to + * start dequeueing the next frame. + */ + tasklet_hi_schedule(&piperp->tx_tasklet); + } + } + } else { + digi_dbg("packet_tx_done called with empty queue\n"); + } + + piperp->tx_result = TX_NOT_DONE; +} +EXPORT_SYMBOL_GPL(packet_tx_done); + +/* + * This function is the entry point for the transmit tasklet. It + * is called to transmit frames. It will first be called to transmit + * the frame and then to retry if the original transmit fails. So + * it does both the first transmit and the subsequent retries. + * + * Arguments + * context context information + */ +void piper_tx_tasklet(unsigned long context) +{ + struct piper_priv *piperp = (struct piper_priv *)context; + frameControlFieldType_t *fc; + int err; + + piperp->expectingAck = false; + del_timer_sync(&piperp->tx_timer); + if ((piperp->tx_result == RECEIVED_ACK) || (piperp->tx_result == TX_COMPLETE)) { + /* + * We will come here if the receiver task received an ACK, or if we got + * a tx fifo empty interrupt. In these cases the receiver thread or ISR + * schedule the tx tasklet to handle the event rather than calling + * packet_tx_done directly. + */ + packet_tx_done(piperp, piperp->tx_result, piperp->tx_signal_strength); + } + + + /* + * Clear flags here to cover ACK case. We do not clear the flags in the ACK + * routine since it is possible to receive an ACK after we have started the + * next packet. The appropriate interrupts will be reenabled if we decide + * to retransmit. + */ + piperp->clear_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + + if (piper_tx_getqueue(piperp) != NULL) { + struct ieee80211_tx_info *txInfo = IEEE80211_SKB_CB(piper_tx_getqueue(piperp)); + struct ieee80211_rate *txRate = get_tx_rate(piperp, txInfo); + + if (txRate != NULL) { + fc = (frameControlFieldType_t *) + &piper_tx_getqueue(piperp)->data[FRAME_CONTROL_FIELD_OFFSET]; + + /* set the retry bit if this is not the first try */ + if (piperp->pstats.tx_retry_count[0] != 0) + fc->retry = 1; + + piperp->ac->wr_reg(piperp, MAC_BACKOFF, + piper_get_cw(piperp, + (piperp->pstats.tx_retry_count[0] == 0)), + op_write); + + /* + * Build the H/W transmit header. The transmit header is rebuilt on each + * retry because it has the TX rate information which may change for + * retries. + */ + phy_set_plcp(piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len - TX_HEADER_LENGTH, + txRate, piperp->use_hw_aes ? 8 : 0); + + /* + * Pause the transmitter so that we don't start transmitting before we + * are ready. + */ + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, BB_GENERAL_CTL_TX_HOLD, op_or); + + handle_rts_cts(piperp, txInfo, fc->type); + + if (piperp->use_hw_aes == true && txInfo->control.hw_key != NULL) { + err = + piper_write_aes(piperp, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } else { + err = + piperp->ac->wr_fifo(piperp, BB_DATA_FIFO, piper_tx_getqueue(piperp)->data, + piper_tx_getqueue(piperp)->len); + } + + /* Clear any pending TX interrupts */ + piperp->ac->wr_reg(piperp, BB_IRQ_STAT, + BB_IRQ_MASK_TX_FIFO_EMPTY | BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT, op_write); + + /* + * Now start the transmitter. + */ + piperp->expectingAck = ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0); + piperp->ac->wr_reg(piperp, BB_GENERAL_CTL, ~BB_GENERAL_CTL_TX_HOLD, + op_and); + + /* + * Set interrupt flags. Use the timeout interrupt if we expect + * an ACK. Use the FIFO empty interrupt if we do not expect an ACK. + */ + if (txInfo->flags & IEEE80211_TX_CTL_NO_ACK) { + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TX_FIFO_EMPTY | + BB_IRQ_MASK_TX_ABORT); + } else { + /* + * We set up a timer to fire in 1/4 second. We should not need it, but somehow + * we seem to miss a timeout interrupt occasionally. Perhaps we encounter a receive + * overrun which causes the H/W to discard the ACK packet without generating + * a timeout. + */ + piperp->tx_timer.expires = jiffies + (HZ >> 2); + add_timer(&piperp->tx_timer); + + /* + * Also set the IRQ mask to listen for timeouts and TX aborts. We will receive + * an ACK (which is handled by the RX routine) if the TX is successful. + */ + piperp->set_irq_mask_bit(piperp, + BB_IRQ_MASK_TIMEOUT | + BB_IRQ_MASK_TX_ABORT); + } + if ((piperp->pstats.tx_total_tetries != 0) && + ((txInfo->flags & IEEE80211_TX_CTL_NO_ACK) == 0)) { + piperp->pstats.ll_stats.dot11ACKFailureCount++; + } + piperp->pstats.tx_retry_count[piperp->pstats.tx_retry_index]++; + piperp->pstats.tx_total_tetries++; + } else { + packet_tx_done(piperp, OUT_OF_RETRIES, 0); + } + } else { + long unsigned int flags; + + spin_lock_irqsave(&piperp->tx_tasklet_lock, flags); + piperp->tx_tasklet_running = false; + spin_unlock_irqrestore(&piperp->tx_tasklet_lock, flags); + } +} +EXPORT_SYMBOL_GPL(piper_tx_tasklet); + + + |