/* * drivers/net/wireless/mwl8k.c * Driver for Marvell TOPDOG 802.11 Wireless cards * * Copyright (C) 2008, 2009, 2010 Marvell Semiconductor Inc. * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied.
*/
/* Module parameters */ staticbool ap_mode_default;
module_param(ap_mode_default, bool, 0);
MODULE_PARM_DESC(ap_mode_default, "Set to 1 to make ap mode the default instead of sta mode");
/* txpriorities are mapped with hw queues. * Each hw queue has a txpriority.
*/ #define TOTAL_HW_TX_QUEUES 8
/* Each HW queue can have one AMPDU stream. * But, because one of the hw queue is reserved, * maximum AMPDU queues that can be created are * one short of total tx queues.
*/ #define MWL8K_NUM_AMPDU_STREAMS (TOTAL_HW_TX_QUEUES - 1)
#define MWL8K_NUM_CHANS 18
struct rxd_ops { int rxd_size; void (*rxd_init)(void *rxd, dma_addr_t next_dma_addr); void (*rxd_refill)(void *rxd, dma_addr_t addr, int len); int (*rxd_process)(void *rxd, struct ieee80211_rx_status *status,
__le16 *qos, s8 *noise);
};
/* XXX need to convert this to handle multiple interfaces */ bool capture_beacon;
u8 capture_bssid[ETH_ALEN]; struct sk_buff *beacon_skb;
/* * This FJ worker has to be global as it is scheduled from the * RX handler. At this point we don't know which interface it * belongs to until the list of bssids waiting to complete join * is checked.
*/ struct work_struct finalize_join_worker;
/* Tasklet to perform TX reclaim. */ struct tasklet_struct poll_tx_task;
/* Tasklet to perform RX. */ struct tasklet_struct poll_rx_task;
/* Most recently reported noise in dBm */
s8 noise;
/* * preserve the queue configurations so they can be restored if/when * the firmware image is swapped.
*/ struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_WMM_QUEUES];
/* To perform the task of reloading the firmware */ struct work_struct fw_reload; bool hw_restart_in_progress;
/* A flag to indicate is HW crypto is enabled for this bssid */ bool is_hw_crypto_enabled;
}; #define MWL8K_VIF(_vif) ((struct mwl8k_vif *)&((_vif)->drv_priv)) #define IEEE80211_KEY_CONF(_u8) ((struct ieee80211_key_conf *)(_u8))
if (hdrlen != sizeof(*tr))
skb_pull(skb, sizeof(*tr) - hdrlen);
}
#define REDUCED_TX_HEADROOM 8
staticvoid
mwl8k_add_dma_header(struct mwl8k_priv *priv, struct sk_buff *skb, int head_pad, int tail_pad)
{ struct ieee80211_hdr *wh; int hdrlen; int reqd_hdrlen; struct mwl8k_dma_data *tr;
/* * Add a firmware DMA header; the firmware requires that we * present a 2-byte payload length followed by a 4-address * header (without QoS field), followed (optionally) by any * WEP/ExtIV header (but only filled in for CCMP).
*/
wh = (struct ieee80211_hdr *)skb->data;
hdrlen = ieee80211_hdrlen(wh->frame_control);
/* * Check if skb_resize is required because of * tx_headroom adjustment.
*/ if (priv->ap_fw && (hdrlen < (sizeof(struct ieee80211_cts)
+ REDUCED_TX_HEADROOM))) { if (pskb_expand_head(skb, REDUCED_TX_HEADROOM, 0, GFP_ATOMIC)) {
/* * Firmware length is the length of the fully formed "802.11 * payload". That is, everything except for the 802.11 header. * This includes all crypto material including the MIC.
*/
tr->fwlen = cpu_to_le16(skb->len - sizeof(*tr) + tail_pad);
}
key_conf = NULL; if (ieee80211_is_data(wh->frame_control))
key_conf = tx_info->control.hw_key;
/* * Make sure the packet header is in the DMA header format (4-address * without QoS), and add head & tail padding when HW crypto is enabled. * * We have the following trailer padding requirements: * - WEP: 4 trailer bytes (ICV) * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) * - CCMP: 8 trailer bytes (MIC)
*/
data_pad = 0; if (key_conf != NULL) {
head_pad = key_conf->iv_len; switch (key_conf->cipher) { case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_WEP104:
data_pad = 4; break; case WLAN_CIPHER_SUITE_TKIP:
data_pad = 12; break; case WLAN_CIPHER_SUITE_CCMP:
data_pad = 8; break;
}
}
mwl8k_add_dma_header(priv, skb, head_pad, data_pad);
}
/* Must be called only when the card's reception is completely halted */ staticvoid mwl8k_rxq_deinit(struct ieee80211_hw *hw, int index)
{ struct mwl8k_priv *priv = hw->priv; struct mwl8k_rx_queue *rxq = priv->rxq + index; int i;
if (rxq->rxd == NULL) return;
for (i = 0; i < MWL8K_RX_DESCS; i++) { if (rxq->buf[i].skb != NULL) {
dma_unmap_single(&priv->pdev->dev,
dma_unmap_addr(&rxq->buf[i], dma),
MWL8K_RX_MAXSZ, DMA_FROM_DEVICE);
dma_unmap_addr_set(&rxq->buf[i], dma, 0);
/* * Scan a list of BSSIDs to process for finalize join. * Allows for extension to process multiple BSSIDs.
*/ staticinlineint
mwl8k_capture_bssid(struct mwl8k_priv *priv, struct ieee80211_hdr *wh)
{ return priv->capture_beacon &&
ieee80211_is_beacon(wh->frame_control) &&
ether_addr_equal_64bits(wh->addr3, priv->capture_bssid);
}
/* * Use GFP_ATOMIC as rxq_process is called from * the primary interrupt handler, memory allocation call * must not sleep.
*/
priv->beacon_skb = skb_copy(skb, GFP_ATOMIC); if (priv->beacon_skb != NULL)
ieee80211_queue_work(hw, &priv->finalize_join_worker);
}
rxq->head++; if (rxq->head == MWL8K_RX_DESCS)
rxq->head = 0;
rxq->rxd_count--;
wh = &((struct mwl8k_dma_data *)skb->data)->wh;
/* * Check for a pending join operation. Save a * copy of the beacon and schedule a tasklet to * send a FINALIZE_JOIN command to the firmware.
*/ if (mwl8k_capture_bssid(priv, (void *)skb->data))
mwl8k_save_beacon(hw, skb);
if (ieee80211_has_protected(wh->frame_control)) {
/* Check if hw crypto has been enabled for * this bss. If yes, set the status flags * accordingly
*/
mwl8k_vif = mwl8k_find_vif_bss(&priv->vif_list,
wh->addr1);
if (mwl8k_vif != NULL &&
mwl8k_vif->is_hw_crypto_enabled) { /* * When MMIC ERROR is encountered * by the firmware, payload is * dropped and only 32 bytes of * mwl8k Firmware header is sent * to the host. * * We need to add four bytes of * key information. In it * MAC80211 expects keyidx set to * 0 for triggering Counter * Measure of MMIC failure.
*/ if (status.flag & RX_FLAG_MMIC_ERROR) { struct mwl8k_dma_data *tr;
tr = (struct mwl8k_dma_data *)skb->data;
memset((void *)&(tr->data), 0, 4);
pkt_len += 4;
}
for (i = 0; i < mwl8k_tx_queues(priv); i++) { struct mwl8k_tx_queue *txq = priv->txq + i; int fw_owned = 0; int drv_owned = 0; int unused = 0; int desc;
/* * Must be called with priv->fw_mutex held and tx queues stopped.
*/ #define MWL8K_TX_WAIT_TIMEOUT_MS 5000
staticint mwl8k_tx_wait_empty(struct ieee80211_hw *hw)
{ struct mwl8k_priv *priv = hw->priv;
DECLARE_COMPLETION_ONSTACK(tx_wait); int retry; int rc;
might_sleep();
/* Since fw restart is in progress, allow only the firmware * commands from the restart code and block the other * commands since they are going to fail in any case since * the firmware has crashed
*/ if (priv->hw_restart_in_progress) { if (priv->hw_restart_owner == current) return 0; else return -EBUSY;
}
if (atomic_read(&priv->watchdog_event_pending)) return 0;
/* * The TX queues are stopped at this point, so this test * doesn't need to take ->tx_lock.
*/ if (!priv->pending_tx_pkts) return 0;
retry = 1;
rc = 0;
spin_lock_bh(&priv->tx_lock);
priv->tx_wait = &tx_wait; while (!rc) { int oldcount; unsignedlong timeout;
switch (tid) { case 0: case 3: return IEEE80211_AC_BE; case 1: case 2: return IEEE80211_AC_BK; case 4: case 5: return IEEE80211_AC_VI; case 6: case 7: return IEEE80211_AC_VO; default: return -1;
}
}
/* The firmware will fill in the rate information * for each packet that gets queued in the hardware * and these macros will interpret that info.
*/
/* Mark descriptor as unused */
tx_desc->pkt_phys_addr = 0;
tx_desc->pkt_len = 0;
info = IEEE80211_SKB_CB(skb); if (ieee80211_is_data(wh->frame_control)) {
rcu_read_lock();
sta = ieee80211_find_sta_by_ifaddr(hw, wh->addr1,
wh->addr2); if (sta) {
sta_info = MWL8K_STA(sta);
BUG_ON(sta_info == NULL);
rate_info = le16_to_cpu(tx_desc->rate_info); /* If rate is < 6.5 Mpbs for an ht station * do not form an ampdu. If the station is a * legacy station (format = 0), do not form an * ampdu
*/ if (RI_RATE_ID_MCS(rate_info) < 1 ||
RI_FORMAT(rate_info) == 0) {
sta_info->is_ampdu_allowed = false;
} else {
sta_info->is_ampdu_allowed = true;
}
}
rcu_read_unlock();
}
ieee80211_tx_info_clear_status(info);
/* Rate control is happening in the firmware. * Ensure no tx rate is being reported.
*/
info->status.rates[0].idx = -1;
info->status.rates[0].count = 1;
if (MWL8K_TXD_SUCCESS(status))
info->flags |= IEEE80211_TX_STAT_ACK;
ieee80211_tx_status_irqsafe(hw, skb);
processed++;
}
return processed;
}
/* must be called only when the card's transmit is completely halted */ staticvoid mwl8k_txq_deinit(struct ieee80211_hw *hw, int index)
{ struct mwl8k_priv *priv = hw->priv; struct mwl8k_tx_queue *txq = priv->txq + index;
/* caller must hold priv->stream_lock when calling the stream functions */ staticstruct mwl8k_ampdu_stream *
mwl8k_add_stream(struct ieee80211_hw *hw, struct ieee80211_sta *sta, u8 tid)
{ struct mwl8k_ampdu_stream *stream; struct mwl8k_priv *priv = hw->priv; int i;
for (i = 0; i < MWL8K_NUM_AMPDU_STREAMS; i++) {
stream = &priv->ampdu[i]; if (stream->state == AMPDU_NO_STREAM) {
stream->sta = sta;
stream->state = AMPDU_STREAM_NEW;
stream->tid = tid;
stream->idx = i;
wiphy_debug(hw->wiphy, "Added a new stream for %pM %d",
sta->addr, tid); return stream;
}
} return NULL;
}
staticint
mwl8k_start_stream(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream)
{ int ret;
/* if the stream has already been started, don't start it again */ if (stream->state != AMPDU_STREAM_NEW) return 0;
ret = ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); if (ret)
wiphy_debug(hw->wiphy, "Failed to start stream for %pM %d: " "%d\n", stream->sta->addr, stream->tid, ret); else
wiphy_debug(hw->wiphy, "Started stream for %pM %d\n",
stream->sta->addr, stream->tid); return ret;
}
if (tx_stats->start_time == 0)
tx_stats->start_time = jiffies;
/* reset the packet count after each second elapses. If the number of * packets ever exceeds the ampdu_min_traffic threshold, we will allow * an ampdu stream to be started.
*/ if (time_after(jiffies, (unsignedlong)tx_stats->start_time + HZ)) {
tx_stats->pkts = 0;
tx_stats->start_time = 0;
} else
tx_stats->pkts++;
}
/* The hardware ampdu queues start from 5. * txpriorities for ampdu queues are * 5 6 7 0 1 2 3 4 ie., queue 5 is highest * and queue 3 is lowest (queue 4 is reserved)
*/ #define BA_QUEUE 5
/* Queue ADDBA request in the respective data queue. While setting up * the ampdu stream, mac80211 queues further packets for that * particular ra/tid pair. However, packets piled up in the hardware * for that ra/tid pair will still go out. ADDBA request and the * related data packets going out from different queues asynchronously * will cause a shift in the receiver window which might result in * ampdu packets getting dropped at the receiver after the stream has * been setup.
*/ if (unlikely(ieee80211_is_action(wh->frame_control) &&
mgmt->u.action.category == WLAN_CATEGORY_BACK &&
mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ &&
priv->ap_fw)) {
u16 capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab);
tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2;
index = mwl8k_tid_queue_mapping(tid);
}
txpriority = index;
if (priv->ap_fw && sta && sta->deflink.ht_cap.ht_supported && !eapol_frame &&
ieee80211_is_data_qos(wh->frame_control)) {
tid = qos & 0xf;
mwl8k_tx_count_packet(sta, tid);
spin_lock(&priv->stream_lock);
stream = mwl8k_lookup_stream(hw, sta->addr, tid); if (stream != NULL) { if (stream->state == AMPDU_STREAM_ACTIVE) {
WARN_ON(!(qos & MWL8K_QOS_ACK_POLICY_BLOCKACK));
txpriority = (BA_QUEUE + stream->idx) %
TOTAL_HW_TX_QUEUES; if (stream->idx <= 1)
index = stream->idx +
MWL8K_TX_WMM_QUEUES;
} elseif (stream->state == AMPDU_STREAM_NEW) { /* We get here if the driver sends us packets * after we've initiated a stream, but before * our ampdu_action routine has been called * with IEEE80211_AMPDU_TX_START to get the SSN * for the ADDBA request. So this packet can * go out with no risk of sequence number * mismatch. No special handling is required.
*/
} else { /* Drop packets that would go out after the * ADDBA request was sent but before the ADDBA * response is received. If we don't do this, * the recipient would probably receive it * after the ADDBA request with SSN 0. This * will cause the recipient's BA receive window * to shift, which would cause the subsequent * packets in the BA stream to be discarded. * mac80211 queues our packets for us in this * case, so this is really just a safety check.
*/
wiphy_warn(hw->wiphy, "Cannot send packet while ADDBA " "dialog is underway.\n");
spin_unlock(&priv->stream_lock);
dev_kfree_skb(skb); return;
}
} else { /* Defer calling mwl8k_start_stream so that the current * skb can go out before the ADDBA request. This * prevents sequence number mismatch at the recepient * as described above.
*/ if (mwl8k_ampdu_allowed(sta, tid)) {
stream = mwl8k_add_stream(hw, sta, tid); if (stream != NULL)
start_ba_session = true;
}
}
spin_unlock(&priv->stream_lock);
} else {
qos &= ~MWL8K_QOS_ACK_POLICY_MASK;
qos |= MWL8K_QOS_ACK_POLICY_NORMAL;
}
if (dma_mapping_error(&priv->pdev->dev, dma)) {
wiphy_debug(hw->wiphy, "failed to dma map skb, dropping TX frame.\n"); if (start_ba_session) {
spin_lock(&priv->stream_lock);
mwl8k_remove_stream(hw, stream);
spin_unlock(&priv->stream_lock);
}
dev_kfree_skb(skb); return;
}
spin_lock_bh(&priv->tx_lock);
txq = priv->txq + index;
/* Mgmt frames that go out frequently are probe * responses. Other mgmt frames got out relatively * infrequently. Hence reserve 2 buffers so that * other mgmt frames do not get dropped due to an * already queued probe response in one of the * reserved buffers.
*/
txq->tail++; if (txq->tail == MWL8K_TX_DESCS)
txq->tail = 0;
mwl8k_tx_start(priv);
spin_unlock_bh(&priv->tx_lock);
/* Initiate the ampdu session here */ if (start_ba_session) {
spin_lock(&priv->stream_lock); if (mwl8k_start_stream(hw, stream))
mwl8k_remove_stream(hw, stream);
spin_unlock(&priv->stream_lock);
}
}
/* * Firmware access. * * We have the following requirements for issuing firmware commands: * - Some commands require that the packet transmit path is idle when * the command is issued. (For simplicity, we'll just quiesce the * transmit path for every command.) * - There are certain sequences of commands that need to be issued to * the hardware sequentially, with no other intervening commands. * * This leads to an implementation of a "firmware lock" as a mutex that * can be taken recursively, and which is taken by both the low-level * command submission function (mwl8k_post_cmd) as well as any users of * that function that require issuing of an atomic sequence of commands, * and quiesces the transmit path whenever it's taken.
*/ staticint mwl8k_fw_lock(struct ieee80211_hw *hw)
{ struct mwl8k_priv *priv = hw->priv;
/* Before posting firmware commands that could change the hardware * characteristics, make sure that all BSSes are stopped temporary. * Enable these stopped BSSes after completion of the commands
*/
rc = mwl8k_fw_lock(hw); if (rc) return rc;
if (priv->ap_fw && priv->running_bsses) { switch (le16_to_cpu(cmd->code)) { case MWL8K_CMD_SET_RF_CHANNEL: case MWL8K_CMD_RADIO_CONTROL: case MWL8K_CMD_RF_TX_POWER: case MWL8K_CMD_TX_POWER: case MWL8K_CMD_RF_ANTENNA: case MWL8K_CMD_RTS_THRESHOLD: case MWL8K_CMD_MIMO_CONFIG:
bitmap = priv->running_bsses;
mwl8k_enable_bsses(hw, false, bitmap); break;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.