diff options
Diffstat (limited to 'drivers/net/wireless/intel/iwlegacy/3945-mac.c')
-rw-r--r-- | drivers/net/wireless/intel/iwlegacy/3945-mac.c | 3959 |
1 files changed, 3959 insertions, 0 deletions
diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c new file mode 100644 index 000000000000..af1b3e6839fa --- /dev/null +++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c @@ -0,0 +1,3959 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless <ilw@linux.intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci-aspm.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/firmware.h> +#include <linux/etherdevice.h> +#include <linux/if_arp.h> + +#include <net/ieee80211_radiotap.h> +#include <net/mac80211.h> + +#include <asm/div64.h> + +#define DRV_NAME "iwl3945" + +#include "commands.h" +#include "common.h" +#include "3945.h" +#include "iwl-spectrum.h" + +/* + * module name, copyright, version, etc. + */ + +#define DRV_DESCRIPTION \ +"Intel(R) PRO/Wireless 3945ABG/BG Network Connection driver for Linux" + +#ifdef CONFIG_IWLEGACY_DEBUG +#define VD "d" +#else +#define VD +#endif + +/* + * add "s" to indicate spectrum measurement included. + * we add it here to be consistent with previous releases in which + * this was configurable. + */ +#define DRV_VERSION IWLWIFI_VERSION VD "s" +#define DRV_COPYRIGHT "Copyright(c) 2003-2011 Intel Corporation" +#define DRV_AUTHOR "<ilw@linux.intel.com>" + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + + /* module parameters */ +struct il_mod_params il3945_mod_params = { + .sw_crypto = 1, + .restart_fw = 1, + .disable_hw_scan = 1, + /* the rest are 0 by default */ +}; + +/** + * il3945_get_antenna_flags - Get antenna flags for RXON command + * @il: eeprom and antenna fields are used to determine antenna flags + * + * il->eeprom39 is used to determine if antenna AUX/MAIN are reversed + * il3945_mod_params.antenna specifies the antenna diversity mode: + * + * IL_ANTENNA_DIVERSITY - NIC selects best antenna by itself + * IL_ANTENNA_MAIN - Force MAIN antenna + * IL_ANTENNA_AUX - Force AUX antenna + */ +__le32 +il3945_get_antenna_flags(const struct il_priv *il) +{ + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + + switch (il3945_mod_params.antenna) { + case IL_ANTENNA_DIVERSITY: + return 0; + + case IL_ANTENNA_MAIN: + if (eeprom->antenna_switch_type) + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK; + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK; + + case IL_ANTENNA_AUX: + if (eeprom->antenna_switch_type) + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK; + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK; + } + + /* bad antenna selector value */ + IL_ERR("Bad antenna selector value (0x%x)\n", + il3945_mod_params.antenna); + + return 0; /* "diversity" is default if error */ +} + +static int +il3945_set_ccmp_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + __le16 key_flags = 0; + int ret; + + key_flags |= (STA_KEY_FLG_CCMP | STA_KEY_FLG_MAP_KEY_MSK); + key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + + if (sta_id == il->hw_params.bcast_id) + key_flags |= STA_KEY_MULTICAST_MSK; + + keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + keyconf->hw_key_idx = keyconf->keyidx; + key_flags &= ~STA_KEY_FLG_INVALID; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].keyinfo.cipher = keyconf->cipher; + il->stations[sta_id].keyinfo.keylen = keyconf->keylen; + memcpy(il->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); + + memcpy(il->stations[sta_id].sta.key.key, keyconf->key, keyconf->keylen); + + if ((il->stations[sta_id].sta.key. + key_flags & STA_KEY_FLG_ENCRYPT_MSK) == STA_KEY_FLG_NO_ENC) + il->stations[sta_id].sta.key.key_offset = + il_get_free_ucode_key_idx(il); + /* else, we are overriding an existing key => no need to allocated room + * in uCode. */ + + WARN(il->stations[sta_id].sta.key.key_offset == WEP_INVALID_OFFSET, + "no space for a new key"); + + il->stations[sta_id].sta.key.key_flags = key_flags; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + D_INFO("hwcrypto: modify ucode station key info\n"); + + ret = il_send_add_sta(il, &il->stations[sta_id].sta, CMD_ASYNC); + + spin_unlock_irqrestore(&il->sta_lock, flags); + + return ret; +} + +static int +il3945_set_tkip_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + return -EOPNOTSUPP; +} + +static int +il3945_set_wep_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + return -EOPNOTSUPP; +} + +static int +il3945_clear_sta_key_info(struct il_priv *il, u8 sta_id) +{ + unsigned long flags; + struct il_addsta_cmd sta_cmd; + + spin_lock_irqsave(&il->sta_lock, flags); + memset(&il->stations[sta_id].keyinfo, 0, sizeof(struct il_hw_key)); + memset(&il->stations[sta_id].sta.key, 0, sizeof(struct il4965_keyinfo)); + il->stations[sta_id].sta.key.key_flags = STA_KEY_FLG_NO_ENC; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + D_INFO("hwcrypto: clear ucode station key info\n"); + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +static int +il3945_set_dynamic_key(struct il_priv *il, struct ieee80211_key_conf *keyconf, + u8 sta_id) +{ + int ret = 0; + + keyconf->hw_key_idx = HW_KEY_DYNAMIC; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + ret = il3945_set_ccmp_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_TKIP: + ret = il3945_set_tkip_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + ret = il3945_set_wep_dynamic_key_info(il, keyconf, sta_id); + break; + default: + IL_ERR("Unknown alg: %s alg=%x\n", __func__, keyconf->cipher); + ret = -EINVAL; + } + + D_WEP("Set dynamic key: alg=%x len=%d idx=%d sta=%d ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, sta_id, ret); + + return ret; +} + +static int +il3945_remove_static_key(struct il_priv *il) +{ + int ret = -EOPNOTSUPP; + + return ret; +} + +static int +il3945_set_static_key(struct il_priv *il, struct ieee80211_key_conf *key) +{ + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) + return -EOPNOTSUPP; + + IL_ERR("Static key invalid: cipher %x\n", key->cipher); + return -EINVAL; +} + +static void +il3945_clear_free_frames(struct il_priv *il) +{ + struct list_head *element; + + D_INFO("%d frames on pre-allocated heap on clear.\n", il->frames_count); + + while (!list_empty(&il->free_frames)) { + element = il->free_frames.next; + list_del(element); + kfree(list_entry(element, struct il3945_frame, list)); + il->frames_count--; + } + + if (il->frames_count) { + IL_WARN("%d frames still in use. Did we lose one?\n", + il->frames_count); + il->frames_count = 0; + } +} + +static struct il3945_frame * +il3945_get_free_frame(struct il_priv *il) +{ + struct il3945_frame *frame; + struct list_head *element; + if (list_empty(&il->free_frames)) { + frame = kzalloc(sizeof(*frame), GFP_KERNEL); + if (!frame) { + IL_ERR("Could not allocate frame!\n"); + return NULL; + } + + il->frames_count++; + return frame; + } + + element = il->free_frames.next; + list_del(element); + return list_entry(element, struct il3945_frame, list); +} + +static void +il3945_free_frame(struct il_priv *il, struct il3945_frame *frame) +{ + memset(frame, 0, sizeof(*frame)); + list_add(&frame->list, &il->free_frames); +} + +unsigned int +il3945_fill_beacon_frame(struct il_priv *il, struct ieee80211_hdr *hdr, + int left) +{ + + if (!il_is_associated(il) || !il->beacon_skb) + return 0; + + if (il->beacon_skb->len > left) + return 0; + + memcpy(hdr, il->beacon_skb->data, il->beacon_skb->len); + + return il->beacon_skb->len; +} + +static int +il3945_send_beacon_cmd(struct il_priv *il) +{ + struct il3945_frame *frame; + unsigned int frame_size; + int rc; + u8 rate; + + frame = il3945_get_free_frame(il); + + if (!frame) { + IL_ERR("Could not obtain free frame buffer for beacon " + "command.\n"); + return -ENOMEM; + } + + rate = il_get_lowest_plcp(il); + + frame_size = il3945_hw_get_beacon_cmd(il, frame, rate); + + rc = il_send_cmd_pdu(il, C_TX_BEACON, frame_size, &frame->u.cmd[0]); + + il3945_free_frame(il, frame); + + return rc; +} + +static void +il3945_unset_hw_params(struct il_priv *il) +{ + if (il->_3945.shared_virt) + dma_free_coherent(&il->pci_dev->dev, + sizeof(struct il3945_shared), + il->_3945.shared_virt, il->_3945.shared_phys); +} + +static void +il3945_build_tx_cmd_hwcrypto(struct il_priv *il, struct ieee80211_tx_info *info, + struct il_device_cmd *cmd, + struct sk_buff *skb_frag, int sta_id) +{ + struct il3945_tx_cmd *tx_cmd = (struct il3945_tx_cmd *)cmd->cmd.payload; + struct il_hw_key *keyinfo = &il->stations[sta_id].keyinfo; + + tx_cmd->sec_ctl = 0; + + switch (keyinfo->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyinfo->key, keyinfo->keylen); + D_TX("tx_cmd with AES hwcrypto\n"); + break; + + case WLAN_CIPHER_SUITE_TKIP: + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= + TX_CMD_SEC_WEP | (info->control.hw_key-> + hw_key_idx & TX_CMD_SEC_MSK) << + TX_CMD_SEC_SHIFT; + + memcpy(&tx_cmd->key[3], keyinfo->key, keyinfo->keylen); + + D_TX("Configuring packet for WEP encryption " "with key %d\n", + info->control.hw_key->hw_key_idx); + break; + + default: + IL_ERR("Unknown encode cipher %x\n", keyinfo->cipher); + break; + } +} + +/* + * handle build C_TX command notification. + */ +static void +il3945_build_tx_cmd_basic(struct il_priv *il, struct il_device_cmd *cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, u8 std_id) +{ + struct il3945_tx_cmd *tx_cmd = (struct il3945_tx_cmd *)cmd->cmd.payload; + __le32 tx_flags = tx_cmd->tx_flags; + __le16 fc = hdr->frame_control; + + tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) { + tx_flags |= TX_CMD_FLG_ACK_MSK; + if (ieee80211_is_mgmt(fc)) + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + if (ieee80211_is_probe_resp(fc) && + !(le16_to_cpu(hdr->seq_ctrl) & 0xf)) + tx_flags |= TX_CMD_FLG_TSF_MSK; + } else { + tx_flags &= (~TX_CMD_FLG_ACK_MSK); + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + tx_cmd->sta_id = std_id; + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } else { + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + il_tx_cmd_protection(il, info, fc, &tx_flags); + + tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); + } else { + tx_cmd->timeout.pm_frame_timeout = 0; + } + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = tx_flags; + tx_cmd->next_frame_len = 0; +} + +/* + * start C_TX command process + */ +static int +il3945_tx_skb(struct il_priv *il, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct il3945_tx_cmd *tx_cmd; + struct il_tx_queue *txq = NULL; + struct il_queue *q = NULL; + struct il_device_cmd *out_cmd; + struct il_cmd_meta *out_meta; + dma_addr_t phys_addr; + dma_addr_t txcmd_phys; + int txq_id = skb_get_queue_mapping(skb); + u16 len, idx, hdr_len; + u16 firstlen, secondlen; + u8 id; + u8 unicast; + u8 sta_id; + u8 tid = 0; + __le16 fc; + u8 wait_write_ptr = 0; + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + if (il_is_rfkill(il)) { + D_DROP("Dropping - RF KILL\n"); + goto drop_unlock; + } + + if ((ieee80211_get_tx_rate(il->hw, info)->hw_value & 0xFF) == + IL_INVALID_RATE) { + IL_ERR("ERROR: No TX rate available.\n"); + goto drop_unlock; + } + + unicast = !is_multicast_ether_addr(hdr->addr1); + id = 0; + + fc = hdr->frame_control; + +#ifdef CONFIG_IWLEGACY_DEBUG + if (ieee80211_is_auth(fc)) + D_TX("Sending AUTH frame\n"); + else if (ieee80211_is_assoc_req(fc)) + D_TX("Sending ASSOC frame\n"); + else if (ieee80211_is_reassoc_req(fc)) + D_TX("Sending REASSOC frame\n"); +#endif + + spin_unlock_irqrestore(&il->lock, flags); + + hdr_len = ieee80211_hdrlen(fc); + + /* Find idx into station table for destination station */ + sta_id = il_sta_id_or_broadcast(il, sta); + if (sta_id == IL_INVALID_STATION) { + D_DROP("Dropping - INVALID STATION: %pM\n", hdr->addr1); + goto drop; + } + + D_RATE("station Id %d\n", sta_id); + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (unlikely(tid >= MAX_TID_COUNT)) + goto drop; + } + + /* Descriptor for chosen Tx queue */ + txq = &il->txq[txq_id]; + q = &txq->q; + + if ((il_queue_space(q) < q->high_mark)) + goto drop; + + spin_lock_irqsave(&il->lock, flags); + + idx = il_get_cmd_idx(q, q->write_ptr, 0); + + txq->skbs[q->write_ptr] = skb; + + /* Init first empty entry in queue's array of Tx/cmd buffers */ + out_cmd = txq->cmd[idx]; + out_meta = &txq->meta[idx]; + tx_cmd = (struct il3945_tx_cmd *)out_cmd->cmd.payload; + memset(&out_cmd->hdr, 0, sizeof(out_cmd->hdr)); + memset(tx_cmd, 0, sizeof(*tx_cmd)); + + /* + * Set up the Tx-command (not MAC!) header. + * Store the chosen Tx queue and TFD idx within the sequence field; + * after Tx, uCode's Tx response will return this value so driver can + * locate the frame within the tx queue and do post-tx processing. + */ + out_cmd->hdr.cmd = C_TX; + out_cmd->hdr.sequence = + cpu_to_le16((u16) + (QUEUE_TO_SEQ(txq_id) | IDX_TO_SEQ(q->write_ptr))); + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdr_len); + + if (info->control.hw_key) + il3945_build_tx_cmd_hwcrypto(il, info, out_cmd, skb, sta_id); + + /* TODO need this for burst mode later on */ + il3945_build_tx_cmd_basic(il, out_cmd, info, hdr, sta_id); + + il3945_hw_build_tx_cmd_rate(il, out_cmd, info, hdr, sta_id); + + /* Total # bytes to be transmitted */ + tx_cmd->len = cpu_to_le16((u16) skb->len); + + tx_cmd->tx_flags &= ~TX_CMD_FLG_ANT_A_MSK; + tx_cmd->tx_flags &= ~TX_CMD_FLG_ANT_B_MSK; + + /* + * Use the first empty entry in this queue's command buffer array + * to contain the Tx command and MAC header concatenated together + * (payload data will be in another buffer). + * Size of this varies, due to varying MAC header length. + * If end is not dword aligned, we'll have 2 extra bytes at the end + * of the MAC header (device reads on dword boundaries). + * We'll tell device about this padding later. + */ + len = + sizeof(struct il3945_tx_cmd) + sizeof(struct il_cmd_header) + + hdr_len; + firstlen = (len + 3) & ~3; + + /* Physical address of this Tx command's header (not MAC header!), + * within command buffer array. */ + txcmd_phys = + pci_map_single(il->pci_dev, &out_cmd->hdr, firstlen, + PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, txcmd_phys))) + goto drop_unlock; + + /* Set up TFD's 2nd entry to point directly to remainder of skb, + * if any (802.11 null frames have no payload). */ + secondlen = skb->len - hdr_len; + if (secondlen > 0) { + phys_addr = + pci_map_single(il->pci_dev, skb->data + hdr_len, secondlen, + PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, phys_addr))) + goto drop_unlock; + } + + /* Add buffer containing Tx command and MAC(!) header to TFD's + * first entry */ + il->ops->txq_attach_buf_to_tfd(il, txq, txcmd_phys, firstlen, 1, 0); + dma_unmap_addr_set(out_meta, mapping, txcmd_phys); + dma_unmap_len_set(out_meta, len, firstlen); + if (secondlen > 0) + il->ops->txq_attach_buf_to_tfd(il, txq, phys_addr, secondlen, 0, + U32_PAD(secondlen)); + + if (!ieee80211_has_morefrags(hdr->frame_control)) { + txq->need_update = 1; + } else { + wait_write_ptr = 1; + txq->need_update = 0; + } + + il_update_stats(il, true, fc, skb->len); + + D_TX("sequence nr = 0X%x\n", le16_to_cpu(out_cmd->hdr.sequence)); + D_TX("tx_flags = 0X%x\n", le32_to_cpu(tx_cmd->tx_flags)); + il_print_hex_dump(il, IL_DL_TX, tx_cmd, sizeof(*tx_cmd)); + il_print_hex_dump(il, IL_DL_TX, (u8 *) tx_cmd->hdr, + ieee80211_hdrlen(fc)); + + /* Tell device the write idx *just past* this latest filled TFD */ + q->write_ptr = il_queue_inc_wrap(q->write_ptr, q->n_bd); + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + + if (il_queue_space(q) < q->high_mark && il->mac80211_registered) { + if (wait_write_ptr) { + spin_lock_irqsave(&il->lock, flags); + txq->need_update = 1; + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + } + + il_stop_queue(il, txq); + } + + return 0; + +drop_unlock: + spin_unlock_irqrestore(&il->lock, flags); +drop: + return -1; +} + +static int +il3945_get_measurement(struct il_priv *il, + struct ieee80211_measurement_params *params, u8 type) +{ + struct il_spectrum_cmd spectrum; + struct il_rx_pkt *pkt; + struct il_host_cmd cmd = { + .id = C_SPECTRUM_MEASUREMENT, + .data = (void *)&spectrum, + .flags = CMD_WANT_SKB, + }; + u32 add_time = le64_to_cpu(params->start_time); + int rc; + int spectrum_resp_status; + int duration = le16_to_cpu(params->duration); + + if (il_is_associated(il)) + add_time = + il_usecs_to_beacons(il, + le64_to_cpu(params->start_time) - + il->_3945.last_tsf, + le16_to_cpu(il->timing.beacon_interval)); + + memset(&spectrum, 0, sizeof(spectrum)); + + spectrum.channel_count = cpu_to_le16(1); + spectrum.flags = + RXON_FLG_TSF2HOST_MSK | RXON_FLG_ANT_A_MSK | RXON_FLG_DIS_DIV_MSK; + spectrum.filter_flags = MEASUREMENT_FILTER_FLAG; + cmd.len = sizeof(spectrum); + spectrum.len = cpu_to_le16(cmd.len - sizeof(spectrum.len)); + + if (il_is_associated(il)) + spectrum.start_time = + il_add_beacon_time(il, il->_3945.last_beacon_time, add_time, + le16_to_cpu(il->timing.beacon_interval)); + else + spectrum.start_time = 0; + + spectrum.channels[0].duration = cpu_to_le32(duration * TIME_UNIT); + spectrum.channels[0].channel = params->channel; + spectrum.channels[0].type = type; + if (il->active.flags & RXON_FLG_BAND_24G_MSK) + spectrum.flags |= + RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_TGG_PROTECT_MSK; + + rc = il_send_cmd_sync(il, &cmd); + if (rc) + return rc; + + pkt = (struct il_rx_pkt *)cmd.reply_page; + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from N_RX_ON_ASSOC command\n"); + rc = -EIO; + } + + spectrum_resp_status = le16_to_cpu(pkt->u.spectrum.status); + switch (spectrum_resp_status) { + case 0: /* Command will be handled */ + if (pkt->u.spectrum.id != 0xff) { + D_INFO("Replaced existing measurement: %d\n", + pkt->u.spectrum.id); + il->measurement_status &= ~MEASUREMENT_READY; + } + il->measurement_status |= MEASUREMENT_ACTIVE; + rc = 0; + break; + + case 1: /* Command will not be handled */ + rc = -EAGAIN; + break; + } + + il_free_pages(il, cmd.reply_page); + + return rc; +} + +static void +il3945_hdl_alive(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_alive_resp *palive; + struct delayed_work *pwork; + + palive = &pkt->u.alive_frame; + + D_INFO("Alive ucode status 0x%08X revision " "0x%01X 0x%01X\n", + palive->is_valid, palive->ver_type, palive->ver_subtype); + + if (palive->ver_subtype == INITIALIZE_SUBTYPE) { + D_INFO("Initialization Alive received.\n"); + memcpy(&il->card_alive_init, &pkt->u.alive_frame, + sizeof(struct il_alive_resp)); + pwork = &il->init_alive_start; + } else { + D_INFO("Runtime Alive received.\n"); + memcpy(&il->card_alive, &pkt->u.alive_frame, + sizeof(struct il_alive_resp)); + pwork = &il->alive_start; + il3945_disable_events(il); + } + + /* We delay the ALIVE response by 5ms to + * give the HW RF Kill time to activate... */ + if (palive->is_valid == UCODE_VALID_OK) + queue_delayed_work(il->workqueue, pwork, msecs_to_jiffies(5)); + else + IL_WARN("uCode did not respond OK.\n"); +} + +static void +il3945_hdl_add_sta(struct il_priv *il, struct il_rx_buf *rxb) +{ +#ifdef CONFIG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); +#endif + + D_RX("Received C_ADD_STA: 0x%02X\n", pkt->u.status); +} + +static void +il3945_hdl_beacon(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il3945_beacon_notif *beacon = &(pkt->u.beacon_status); +#ifdef CONFIG_IWLEGACY_DEBUG + u8 rate = beacon->beacon_notify_hdr.rate; + + D_RX("beacon status %x retries %d iss %d " "tsf %d %d rate %d\n", + le32_to_cpu(beacon->beacon_notify_hdr.status) & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le32_to_cpu(beacon->ibss_mgr_status), + le32_to_cpu(beacon->high_tsf), le32_to_cpu(beacon->low_tsf), rate); +#endif + + il->ibss_manager = le32_to_cpu(beacon->ibss_mgr_status); + +} + +/* Handle notification from uCode that card's power state is changing + * due to software, hardware, or critical temperature RFKILL */ +static void +il3945_hdl_card_state(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u32 flags = le32_to_cpu(pkt->u.card_state_notif.flags); + unsigned long status = il->status; + + IL_WARN("Card state received: HW:%s SW:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On"); + + _il_wr(il, CSR_UCODE_DRV_GP1_SET, CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + if (flags & HW_CARD_DISABLED) + set_bit(S_RFKILL, &il->status); + else + clear_bit(S_RFKILL, &il->status); + + il_scan_cancel(il); + + if ((test_bit(S_RFKILL, &status) != + test_bit(S_RFKILL, &il->status))) + wiphy_rfkill_set_hw_state(il->hw->wiphy, + test_bit(S_RFKILL, &il->status)); + else + wake_up(&il->wait_command_queue); +} + +/** + * il3945_setup_handlers - Initialize Rx handler callbacks + * + * Setup the RX handlers for each of the reply types sent from the uCode + * to the host. + * + * This function chains into the hardware specific files for them to setup + * any hardware specific handlers as well. + */ +static void +il3945_setup_handlers(struct il_priv *il) +{ + il->handlers[N_ALIVE] = il3945_hdl_alive; + il->handlers[C_ADD_STA] = il3945_hdl_add_sta; + il->handlers[N_ERROR] = il_hdl_error; + il->handlers[N_CHANNEL_SWITCH] = il_hdl_csa; + il->handlers[N_SPECTRUM_MEASUREMENT] = il_hdl_spectrum_measurement; + il->handlers[N_PM_SLEEP] = il_hdl_pm_sleep; + il->handlers[N_PM_DEBUG_STATS] = il_hdl_pm_debug_stats; + il->handlers[N_BEACON] = il3945_hdl_beacon; + + /* + * The same handler is used for both the REPLY to a discrete + * stats request from the host as well as for the periodic + * stats notifications (after received beacons) from the uCode. + */ + il->handlers[C_STATS] = il3945_hdl_c_stats; + il->handlers[N_STATS] = il3945_hdl_stats; + + il_setup_rx_scan_handlers(il); + il->handlers[N_CARD_STATE] = il3945_hdl_card_state; + + /* Set up hardware specific Rx handlers */ + il3945_hw_handler_setup(il); +} + +/************************** RX-FUNCTIONS ****************************/ +/* + * Rx theory of operation + * + * The host allocates 32 DMA target addresses and passes the host address + * to the firmware at register IL_RFDS_TBL_LOWER + N * RFD_SIZE where N is + * 0 to 31 + * + * Rx Queue Indexes + * The host/firmware share two idx registers for managing the Rx buffers. + * + * The READ idx maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ idx is managed by the firmware once the card is enabled. + * + * The WRITE idx maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * IDX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ idx + * and fire the RX interrupt. The driver can then query the READ idx and + * process as many packets as possible, moving the WRITE idx forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated SKBs is stored in iwl->rxq->rx_free. When + * iwl->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled + * to replenish the iwl->rxq->rx_free. + * + In il3945_rx_replenish (scheduled) if 'processed' != 'read' then the + * iwl->rxq is replenished and the READ IDX is updated (updating the + * 'processed' and 'read' driver idxes as well) + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' idx is updated. + * + The Host/Firmware iwl->rxq is replenished at tasklet time from the rx_free + * list. If there are no allocated buffers in iwl->rxq->rx_free, the READ + * IDX is not incremented and iwl->status(RX_STALLED) is set. If there + * were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * il3945_rx_replenish() Replenishes rx_free list from rx_used, and calls + * il3945_rx_queue_restock + * il3945_rx_queue_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE idx. If insufficient rx_free buffers + * are available, schedules il3945_rx_replenish + * + * -- enable interrupts -- + * ISR - il3945_rx() Detach il_rx_bufs from pool up to the + * READ IDX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Calls il3945_rx_queue_restock to refill any empty + * slots. + * ... + * + */ + +/** + * il3945_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 +il3945_dma_addr2rbd_ptr(struct il_priv *il, dma_addr_t dma_addr) +{ + return cpu_to_le32((u32) dma_addr); +} + +/** + * il3945_rx_queue_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' idx forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static void +il3945_rx_queue_restock(struct il_priv *il) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + unsigned long flags; + int write; + + spin_lock_irqsave(&rxq->lock, flags); + write = rxq->write & ~0x7; + while (il_rx_queue_space(rxq) > 0 && rxq->free_count) { + /* Get next free Rx buffer, remove from free list */ + element = rxq->rx_free.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + /* Point to Rx buffer via next RBD in circular buffer */ + rxq->bd[rxq->write] = + il3945_dma_addr2rbd_ptr(il, rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_irqrestore(&rxq->lock, flags); + /* If the pre-allocated buffer pool is dropping low, schedule to + * refill it */ + if (rxq->free_count <= RX_LOW_WATERMARK) + queue_work(il->workqueue, &il->rx_replenish); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7) || + abs(rxq->write - rxq->read) > 7) { + spin_lock_irqsave(&rxq->lock, flags); + rxq->need_update = 1; + spin_unlock_irqrestore(&rxq->lock, flags); + il_rx_queue_update_write_ptr(il, rxq); + } +} + +/** + * il3945_rx_replenish - Move all used packet from rx_used to rx_free + * + * When moving to rx_free an SKB is allocated for the slot. + * + * Also restock the Rx queue via il3945_rx_queue_restock. + * This is called as a scheduled work item (except for during initialization) + */ +static void +il3945_rx_allocate(struct il_priv *il, gfp_t priority) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + struct page *page; + dma_addr_t page_dma; + unsigned long flags; + gfp_t gfp_mask = priority; + + while (1) { + spin_lock_irqsave(&rxq->lock, flags); + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + return; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + if (rxq->free_count > RX_LOW_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + if (il->hw_params.rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, il->hw_params.rx_page_order); + if (!page) { + if (net_ratelimit()) + D_INFO("Failed to allocate SKB buffer.\n"); + if (rxq->free_count <= RX_LOW_WATERMARK && + net_ratelimit()) + IL_ERR("Failed to allocate SKB buffer with %0x." + "Only %u free buffers remaining.\n", + priority, rxq->free_count); + /* We don't reschedule replenish work here -- we will + * call the restock method and if it still needs + * more buffers it will schedule replenish */ + break; + } + + /* Get physical address of RB/SKB */ + page_dma = + pci_map_page(il->pci_dev, page, 0, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + + if (unlikely(pci_dma_mapping_error(il->pci_dev, page_dma))) { + __free_pages(page, il->hw_params.rx_page_order); + break; + } + + spin_lock_irqsave(&rxq->lock, flags); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + pci_unmap_page(il->pci_dev, page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __free_pages(page, il->hw_params.rx_page_order); + return; + } + + element = rxq->rx_used.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + rxb->page = page; + rxb->page_dma = page_dma; + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + il->alloc_rxb_page++; + + spin_unlock_irqrestore(&rxq->lock, flags); + } +} + +void +il3945_rx_queue_reset(struct il_priv *il, struct il_rx_queue *rxq) |