| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 1 | /****************************************************************************** | 
|  | 2 | * | 
|  | 3 | * GPL LICENSE SUMMARY | 
|  | 4 | * | 
|  | 5 | * Copyright(c) 2008 - 2010 Intel Corporation. All rights reserved. | 
|  | 6 | * | 
|  | 7 | * This program is free software; you can redistribute it and/or modify | 
|  | 8 | * it under the terms of version 2 of the GNU General Public License as | 
|  | 9 | * published by the Free Software Foundation. | 
|  | 10 | * | 
|  | 11 | * This program is distributed in the hope that it will be useful, but | 
|  | 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | 14 | * General Public License for more details. | 
|  | 15 | * | 
|  | 16 | * You should have received a copy of the GNU General Public License | 
|  | 17 | * along with this program; if not, write to the Free Software | 
|  | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | 
|  | 19 | * USA | 
|  | 20 | * | 
|  | 21 | * The full GNU General Public License is included in this distribution | 
|  | 22 | * in the file called LICENSE.GPL. | 
|  | 23 | * | 
|  | 24 | * Contact Information: | 
|  | 25 | *  Intel Linux Wireless <ilw@linux.intel.com> | 
|  | 26 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | 
|  | 27 | * | 
|  | 28 | *****************************************************************************/ | 
|  | 29 |  | 
|  | 30 | #include <linux/kernel.h> | 
|  | 31 | #include <linux/module.h> | 
|  | 32 | #include <linux/init.h> | 
|  | 33 | #include <linux/sched.h> | 
|  | 34 |  | 
|  | 35 | #include "iwl-dev.h" | 
|  | 36 | #include "iwl-core.h" | 
|  | 37 | #include "iwl-sta.h" | 
|  | 38 | #include "iwl-io.h" | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 39 | #include "iwl-helpers.h" | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 40 | #include "iwl-agn-hw.h" | 
| Wey-Yi Guy | 8d80108 | 2010-03-17 13:34:36 -0700 | [diff] [blame] | 41 | #include "iwl-agn.h" | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 42 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 43 | /* | 
|  | 44 | * mac80211 queues, ACs, hardware queues, FIFOs. | 
|  | 45 | * | 
|  | 46 | * Cf. http://wireless.kernel.org/en/developers/Documentation/mac80211/queues | 
|  | 47 | * | 
|  | 48 | * Mac80211 uses the following numbers, which we get as from it | 
|  | 49 | * by way of skb_get_queue_mapping(skb): | 
|  | 50 | * | 
|  | 51 | *	VO	0 | 
|  | 52 | *	VI	1 | 
|  | 53 | *	BE	2 | 
|  | 54 | *	BK	3 | 
|  | 55 | * | 
|  | 56 | * | 
|  | 57 | * Regular (not A-MPDU) frames are put into hardware queues corresponding | 
|  | 58 | * to the FIFOs, see comments in iwl-prph.h. Aggregated frames get their | 
|  | 59 | * own queue per aggregation session (RA/TID combination), such queues are | 
|  | 60 | * set up to map into FIFOs too, for which we need an AC->FIFO mapping. In | 
|  | 61 | * order to map frames to the right queue, we also need an AC->hw queue | 
|  | 62 | * mapping. This is implemented here. | 
|  | 63 | * | 
|  | 64 | * Due to the way hw queues are set up (by the hw specific modules like | 
|  | 65 | * iwl-4965.c, iwl-5000.c etc.), the AC->hw queue mapping is the identity | 
|  | 66 | * mapping. | 
|  | 67 | */ | 
|  | 68 |  | 
|  | 69 | static const u8 tid_to_ac[] = { | 
|  | 70 | /* this matches the mac80211 numbers */ | 
|  | 71 | 2, 3, 3, 2, 1, 1, 0, 0 | 
|  | 72 | }; | 
|  | 73 |  | 
|  | 74 | static const u8 ac_to_fifo[] = { | 
|  | 75 | IWL_TX_FIFO_VO, | 
|  | 76 | IWL_TX_FIFO_VI, | 
|  | 77 | IWL_TX_FIFO_BE, | 
|  | 78 | IWL_TX_FIFO_BK, | 
|  | 79 | }; | 
|  | 80 |  | 
|  | 81 | static inline int get_fifo_from_ac(u8 ac) | 
|  | 82 | { | 
|  | 83 | return ac_to_fifo[ac]; | 
|  | 84 | } | 
|  | 85 |  | 
| Shanyu Zhao | c2845d0 | 2010-04-14 15:35:14 -0700 | [diff] [blame] | 86 | static inline int get_ac_from_tid(u16 tid) | 
|  | 87 | { | 
|  | 88 | if (likely(tid < ARRAY_SIZE(tid_to_ac))) | 
|  | 89 | return tid_to_ac[tid]; | 
|  | 90 |  | 
|  | 91 | /* no support for TIDs 8-15 yet */ | 
|  | 92 | return -EINVAL; | 
|  | 93 | } | 
|  | 94 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 95 | static inline int get_fifo_from_tid(u16 tid) | 
|  | 96 | { | 
|  | 97 | if (likely(tid < ARRAY_SIZE(tid_to_ac))) | 
|  | 98 | return get_fifo_from_ac(tid_to_ac[tid]); | 
|  | 99 |  | 
|  | 100 | /* no support for TIDs 8-15 yet */ | 
|  | 101 | return -EINVAL; | 
|  | 102 | } | 
|  | 103 |  | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 104 | /** | 
|  | 105 | * iwlagn_txq_update_byte_cnt_tbl - Set up entry in Tx byte-count array | 
|  | 106 | */ | 
|  | 107 | void iwlagn_txq_update_byte_cnt_tbl(struct iwl_priv *priv, | 
|  | 108 | struct iwl_tx_queue *txq, | 
|  | 109 | u16 byte_cnt) | 
|  | 110 | { | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 111 | struct iwlagn_scd_bc_tbl *scd_bc_tbl = priv->scd_bc_tbls.addr; | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 112 | int write_ptr = txq->q.write_ptr; | 
|  | 113 | int txq_id = txq->q.id; | 
|  | 114 | u8 sec_ctl = 0; | 
|  | 115 | u8 sta_id = 0; | 
|  | 116 | u16 len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE; | 
|  | 117 | __le16 bc_ent; | 
|  | 118 |  | 
|  | 119 | WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX); | 
|  | 120 |  | 
|  | 121 | if (txq_id != IWL_CMD_QUEUE_NUM) { | 
|  | 122 | sta_id = txq->cmd[txq->q.write_ptr]->cmd.tx.sta_id; | 
|  | 123 | sec_ctl = txq->cmd[txq->q.write_ptr]->cmd.tx.sec_ctl; | 
|  | 124 |  | 
|  | 125 | switch (sec_ctl & TX_CMD_SEC_MSK) { | 
|  | 126 | case TX_CMD_SEC_CCM: | 
|  | 127 | len += CCMP_MIC_LEN; | 
|  | 128 | break; | 
|  | 129 | case TX_CMD_SEC_TKIP: | 
|  | 130 | len += TKIP_ICV_LEN; | 
|  | 131 | break; | 
|  | 132 | case TX_CMD_SEC_WEP: | 
|  | 133 | len += WEP_IV_LEN + WEP_ICV_LEN; | 
|  | 134 | break; | 
|  | 135 | } | 
|  | 136 | } | 
|  | 137 |  | 
|  | 138 | bc_ent = cpu_to_le16((len & 0xFFF) | (sta_id << 12)); | 
|  | 139 |  | 
|  | 140 | scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent; | 
|  | 141 |  | 
|  | 142 | if (write_ptr < TFD_QUEUE_SIZE_BC_DUP) | 
|  | 143 | scd_bc_tbl[txq_id]. | 
|  | 144 | tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] = bc_ent; | 
|  | 145 | } | 
|  | 146 |  | 
|  | 147 | void iwlagn_txq_inval_byte_cnt_tbl(struct iwl_priv *priv, | 
|  | 148 | struct iwl_tx_queue *txq) | 
|  | 149 | { | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 150 | struct iwlagn_scd_bc_tbl *scd_bc_tbl = priv->scd_bc_tbls.addr; | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 151 | int txq_id = txq->q.id; | 
|  | 152 | int read_ptr = txq->q.read_ptr; | 
|  | 153 | u8 sta_id = 0; | 
|  | 154 | __le16 bc_ent; | 
|  | 155 |  | 
|  | 156 | WARN_ON(read_ptr >= TFD_QUEUE_SIZE_MAX); | 
|  | 157 |  | 
|  | 158 | if (txq_id != IWL_CMD_QUEUE_NUM) | 
|  | 159 | sta_id = txq->cmd[read_ptr]->cmd.tx.sta_id; | 
|  | 160 |  | 
|  | 161 | bc_ent = cpu_to_le16(1 | (sta_id << 12)); | 
|  | 162 | scd_bc_tbl[txq_id].tfd_offset[read_ptr] = bc_ent; | 
|  | 163 |  | 
|  | 164 | if (read_ptr < TFD_QUEUE_SIZE_BC_DUP) | 
|  | 165 | scd_bc_tbl[txq_id]. | 
|  | 166 | tfd_offset[TFD_QUEUE_SIZE_MAX + read_ptr] = bc_ent; | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | static int iwlagn_tx_queue_set_q2ratid(struct iwl_priv *priv, u16 ra_tid, | 
|  | 170 | u16 txq_id) | 
|  | 171 | { | 
|  | 172 | u32 tbl_dw_addr; | 
|  | 173 | u32 tbl_dw; | 
|  | 174 | u16 scd_q2ratid; | 
|  | 175 |  | 
|  | 176 | scd_q2ratid = ra_tid & IWL_SCD_QUEUE_RA_TID_MAP_RATID_MSK; | 
|  | 177 |  | 
|  | 178 | tbl_dw_addr = priv->scd_base_addr + | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 179 | IWLAGN_SCD_TRANSLATE_TBL_OFFSET_QUEUE(txq_id); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 180 |  | 
|  | 181 | tbl_dw = iwl_read_targ_mem(priv, tbl_dw_addr); | 
|  | 182 |  | 
|  | 183 | if (txq_id & 0x1) | 
|  | 184 | tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF); | 
|  | 185 | else | 
|  | 186 | tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000); | 
|  | 187 |  | 
|  | 188 | iwl_write_targ_mem(priv, tbl_dw_addr, tbl_dw); | 
|  | 189 |  | 
|  | 190 | return 0; | 
|  | 191 | } | 
|  | 192 |  | 
|  | 193 | static void iwlagn_tx_queue_stop_scheduler(struct iwl_priv *priv, u16 txq_id) | 
|  | 194 | { | 
|  | 195 | /* Simply stop the queue, but don't change any configuration; | 
|  | 196 | * the SCD_ACT_EN bit is the write-enable mask for the ACTIVE bit. */ | 
|  | 197 | iwl_write_prph(priv, | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 198 | IWLAGN_SCD_QUEUE_STATUS_BITS(txq_id), | 
|  | 199 | (0 << IWLAGN_SCD_QUEUE_STTS_REG_POS_ACTIVE)| | 
|  | 200 | (1 << IWLAGN_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 201 | } | 
|  | 202 |  | 
|  | 203 | void iwlagn_set_wr_ptrs(struct iwl_priv *priv, | 
|  | 204 | int txq_id, u32 index) | 
|  | 205 | { | 
|  | 206 | iwl_write_direct32(priv, HBUS_TARG_WRPTR, | 
|  | 207 | (index & 0xff) | (txq_id << 8)); | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 208 | iwl_write_prph(priv, IWLAGN_SCD_QUEUE_RDPTR(txq_id), index); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 209 | } | 
|  | 210 |  | 
|  | 211 | void iwlagn_tx_queue_set_status(struct iwl_priv *priv, | 
|  | 212 | struct iwl_tx_queue *txq, | 
|  | 213 | int tx_fifo_id, int scd_retry) | 
|  | 214 | { | 
|  | 215 | int txq_id = txq->q.id; | 
|  | 216 | int active = test_bit(txq_id, &priv->txq_ctx_active_msk) ? 1 : 0; | 
|  | 217 |  | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 218 | iwl_write_prph(priv, IWLAGN_SCD_QUEUE_STATUS_BITS(txq_id), | 
|  | 219 | (active << IWLAGN_SCD_QUEUE_STTS_REG_POS_ACTIVE) | | 
|  | 220 | (tx_fifo_id << IWLAGN_SCD_QUEUE_STTS_REG_POS_TXF) | | 
|  | 221 | (1 << IWLAGN_SCD_QUEUE_STTS_REG_POS_WSL) | | 
|  | 222 | IWLAGN_SCD_QUEUE_STTS_REG_MSK); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 223 |  | 
|  | 224 | txq->sched_retry = scd_retry; | 
|  | 225 |  | 
|  | 226 | IWL_DEBUG_INFO(priv, "%s %s Queue %d on FIFO %d\n", | 
|  | 227 | active ? "Activate" : "Deactivate", | 
|  | 228 | scd_retry ? "BA" : "AC/CMD", txq_id, tx_fifo_id); | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | int iwlagn_txq_agg_enable(struct iwl_priv *priv, int txq_id, | 
|  | 232 | int tx_fifo, int sta_id, int tid, u16 ssn_idx) | 
|  | 233 | { | 
|  | 234 | unsigned long flags; | 
|  | 235 | u16 ra_tid; | 
|  | 236 |  | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 237 | if ((IWLAGN_FIRST_AMPDU_QUEUE > txq_id) || | 
|  | 238 | (IWLAGN_FIRST_AMPDU_QUEUE + priv->cfg->num_of_ampdu_queues | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 239 | <= txq_id)) { | 
|  | 240 | IWL_WARN(priv, | 
|  | 241 | "queue number out of range: %d, must be %d to %d\n", | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 242 | txq_id, IWLAGN_FIRST_AMPDU_QUEUE, | 
|  | 243 | IWLAGN_FIRST_AMPDU_QUEUE + | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 244 | priv->cfg->num_of_ampdu_queues - 1); | 
|  | 245 | return -EINVAL; | 
|  | 246 | } | 
|  | 247 |  | 
|  | 248 | ra_tid = BUILD_RAxTID(sta_id, tid); | 
|  | 249 |  | 
|  | 250 | /* Modify device's station table to Tx this TID */ | 
|  | 251 | iwl_sta_tx_modify_enable_tid(priv, sta_id, tid); | 
|  | 252 |  | 
|  | 253 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 254 |  | 
|  | 255 | /* Stop this Tx queue before configuring it */ | 
|  | 256 | iwlagn_tx_queue_stop_scheduler(priv, txq_id); | 
|  | 257 |  | 
|  | 258 | /* Map receiver-address / traffic-ID to this queue */ | 
|  | 259 | iwlagn_tx_queue_set_q2ratid(priv, ra_tid, txq_id); | 
|  | 260 |  | 
|  | 261 | /* Set this queue as a chain-building queue */ | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 262 | iwl_set_bits_prph(priv, IWLAGN_SCD_QUEUECHAIN_SEL, (1<<txq_id)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 263 |  | 
|  | 264 | /* enable aggregations for the queue */ | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 265 | iwl_set_bits_prph(priv, IWLAGN_SCD_AGGR_SEL, (1<<txq_id)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 266 |  | 
|  | 267 | /* Place first TFD at index corresponding to start sequence number. | 
|  | 268 | * Assumes that ssn_idx is valid (!= 0xFFF) */ | 
|  | 269 | priv->txq[txq_id].q.read_ptr = (ssn_idx & 0xff); | 
|  | 270 | priv->txq[txq_id].q.write_ptr = (ssn_idx & 0xff); | 
|  | 271 | iwlagn_set_wr_ptrs(priv, txq_id, ssn_idx); | 
|  | 272 |  | 
|  | 273 | /* Set up Tx window size and frame limit for this queue */ | 
|  | 274 | iwl_write_targ_mem(priv, priv->scd_base_addr + | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 275 | IWLAGN_SCD_CONTEXT_QUEUE_OFFSET(txq_id) + | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 276 | sizeof(u32), | 
|  | 277 | ((SCD_WIN_SIZE << | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 278 | IWLAGN_SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) & | 
|  | 279 | IWLAGN_SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) | | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 280 | ((SCD_FRAME_LIMIT << | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 281 | IWLAGN_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) & | 
|  | 282 | IWLAGN_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 283 |  | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 284 | iwl_set_bits_prph(priv, IWLAGN_SCD_INTERRUPT_MASK, (1 << txq_id)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 285 |  | 
|  | 286 | /* Set up Status area in SRAM, map to Tx DMA/FIFO, activate the queue */ | 
|  | 287 | iwlagn_tx_queue_set_status(priv, &priv->txq[txq_id], tx_fifo, 1); | 
|  | 288 |  | 
|  | 289 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 290 |  | 
|  | 291 | return 0; | 
|  | 292 | } | 
|  | 293 |  | 
|  | 294 | int iwlagn_txq_agg_disable(struct iwl_priv *priv, u16 txq_id, | 
|  | 295 | u16 ssn_idx, u8 tx_fifo) | 
|  | 296 | { | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 297 | if ((IWLAGN_FIRST_AMPDU_QUEUE > txq_id) || | 
|  | 298 | (IWLAGN_FIRST_AMPDU_QUEUE + priv->cfg->num_of_ampdu_queues | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 299 | <= txq_id)) { | 
|  | 300 | IWL_ERR(priv, | 
|  | 301 | "queue number out of range: %d, must be %d to %d\n", | 
| Wey-Yi Guy | 19e6cda | 2010-03-16 17:41:23 -0700 | [diff] [blame] | 302 | txq_id, IWLAGN_FIRST_AMPDU_QUEUE, | 
|  | 303 | IWLAGN_FIRST_AMPDU_QUEUE + | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 304 | priv->cfg->num_of_ampdu_queues - 1); | 
|  | 305 | return -EINVAL; | 
|  | 306 | } | 
|  | 307 |  | 
|  | 308 | iwlagn_tx_queue_stop_scheduler(priv, txq_id); | 
|  | 309 |  | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 310 | iwl_clear_bits_prph(priv, IWLAGN_SCD_AGGR_SEL, (1 << txq_id)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 311 |  | 
|  | 312 | priv->txq[txq_id].q.read_ptr = (ssn_idx & 0xff); | 
|  | 313 | priv->txq[txq_id].q.write_ptr = (ssn_idx & 0xff); | 
|  | 314 | /* supposes that ssn_idx is valid (!= 0xFFF) */ | 
|  | 315 | iwlagn_set_wr_ptrs(priv, txq_id, ssn_idx); | 
|  | 316 |  | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 317 | iwl_clear_bits_prph(priv, IWLAGN_SCD_INTERRUPT_MASK, (1 << txq_id)); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 318 | iwl_txq_ctx_deactivate(priv, txq_id); | 
|  | 319 | iwlagn_tx_queue_set_status(priv, &priv->txq[txq_id], tx_fifo, 0); | 
|  | 320 |  | 
|  | 321 | return 0; | 
|  | 322 | } | 
|  | 323 |  | 
|  | 324 | /* | 
|  | 325 | * Activate/Deactivate Tx DMA/FIFO channels according tx fifos mask | 
|  | 326 | * must be called under priv->lock and mac access | 
|  | 327 | */ | 
|  | 328 | void iwlagn_txq_set_sched(struct iwl_priv *priv, u32 mask) | 
|  | 329 | { | 
| Wey-Yi Guy | f4388ad | 2010-04-12 18:32:11 -0700 | [diff] [blame] | 330 | iwl_write_prph(priv, IWLAGN_SCD_TXFACT, mask); | 
| Wey-Yi Guy | b305a08 | 2010-03-16 17:41:22 -0700 | [diff] [blame] | 331 | } | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 332 |  | 
|  | 333 | static inline int get_queue_from_ac(u16 ac) | 
|  | 334 | { | 
|  | 335 | return ac; | 
|  | 336 | } | 
|  | 337 |  | 
|  | 338 | /* | 
|  | 339 | * handle build REPLY_TX command notification. | 
|  | 340 | */ | 
|  | 341 | static void iwlagn_tx_cmd_build_basic(struct iwl_priv *priv, | 
|  | 342 | struct iwl_tx_cmd *tx_cmd, | 
|  | 343 | struct ieee80211_tx_info *info, | 
|  | 344 | struct ieee80211_hdr *hdr, | 
|  | 345 | u8 std_id) | 
|  | 346 | { | 
|  | 347 | __le16 fc = hdr->frame_control; | 
|  | 348 | __le32 tx_flags = tx_cmd->tx_flags; | 
|  | 349 |  | 
|  | 350 | tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; | 
|  | 351 | if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) { | 
|  | 352 | tx_flags |= TX_CMD_FLG_ACK_MSK; | 
|  | 353 | if (ieee80211_is_mgmt(fc)) | 
|  | 354 | tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; | 
|  | 355 | if (ieee80211_is_probe_resp(fc) && | 
|  | 356 | !(le16_to_cpu(hdr->seq_ctrl) & 0xf)) | 
|  | 357 | tx_flags |= TX_CMD_FLG_TSF_MSK; | 
|  | 358 | } else { | 
|  | 359 | tx_flags &= (~TX_CMD_FLG_ACK_MSK); | 
|  | 360 | tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; | 
|  | 361 | } | 
|  | 362 |  | 
|  | 363 | if (ieee80211_is_back_req(fc)) | 
|  | 364 | tx_flags |= TX_CMD_FLG_ACK_MSK | TX_CMD_FLG_IMM_BA_RSP_MASK; | 
|  | 365 |  | 
|  | 366 |  | 
|  | 367 | tx_cmd->sta_id = std_id; | 
|  | 368 | if (ieee80211_has_morefrags(fc)) | 
|  | 369 | tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; | 
|  | 370 |  | 
|  | 371 | if (ieee80211_is_data_qos(fc)) { | 
|  | 372 | u8 *qc = ieee80211_get_qos_ctl(hdr); | 
|  | 373 | tx_cmd->tid_tspec = qc[0] & 0xf; | 
|  | 374 | tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; | 
|  | 375 | } else { | 
|  | 376 | tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; | 
|  | 377 | } | 
|  | 378 |  | 
|  | 379 | priv->cfg->ops->utils->rts_tx_cmd_flag(info, &tx_flags); | 
|  | 380 |  | 
|  | 381 | if ((tx_flags & TX_CMD_FLG_RTS_MSK) || (tx_flags & TX_CMD_FLG_CTS_MSK)) | 
|  | 382 | tx_flags |= TX_CMD_FLG_FULL_TXOP_PROT_MSK; | 
|  | 383 |  | 
|  | 384 | tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); | 
|  | 385 | if (ieee80211_is_mgmt(fc)) { | 
|  | 386 | if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) | 
|  | 387 | tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); | 
|  | 388 | else | 
|  | 389 | tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); | 
|  | 390 | } else { | 
|  | 391 | tx_cmd->timeout.pm_frame_timeout = 0; | 
|  | 392 | } | 
|  | 393 |  | 
|  | 394 | tx_cmd->driver_txop = 0; | 
|  | 395 | tx_cmd->tx_flags = tx_flags; | 
|  | 396 | tx_cmd->next_frame_len = 0; | 
|  | 397 | } | 
|  | 398 |  | 
|  | 399 | #define RTS_DFAULT_RETRY_LIMIT		60 | 
|  | 400 |  | 
|  | 401 | static void iwlagn_tx_cmd_build_rate(struct iwl_priv *priv, | 
|  | 402 | struct iwl_tx_cmd *tx_cmd, | 
|  | 403 | struct ieee80211_tx_info *info, | 
|  | 404 | __le16 fc) | 
|  | 405 | { | 
|  | 406 | u32 rate_flags; | 
|  | 407 | int rate_idx; | 
|  | 408 | u8 rts_retry_limit; | 
|  | 409 | u8 data_retry_limit; | 
|  | 410 | u8 rate_plcp; | 
|  | 411 |  | 
|  | 412 | /* Set retry limit on DATA packets and Probe Responses*/ | 
|  | 413 | if (ieee80211_is_probe_resp(fc)) | 
|  | 414 | data_retry_limit = 3; | 
|  | 415 | else | 
| Wey-Yi Guy | b744cb7 | 2010-03-23 11:37:59 -0700 | [diff] [blame] | 416 | data_retry_limit = IWLAGN_DEFAULT_TX_RETRY; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 417 | tx_cmd->data_retry_limit = data_retry_limit; | 
|  | 418 |  | 
|  | 419 | /* Set retry limit on RTS packets */ | 
|  | 420 | rts_retry_limit = RTS_DFAULT_RETRY_LIMIT; | 
|  | 421 | if (data_retry_limit < rts_retry_limit) | 
|  | 422 | rts_retry_limit = data_retry_limit; | 
|  | 423 | tx_cmd->rts_retry_limit = rts_retry_limit; | 
|  | 424 |  | 
|  | 425 | /* DATA packets will use the uCode station table for rate/antenna | 
|  | 426 | * selection */ | 
|  | 427 | if (ieee80211_is_data(fc)) { | 
|  | 428 | tx_cmd->initial_rate_index = 0; | 
|  | 429 | tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; | 
|  | 430 | return; | 
|  | 431 | } | 
|  | 432 |  | 
|  | 433 | /** | 
|  | 434 | * If the current TX rate stored in mac80211 has the MCS bit set, it's | 
|  | 435 | * not really a TX rate.  Thus, we use the lowest supported rate for | 
|  | 436 | * this band.  Also use the lowest supported rate if the stored rate | 
|  | 437 | * index is invalid. | 
|  | 438 | */ | 
|  | 439 | rate_idx = info->control.rates[0].idx; | 
|  | 440 | if (info->control.rates[0].flags & IEEE80211_TX_RC_MCS || | 
|  | 441 | (rate_idx < 0) || (rate_idx > IWL_RATE_COUNT_LEGACY)) | 
|  | 442 | rate_idx = rate_lowest_index(&priv->bands[info->band], | 
|  | 443 | info->control.sta); | 
|  | 444 | /* For 5 GHZ band, remap mac80211 rate indices into driver indices */ | 
|  | 445 | if (info->band == IEEE80211_BAND_5GHZ) | 
|  | 446 | rate_idx += IWL_FIRST_OFDM_RATE; | 
|  | 447 | /* Get PLCP rate for tx_cmd->rate_n_flags */ | 
|  | 448 | rate_plcp = iwl_rates[rate_idx].plcp; | 
|  | 449 | /* Zero out flags for this packet */ | 
|  | 450 | rate_flags = 0; | 
|  | 451 |  | 
|  | 452 | /* Set CCK flag as needed */ | 
|  | 453 | if ((rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE)) | 
|  | 454 | rate_flags |= RATE_MCS_CCK_MSK; | 
|  | 455 |  | 
|  | 456 | /* Set up RTS and CTS flags for certain packets */ | 
|  | 457 | switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { | 
|  | 458 | case cpu_to_le16(IEEE80211_STYPE_AUTH): | 
|  | 459 | case cpu_to_le16(IEEE80211_STYPE_DEAUTH): | 
|  | 460 | case cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ): | 
|  | 461 | case cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ): | 
|  | 462 | if (tx_cmd->tx_flags & TX_CMD_FLG_RTS_MSK) { | 
|  | 463 | tx_cmd->tx_flags &= ~TX_CMD_FLG_RTS_MSK; | 
|  | 464 | tx_cmd->tx_flags |= TX_CMD_FLG_CTS_MSK; | 
|  | 465 | } | 
|  | 466 | break; | 
|  | 467 | default: | 
|  | 468 | break; | 
|  | 469 | } | 
|  | 470 |  | 
|  | 471 | /* Set up antennas */ | 
| Johannes Berg | 0e1654f | 2010-05-18 02:48:36 -0700 | [diff] [blame] | 472 | priv->mgmt_tx_ant = iwl_toggle_tx_ant(priv, priv->mgmt_tx_ant, | 
|  | 473 | priv->hw_params.valid_tx_ant); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 474 | rate_flags |= iwl_ant_idx_to_flags(priv->mgmt_tx_ant); | 
|  | 475 |  | 
|  | 476 | /* Set the rate in the TX cmd */ | 
|  | 477 | tx_cmd->rate_n_flags = iwl_hw_set_rate_n_flags(rate_plcp, rate_flags); | 
|  | 478 | } | 
|  | 479 |  | 
|  | 480 | static void iwlagn_tx_cmd_build_hwcrypto(struct iwl_priv *priv, | 
|  | 481 | struct ieee80211_tx_info *info, | 
|  | 482 | struct iwl_tx_cmd *tx_cmd, | 
|  | 483 | struct sk_buff *skb_frag, | 
|  | 484 | int sta_id) | 
|  | 485 | { | 
|  | 486 | struct ieee80211_key_conf *keyconf = info->control.hw_key; | 
|  | 487 |  | 
|  | 488 | switch (keyconf->alg) { | 
|  | 489 | case ALG_CCMP: | 
|  | 490 | tx_cmd->sec_ctl = TX_CMD_SEC_CCM; | 
|  | 491 | memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); | 
|  | 492 | if (info->flags & IEEE80211_TX_CTL_AMPDU) | 
|  | 493 | tx_cmd->tx_flags |= TX_CMD_FLG_AGG_CCMP_MSK; | 
|  | 494 | IWL_DEBUG_TX(priv, "tx_cmd with AES hwcrypto\n"); | 
|  | 495 | break; | 
|  | 496 |  | 
|  | 497 | case ALG_TKIP: | 
|  | 498 | tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; | 
|  | 499 | ieee80211_get_tkip_key(keyconf, skb_frag, | 
|  | 500 | IEEE80211_TKIP_P2_KEY, tx_cmd->key); | 
|  | 501 | IWL_DEBUG_TX(priv, "tx_cmd with tkip hwcrypto\n"); | 
|  | 502 | break; | 
|  | 503 |  | 
|  | 504 | case ALG_WEP: | 
|  | 505 | tx_cmd->sec_ctl |= (TX_CMD_SEC_WEP | | 
|  | 506 | (keyconf->keyidx & TX_CMD_SEC_MSK) << TX_CMD_SEC_SHIFT); | 
|  | 507 |  | 
|  | 508 | if (keyconf->keylen == WEP_KEY_LEN_128) | 
|  | 509 | tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; | 
|  | 510 |  | 
|  | 511 | memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); | 
|  | 512 |  | 
|  | 513 | IWL_DEBUG_TX(priv, "Configuring packet for WEP encryption " | 
|  | 514 | "with key %d\n", keyconf->keyidx); | 
|  | 515 | break; | 
|  | 516 |  | 
|  | 517 | default: | 
|  | 518 | IWL_ERR(priv, "Unknown encode alg %d\n", keyconf->alg); | 
|  | 519 | break; | 
|  | 520 | } | 
|  | 521 | } | 
|  | 522 |  | 
|  | 523 | /* | 
|  | 524 | * start REPLY_TX command process | 
|  | 525 | */ | 
|  | 526 | int iwlagn_tx_skb(struct iwl_priv *priv, struct sk_buff *skb) | 
|  | 527 | { | 
|  | 528 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; | 
|  | 529 | struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | 
|  | 530 | struct ieee80211_sta *sta = info->control.sta; | 
|  | 531 | struct iwl_station_priv *sta_priv = NULL; | 
|  | 532 | struct iwl_tx_queue *txq; | 
|  | 533 | struct iwl_queue *q; | 
|  | 534 | struct iwl_device_cmd *out_cmd; | 
|  | 535 | struct iwl_cmd_meta *out_meta; | 
|  | 536 | struct iwl_tx_cmd *tx_cmd; | 
|  | 537 | int swq_id, txq_id; | 
|  | 538 | dma_addr_t phys_addr; | 
|  | 539 | dma_addr_t txcmd_phys; | 
|  | 540 | dma_addr_t scratch_phys; | 
|  | 541 | u16 len, len_org, firstlen, secondlen; | 
|  | 542 | u16 seq_number = 0; | 
|  | 543 | __le16 fc; | 
|  | 544 | u8 hdr_len; | 
|  | 545 | u8 sta_id; | 
|  | 546 | u8 wait_write_ptr = 0; | 
|  | 547 | u8 tid = 0; | 
|  | 548 | u8 *qc = NULL; | 
|  | 549 | unsigned long flags; | 
|  | 550 |  | 
|  | 551 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 552 | if (iwl_is_rfkill(priv)) { | 
|  | 553 | IWL_DEBUG_DROP(priv, "Dropping - RF KILL\n"); | 
|  | 554 | goto drop_unlock; | 
|  | 555 | } | 
|  | 556 |  | 
|  | 557 | fc = hdr->frame_control; | 
|  | 558 |  | 
|  | 559 | #ifdef CONFIG_IWLWIFI_DEBUG | 
|  | 560 | if (ieee80211_is_auth(fc)) | 
|  | 561 | IWL_DEBUG_TX(priv, "Sending AUTH frame\n"); | 
|  | 562 | else if (ieee80211_is_assoc_req(fc)) | 
|  | 563 | IWL_DEBUG_TX(priv, "Sending ASSOC frame\n"); | 
|  | 564 | else if (ieee80211_is_reassoc_req(fc)) | 
|  | 565 | IWL_DEBUG_TX(priv, "Sending REASSOC frame\n"); | 
|  | 566 | #endif | 
|  | 567 |  | 
|  | 568 | hdr_len = ieee80211_hdrlen(fc); | 
|  | 569 |  | 
| Johannes Berg | 2a87c26 | 2010-04-30 11:30:45 -0700 | [diff] [blame] | 570 | /* Find index into station table for destination station */ | 
| Johannes Berg | 0af8bca | 2010-04-30 14:08:00 -0700 | [diff] [blame] | 571 | sta_id = iwl_sta_id_or_broadcast(priv, info->control.sta); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 572 | if (sta_id == IWL_INVALID_STATION) { | 
|  | 573 | IWL_DEBUG_DROP(priv, "Dropping - INVALID STATION: %pM\n", | 
|  | 574 | hdr->addr1); | 
|  | 575 | goto drop_unlock; | 
|  | 576 | } | 
|  | 577 |  | 
|  | 578 | IWL_DEBUG_TX(priv, "station Id %d\n", sta_id); | 
|  | 579 |  | 
|  | 580 | if (sta) | 
|  | 581 | sta_priv = (void *)sta->drv_priv; | 
|  | 582 |  | 
|  | 583 | if (sta_priv && sta_id != priv->hw_params.bcast_sta_id && | 
|  | 584 | sta_priv->asleep) { | 
|  | 585 | WARN_ON(!(info->flags & IEEE80211_TX_CTL_PSPOLL_RESPONSE)); | 
|  | 586 | /* | 
|  | 587 | * This sends an asynchronous command to the device, | 
|  | 588 | * but we can rely on it being processed before the | 
|  | 589 | * next frame is processed -- and the next frame to | 
|  | 590 | * this station is the one that will consume this | 
|  | 591 | * counter. | 
|  | 592 | * For now set the counter to just 1 since we do not | 
|  | 593 | * support uAPSD yet. | 
|  | 594 | */ | 
|  | 595 | iwl_sta_modify_sleep_tx_count(priv, sta_id, 1); | 
|  | 596 | } | 
|  | 597 |  | 
|  | 598 | txq_id = get_queue_from_ac(skb_get_queue_mapping(skb)); | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 599 |  | 
|  | 600 | /* irqs already disabled/saved above when locking priv->lock */ | 
|  | 601 | spin_lock(&priv->sta_lock); | 
|  | 602 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 603 | if (ieee80211_is_data_qos(fc)) { | 
|  | 604 | qc = ieee80211_get_qos_ctl(hdr); | 
|  | 605 | tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 606 | if (WARN_ON_ONCE(tid >= MAX_TID_COUNT)) { | 
|  | 607 | spin_unlock(&priv->sta_lock); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 608 | goto drop_unlock; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 609 | } | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 610 | seq_number = priv->stations[sta_id].tid[tid].seq_number; | 
|  | 611 | seq_number &= IEEE80211_SCTL_SEQ; | 
|  | 612 | hdr->seq_ctrl = hdr->seq_ctrl & | 
|  | 613 | cpu_to_le16(IEEE80211_SCTL_FRAG); | 
|  | 614 | hdr->seq_ctrl |= cpu_to_le16(seq_number); | 
|  | 615 | seq_number += 0x10; | 
|  | 616 | /* aggregation is on for this <sta,tid> */ | 
|  | 617 | if (info->flags & IEEE80211_TX_CTL_AMPDU && | 
|  | 618 | priv->stations[sta_id].tid[tid].agg.state == IWL_AGG_ON) { | 
|  | 619 | txq_id = priv->stations[sta_id].tid[tid].agg.txq_id; | 
|  | 620 | } | 
|  | 621 | } | 
|  | 622 |  | 
|  | 623 | txq = &priv->txq[txq_id]; | 
|  | 624 | swq_id = txq->swq_id; | 
|  | 625 | q = &txq->q; | 
|  | 626 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 627 | if (unlikely(iwl_queue_space(q) < q->high_mark)) { | 
|  | 628 | spin_unlock(&priv->sta_lock); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 629 | goto drop_unlock; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 630 | } | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 631 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 632 | if (ieee80211_is_data_qos(fc)) { | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 633 | priv->stations[sta_id].tid[tid].tfds_in_queue++; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 634 | if (!ieee80211_has_morefrags(fc)) | 
|  | 635 | priv->stations[sta_id].tid[tid].seq_number = seq_number; | 
|  | 636 | } | 
|  | 637 |  | 
|  | 638 | spin_unlock(&priv->sta_lock); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 639 |  | 
|  | 640 | /* Set up driver data for this TFD */ | 
|  | 641 | memset(&(txq->txb[q->write_ptr]), 0, sizeof(struct iwl_tx_info)); | 
| Johannes Berg | ff0d91c | 2010-05-17 02:37:34 -0700 | [diff] [blame] | 642 | txq->txb[q->write_ptr].skb = skb; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 643 |  | 
|  | 644 | /* Set up first empty entry in queue's array of Tx/cmd buffers */ | 
|  | 645 | out_cmd = txq->cmd[q->write_ptr]; | 
|  | 646 | out_meta = &txq->meta[q->write_ptr]; | 
|  | 647 | tx_cmd = &out_cmd->cmd.tx; | 
|  | 648 | memset(&out_cmd->hdr, 0, sizeof(out_cmd->hdr)); | 
|  | 649 | memset(tx_cmd, 0, sizeof(struct iwl_tx_cmd)); | 
|  | 650 |  | 
|  | 651 | /* | 
|  | 652 | * Set up the Tx-command (not MAC!) header. | 
|  | 653 | * Store the chosen Tx queue and TFD index within the sequence field; | 
|  | 654 | * after Tx, uCode's Tx response will return this value so driver can | 
|  | 655 | * locate the frame within the tx queue and do post-tx processing. | 
|  | 656 | */ | 
|  | 657 | out_cmd->hdr.cmd = REPLY_TX; | 
|  | 658 | out_cmd->hdr.sequence = cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | | 
|  | 659 | INDEX_TO_SEQ(q->write_ptr))); | 
|  | 660 |  | 
|  | 661 | /* Copy MAC header from skb into command buffer */ | 
|  | 662 | memcpy(tx_cmd->hdr, hdr, hdr_len); | 
|  | 663 |  | 
|  | 664 |  | 
|  | 665 | /* Total # bytes to be transmitted */ | 
|  | 666 | len = (u16)skb->len; | 
|  | 667 | tx_cmd->len = cpu_to_le16(len); | 
|  | 668 |  | 
|  | 669 | if (info->control.hw_key) | 
|  | 670 | iwlagn_tx_cmd_build_hwcrypto(priv, info, tx_cmd, skb, sta_id); | 
|  | 671 |  | 
|  | 672 | /* TODO need this for burst mode later on */ | 
|  | 673 | iwlagn_tx_cmd_build_basic(priv, tx_cmd, info, hdr, sta_id); | 
|  | 674 | iwl_dbg_log_tx_data_frame(priv, len, hdr); | 
|  | 675 |  | 
|  | 676 | iwlagn_tx_cmd_build_rate(priv, tx_cmd, info, fc); | 
|  | 677 |  | 
|  | 678 | iwl_update_stats(priv, true, fc, len); | 
|  | 679 | /* | 
|  | 680 | * Use the first empty entry in this queue's command buffer array | 
|  | 681 | * to contain the Tx command and MAC header concatenated together | 
|  | 682 | * (payload data will be in another buffer). | 
|  | 683 | * Size of this varies, due to varying MAC header length. | 
|  | 684 | * If end is not dword aligned, we'll have 2 extra bytes at the end | 
|  | 685 | * of the MAC header (device reads on dword boundaries). | 
|  | 686 | * We'll tell device about this padding later. | 
|  | 687 | */ | 
|  | 688 | len = sizeof(struct iwl_tx_cmd) + | 
|  | 689 | sizeof(struct iwl_cmd_header) + hdr_len; | 
|  | 690 |  | 
|  | 691 | len_org = len; | 
|  | 692 | firstlen = len = (len + 3) & ~3; | 
|  | 693 |  | 
|  | 694 | if (len_org != len) | 
|  | 695 | len_org = 1; | 
|  | 696 | else | 
|  | 697 | len_org = 0; | 
|  | 698 |  | 
|  | 699 | /* Tell NIC about any 2-byte padding after MAC header */ | 
|  | 700 | if (len_org) | 
|  | 701 | tx_cmd->tx_flags |= TX_CMD_FLG_MH_PAD_MSK; | 
|  | 702 |  | 
|  | 703 | /* Physical address of this Tx command's header (not MAC header!), | 
|  | 704 | * within command buffer array. */ | 
|  | 705 | txcmd_phys = pci_map_single(priv->pci_dev, | 
|  | 706 | &out_cmd->hdr, len, | 
|  | 707 | PCI_DMA_BIDIRECTIONAL); | 
| FUJITA Tomonori | 2e72444 | 2010-06-03 14:19:20 +0900 | [diff] [blame] | 708 | dma_unmap_addr_set(out_meta, mapping, txcmd_phys); | 
|  | 709 | dma_unmap_len_set(out_meta, len, len); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 710 | /* Add buffer containing Tx command and MAC(!) header to TFD's | 
|  | 711 | * first entry */ | 
|  | 712 | priv->cfg->ops->lib->txq_attach_buf_to_tfd(priv, txq, | 
|  | 713 | txcmd_phys, len, 1, 0); | 
|  | 714 |  | 
|  | 715 | if (!ieee80211_has_morefrags(hdr->frame_control)) { | 
|  | 716 | txq->need_update = 1; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 717 | } else { | 
|  | 718 | wait_write_ptr = 1; | 
|  | 719 | txq->need_update = 0; | 
|  | 720 | } | 
|  | 721 |  | 
|  | 722 | /* Set up TFD's 2nd entry to point directly to remainder of skb, | 
|  | 723 | * if any (802.11 null frames have no payload). */ | 
|  | 724 | secondlen = len = skb->len - hdr_len; | 
|  | 725 | if (len) { | 
|  | 726 | phys_addr = pci_map_single(priv->pci_dev, skb->data + hdr_len, | 
|  | 727 | len, PCI_DMA_TODEVICE); | 
|  | 728 | priv->cfg->ops->lib->txq_attach_buf_to_tfd(priv, txq, | 
|  | 729 | phys_addr, len, | 
|  | 730 | 0, 0); | 
|  | 731 | } | 
|  | 732 |  | 
|  | 733 | scratch_phys = txcmd_phys + sizeof(struct iwl_cmd_header) + | 
|  | 734 | offsetof(struct iwl_tx_cmd, scratch); | 
|  | 735 |  | 
|  | 736 | len = sizeof(struct iwl_tx_cmd) + | 
|  | 737 | sizeof(struct iwl_cmd_header) + hdr_len; | 
|  | 738 | /* take back ownership of DMA buffer to enable update */ | 
|  | 739 | pci_dma_sync_single_for_cpu(priv->pci_dev, txcmd_phys, | 
|  | 740 | len, PCI_DMA_BIDIRECTIONAL); | 
|  | 741 | tx_cmd->dram_lsb_ptr = cpu_to_le32(scratch_phys); | 
|  | 742 | tx_cmd->dram_msb_ptr = iwl_get_dma_hi_addr(scratch_phys); | 
|  | 743 |  | 
| Frans Pop | 91dd6c2 | 2010-03-24 14:19:58 -0700 | [diff] [blame] | 744 | IWL_DEBUG_TX(priv, "sequence nr = 0X%x\n", | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 745 | le16_to_cpu(out_cmd->hdr.sequence)); | 
| Frans Pop | 91dd6c2 | 2010-03-24 14:19:58 -0700 | [diff] [blame] | 746 | IWL_DEBUG_TX(priv, "tx_flags = 0X%x\n", le32_to_cpu(tx_cmd->tx_flags)); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 747 | iwl_print_hex_dump(priv, IWL_DL_TX, (u8 *)tx_cmd, sizeof(*tx_cmd)); | 
|  | 748 | iwl_print_hex_dump(priv, IWL_DL_TX, (u8 *)tx_cmd->hdr, hdr_len); | 
|  | 749 |  | 
|  | 750 | /* Set up entry for this TFD in Tx byte-count array */ | 
|  | 751 | if (info->flags & IEEE80211_TX_CTL_AMPDU) | 
|  | 752 | priv->cfg->ops->lib->txq_update_byte_cnt_tbl(priv, txq, | 
|  | 753 | le16_to_cpu(tx_cmd->len)); | 
|  | 754 |  | 
|  | 755 | pci_dma_sync_single_for_device(priv->pci_dev, txcmd_phys, | 
|  | 756 | len, PCI_DMA_BIDIRECTIONAL); | 
|  | 757 |  | 
|  | 758 | trace_iwlwifi_dev_tx(priv, | 
|  | 759 | &((struct iwl_tfd *)txq->tfds)[txq->q.write_ptr], | 
|  | 760 | sizeof(struct iwl_tfd), | 
|  | 761 | &out_cmd->hdr, firstlen, | 
|  | 762 | skb->data + hdr_len, secondlen); | 
|  | 763 |  | 
|  | 764 | /* Tell device the write index *just past* this latest filled TFD */ | 
|  | 765 | q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd); | 
|  | 766 | iwl_txq_update_write_ptr(priv, txq); | 
|  | 767 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 768 |  | 
|  | 769 | /* | 
|  | 770 | * At this point the frame is "transmitted" successfully | 
|  | 771 | * and we will get a TX status notification eventually, | 
|  | 772 | * regardless of the value of ret. "ret" only indicates | 
|  | 773 | * whether or not we should update the write pointer. | 
|  | 774 | */ | 
|  | 775 |  | 
|  | 776 | /* avoid atomic ops if it isn't an associated client */ | 
|  | 777 | if (sta_priv && sta_priv->client) | 
|  | 778 | atomic_inc(&sta_priv->pending_frames); | 
|  | 779 |  | 
|  | 780 | if ((iwl_queue_space(q) < q->high_mark) && priv->mac80211_registered) { | 
|  | 781 | if (wait_write_ptr) { | 
|  | 782 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 783 | txq->need_update = 1; | 
|  | 784 | iwl_txq_update_write_ptr(priv, txq); | 
|  | 785 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 786 | } else { | 
|  | 787 | iwl_stop_queue(priv, txq->swq_id); | 
|  | 788 | } | 
|  | 789 | } | 
|  | 790 |  | 
|  | 791 | return 0; | 
|  | 792 |  | 
|  | 793 | drop_unlock: | 
|  | 794 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 795 | return -1; | 
|  | 796 | } | 
|  | 797 |  | 
|  | 798 | static inline int iwlagn_alloc_dma_ptr(struct iwl_priv *priv, | 
|  | 799 | struct iwl_dma_ptr *ptr, size_t size) | 
|  | 800 | { | 
|  | 801 | ptr->addr = dma_alloc_coherent(&priv->pci_dev->dev, size, &ptr->dma, | 
|  | 802 | GFP_KERNEL); | 
|  | 803 | if (!ptr->addr) | 
|  | 804 | return -ENOMEM; | 
|  | 805 | ptr->size = size; | 
|  | 806 | return 0; | 
|  | 807 | } | 
|  | 808 |  | 
|  | 809 | static inline void iwlagn_free_dma_ptr(struct iwl_priv *priv, | 
|  | 810 | struct iwl_dma_ptr *ptr) | 
|  | 811 | { | 
|  | 812 | if (unlikely(!ptr->addr)) | 
|  | 813 | return; | 
|  | 814 |  | 
|  | 815 | dma_free_coherent(&priv->pci_dev->dev, ptr->size, ptr->addr, ptr->dma); | 
|  | 816 | memset(ptr, 0, sizeof(*ptr)); | 
|  | 817 | } | 
|  | 818 |  | 
|  | 819 | /** | 
|  | 820 | * iwlagn_hw_txq_ctx_free - Free TXQ Context | 
|  | 821 | * | 
|  | 822 | * Destroy all TX DMA queues and structures | 
|  | 823 | */ | 
|  | 824 | void iwlagn_hw_txq_ctx_free(struct iwl_priv *priv) | 
|  | 825 | { | 
|  | 826 | int txq_id; | 
|  | 827 |  | 
|  | 828 | /* Tx queues */ | 
|  | 829 | if (priv->txq) { | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 830 | for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 831 | if (txq_id == IWL_CMD_QUEUE_NUM) | 
|  | 832 | iwl_cmd_queue_free(priv); | 
|  | 833 | else | 
|  | 834 | iwl_tx_queue_free(priv, txq_id); | 
|  | 835 | } | 
|  | 836 | iwlagn_free_dma_ptr(priv, &priv->kw); | 
|  | 837 |  | 
|  | 838 | iwlagn_free_dma_ptr(priv, &priv->scd_bc_tbls); | 
|  | 839 |  | 
|  | 840 | /* free tx queue structure */ | 
|  | 841 | iwl_free_txq_mem(priv); | 
|  | 842 | } | 
|  | 843 |  | 
|  | 844 | /** | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 845 | * iwlagn_txq_ctx_alloc - allocate TX queue context | 
|  | 846 | * Allocate all Tx DMA structures and initialize them | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 847 | * | 
|  | 848 | * @param priv | 
|  | 849 | * @return error code | 
|  | 850 | */ | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 851 | int iwlagn_txq_ctx_alloc(struct iwl_priv *priv) | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 852 | { | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 853 | int ret; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 854 | int txq_id, slots_num; | 
|  | 855 | unsigned long flags; | 
|  | 856 |  | 
|  | 857 | /* Free all tx/cmd queues and keep-warm buffer */ | 
|  | 858 | iwlagn_hw_txq_ctx_free(priv); | 
|  | 859 |  | 
|  | 860 | ret = iwlagn_alloc_dma_ptr(priv, &priv->scd_bc_tbls, | 
|  | 861 | priv->hw_params.scd_bc_tbls_size); | 
|  | 862 | if (ret) { | 
|  | 863 | IWL_ERR(priv, "Scheduler BC Table allocation failed\n"); | 
|  | 864 | goto error_bc_tbls; | 
|  | 865 | } | 
|  | 866 | /* Alloc keep-warm buffer */ | 
|  | 867 | ret = iwlagn_alloc_dma_ptr(priv, &priv->kw, IWL_KW_SIZE); | 
|  | 868 | if (ret) { | 
|  | 869 | IWL_ERR(priv, "Keep Warm allocation failed\n"); | 
|  | 870 | goto error_kw; | 
|  | 871 | } | 
|  | 872 |  | 
|  | 873 | /* allocate tx queue structure */ | 
|  | 874 | ret = iwl_alloc_txq_mem(priv); | 
|  | 875 | if (ret) | 
|  | 876 | goto error; | 
|  | 877 |  | 
|  | 878 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 879 |  | 
|  | 880 | /* Turn off all Tx DMA fifos */ | 
|  | 881 | priv->cfg->ops->lib->txq_set_sched(priv, 0); | 
|  | 882 |  | 
|  | 883 | /* Tell NIC where to find the "keep warm" buffer */ | 
|  | 884 | iwl_write_direct32(priv, FH_KW_MEM_ADDR_REG, priv->kw.dma >> 4); | 
|  | 885 |  | 
|  | 886 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 887 |  | 
|  | 888 | /* Alloc and init all Tx queues, including the command queue (#4) */ | 
|  | 889 | for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) { | 
|  | 890 | slots_num = (txq_id == IWL_CMD_QUEUE_NUM) ? | 
|  | 891 | TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS; | 
|  | 892 | ret = iwl_tx_queue_init(priv, &priv->txq[txq_id], slots_num, | 
|  | 893 | txq_id); | 
|  | 894 | if (ret) { | 
|  | 895 | IWL_ERR(priv, "Tx %d queue init failed\n", txq_id); | 
|  | 896 | goto error; | 
|  | 897 | } | 
|  | 898 | } | 
|  | 899 |  | 
|  | 900 | return ret; | 
|  | 901 |  | 
|  | 902 | error: | 
|  | 903 | iwlagn_hw_txq_ctx_free(priv); | 
|  | 904 | iwlagn_free_dma_ptr(priv, &priv->kw); | 
|  | 905 | error_kw: | 
|  | 906 | iwlagn_free_dma_ptr(priv, &priv->scd_bc_tbls); | 
|  | 907 | error_bc_tbls: | 
|  | 908 | return ret; | 
|  | 909 | } | 
|  | 910 |  | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 911 | void iwlagn_txq_ctx_reset(struct iwl_priv *priv) | 
|  | 912 | { | 
|  | 913 | int txq_id, slots_num; | 
|  | 914 | unsigned long flags; | 
|  | 915 |  | 
|  | 916 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 917 |  | 
|  | 918 | /* Turn off all Tx DMA fifos */ | 
|  | 919 | priv->cfg->ops->lib->txq_set_sched(priv, 0); | 
|  | 920 |  | 
|  | 921 | /* Tell NIC where to find the "keep warm" buffer */ | 
|  | 922 | iwl_write_direct32(priv, FH_KW_MEM_ADDR_REG, priv->kw.dma >> 4); | 
|  | 923 |  | 
|  | 924 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 925 |  | 
|  | 926 | /* Alloc and init all Tx queues, including the command queue (#4) */ | 
|  | 927 | for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) { | 
|  | 928 | slots_num = txq_id == IWL_CMD_QUEUE_NUM ? | 
|  | 929 | TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS; | 
|  | 930 | iwl_tx_queue_reset(priv, &priv->txq[txq_id], slots_num, txq_id); | 
|  | 931 | } | 
|  | 932 | } | 
|  | 933 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 934 | /** | 
| Zhu Yi | 470058e | 2010-04-02 13:38:54 -0700 | [diff] [blame] | 935 | * iwlagn_txq_ctx_stop - Stop all Tx DMA channels | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 936 | */ | 
|  | 937 | void iwlagn_txq_ctx_stop(struct iwl_priv *priv) | 
|  | 938 | { | 
|  | 939 | int ch; | 
|  | 940 | unsigned long flags; | 
|  | 941 |  | 
|  | 942 | /* Turn off all Tx DMA fifos */ | 
|  | 943 | spin_lock_irqsave(&priv->lock, flags); | 
|  | 944 |  | 
|  | 945 | priv->cfg->ops->lib->txq_set_sched(priv, 0); | 
|  | 946 |  | 
|  | 947 | /* Stop each Tx DMA channel, and wait for it to be idle */ | 
|  | 948 | for (ch = 0; ch < priv->hw_params.dma_chnl_num; ch++) { | 
|  | 949 | iwl_write_direct32(priv, FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0); | 
|  | 950 | iwl_poll_direct_bit(priv, FH_TSSR_TX_STATUS_REG, | 
|  | 951 | FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch), | 
|  | 952 | 1000); | 
|  | 953 | } | 
|  | 954 | spin_unlock_irqrestore(&priv->lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 955 | } | 
|  | 956 |  | 
|  | 957 | /* | 
|  | 958 | * Find first available (lowest unused) Tx Queue, mark it "active". | 
|  | 959 | * Called only when finding queue for aggregation. | 
|  | 960 | * Should never return anything < 7, because they should already | 
|  | 961 | * be in use as EDCA AC (0-3), Command (4), reserved (5, 6) | 
|  | 962 | */ | 
|  | 963 | static int iwlagn_txq_ctx_activate_free(struct iwl_priv *priv) | 
|  | 964 | { | 
|  | 965 | int txq_id; | 
|  | 966 |  | 
|  | 967 | for (txq_id = 0; txq_id < priv->hw_params.max_txq_num; txq_id++) | 
|  | 968 | if (!test_and_set_bit(txq_id, &priv->txq_ctx_active_msk)) | 
|  | 969 | return txq_id; | 
|  | 970 | return -1; | 
|  | 971 | } | 
|  | 972 |  | 
| Johannes Berg | 832f47e | 2010-04-29 04:43:07 -0700 | [diff] [blame] | 973 | int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 974 | struct ieee80211_sta *sta, u16 tid, u16 *ssn) | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 975 | { | 
|  | 976 | int sta_id; | 
|  | 977 | int tx_fifo; | 
|  | 978 | int txq_id; | 
|  | 979 | int ret; | 
|  | 980 | unsigned long flags; | 
|  | 981 | struct iwl_tid_data *tid_data; | 
|  | 982 |  | 
|  | 983 | tx_fifo = get_fifo_from_tid(tid); | 
|  | 984 | if (unlikely(tx_fifo < 0)) | 
|  | 985 | return tx_fifo; | 
|  | 986 |  | 
|  | 987 | IWL_WARN(priv, "%s on ra = %pM tid = %d\n", | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 988 | __func__, sta->addr, tid); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 989 |  | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 990 | sta_id = iwl_sta_id(sta); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 991 | if (sta_id == IWL_INVALID_STATION) { | 
|  | 992 | IWL_ERR(priv, "Start AGG on invalid station\n"); | 
|  | 993 | return -ENXIO; | 
|  | 994 | } | 
|  | 995 | if (unlikely(tid >= MAX_TID_COUNT)) | 
|  | 996 | return -EINVAL; | 
|  | 997 |  | 
|  | 998 | if (priv->stations[sta_id].tid[tid].agg.state != IWL_AGG_OFF) { | 
|  | 999 | IWL_ERR(priv, "Start AGG when state is not IWL_AGG_OFF !\n"); | 
|  | 1000 | return -ENXIO; | 
|  | 1001 | } | 
|  | 1002 |  | 
|  | 1003 | txq_id = iwlagn_txq_ctx_activate_free(priv); | 
|  | 1004 | if (txq_id == -1) { | 
|  | 1005 | IWL_ERR(priv, "No free aggregation queue available\n"); | 
|  | 1006 | return -ENXIO; | 
|  | 1007 | } | 
|  | 1008 |  | 
|  | 1009 | spin_lock_irqsave(&priv->sta_lock, flags); | 
|  | 1010 | tid_data = &priv->stations[sta_id].tid[tid]; | 
|  | 1011 | *ssn = SEQ_TO_SN(tid_data->seq_number); | 
|  | 1012 | tid_data->agg.txq_id = txq_id; | 
| Shanyu Zhao | c2845d0 | 2010-04-14 15:35:14 -0700 | [diff] [blame] | 1013 | priv->txq[txq_id].swq_id = iwl_virtual_agg_queue_num(get_ac_from_tid(tid), txq_id); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1014 | spin_unlock_irqrestore(&priv->sta_lock, flags); | 
|  | 1015 |  | 
|  | 1016 | ret = priv->cfg->ops->lib->txq_agg_enable(priv, txq_id, tx_fifo, | 
|  | 1017 | sta_id, tid, *ssn); | 
|  | 1018 | if (ret) | 
|  | 1019 | return ret; | 
|  | 1020 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1021 | spin_lock_irqsave(&priv->sta_lock, flags); | 
|  | 1022 | tid_data = &priv->stations[sta_id].tid[tid]; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1023 | if (tid_data->tfds_in_queue == 0) { | 
|  | 1024 | IWL_DEBUG_HT(priv, "HW queue is empty\n"); | 
|  | 1025 | tid_data->agg.state = IWL_AGG_ON; | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 1026 | ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1027 | } else { | 
|  | 1028 | IWL_DEBUG_HT(priv, "HW queue is NOT empty: %d packets in HW queue\n", | 
|  | 1029 | tid_data->tfds_in_queue); | 
|  | 1030 | tid_data->agg.state = IWL_EMPTYING_HW_QUEUE_ADDBA; | 
|  | 1031 | } | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1032 | spin_unlock_irqrestore(&priv->sta_lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1033 | return ret; | 
|  | 1034 | } | 
|  | 1035 |  | 
| Johannes Berg | 832f47e | 2010-04-29 04:43:07 -0700 | [diff] [blame] | 1036 | int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 1037 | struct ieee80211_sta *sta, u16 tid) | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1038 | { | 
|  | 1039 | int tx_fifo_id, txq_id, sta_id, ssn = -1; | 
|  | 1040 | struct iwl_tid_data *tid_data; | 
|  | 1041 | int write_ptr, read_ptr; | 
|  | 1042 | unsigned long flags; | 
|  | 1043 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1044 | tx_fifo_id = get_fifo_from_tid(tid); | 
|  | 1045 | if (unlikely(tx_fifo_id < 0)) | 
|  | 1046 | return tx_fifo_id; | 
|  | 1047 |  | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 1048 | sta_id = iwl_sta_id(sta); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1049 |  | 
|  | 1050 | if (sta_id == IWL_INVALID_STATION) { | 
|  | 1051 | IWL_ERR(priv, "Invalid station for AGG tid %d\n", tid); | 
|  | 1052 | return -ENXIO; | 
|  | 1053 | } | 
|  | 1054 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1055 | spin_lock_irqsave(&priv->sta_lock, flags); | 
|  | 1056 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1057 | if (priv->stations[sta_id].tid[tid].agg.state == | 
|  | 1058 | IWL_EMPTYING_HW_QUEUE_ADDBA) { | 
|  | 1059 | IWL_DEBUG_HT(priv, "AGG stop before setup done\n"); | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 1060 | ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1061 | priv->stations[sta_id].tid[tid].agg.state = IWL_AGG_OFF; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1062 | spin_unlock_irqrestore(&priv->sta_lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1063 | return 0; | 
|  | 1064 | } | 
|  | 1065 |  | 
|  | 1066 | if (priv->stations[sta_id].tid[tid].agg.state != IWL_AGG_ON) | 
|  | 1067 | IWL_WARN(priv, "Stopping AGG while state not ON or starting\n"); | 
|  | 1068 |  | 
|  | 1069 | tid_data = &priv->stations[sta_id].tid[tid]; | 
|  | 1070 | ssn = (tid_data->seq_number & IEEE80211_SCTL_SEQ) >> 4; | 
|  | 1071 | txq_id = tid_data->agg.txq_id; | 
|  | 1072 | write_ptr = priv->txq[txq_id].q.write_ptr; | 
|  | 1073 | read_ptr = priv->txq[txq_id].q.read_ptr; | 
|  | 1074 |  | 
|  | 1075 | /* The queue is not empty */ | 
|  | 1076 | if (write_ptr != read_ptr) { | 
|  | 1077 | IWL_DEBUG_HT(priv, "Stopping a non empty AGG HW QUEUE\n"); | 
|  | 1078 | priv->stations[sta_id].tid[tid].agg.state = | 
|  | 1079 | IWL_EMPTYING_HW_QUEUE_DELBA; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1080 | spin_unlock_irqrestore(&priv->sta_lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1081 | return 0; | 
|  | 1082 | } | 
|  | 1083 |  | 
|  | 1084 | IWL_DEBUG_HT(priv, "HW queue is empty\n"); | 
|  | 1085 | priv->stations[sta_id].tid[tid].agg.state = IWL_AGG_OFF; | 
|  | 1086 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1087 | /* do not restore/save irqs */ | 
|  | 1088 | spin_unlock(&priv->sta_lock); | 
|  | 1089 | spin_lock(&priv->lock); | 
|  | 1090 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1091 | /* | 
|  | 1092 | * the only reason this call can fail is queue number out of range, | 
|  | 1093 | * which can happen if uCode is reloaded and all the station | 
|  | 1094 | * information are lost. if it is outside the range, there is no need | 
|  | 1095 | * to deactivate the uCode queue, just return "success" to allow | 
|  | 1096 | *  mac80211 to clean up it own data. | 
|  | 1097 | */ | 
|  | 1098 | priv->cfg->ops->lib->txq_agg_disable(priv, txq_id, ssn, | 
|  | 1099 | tx_fifo_id); | 
|  | 1100 | spin_unlock_irqrestore(&priv->lock, flags); | 
|  | 1101 |  | 
| Johannes Berg | 619753f | 2010-04-30 11:30:46 -0700 | [diff] [blame] | 1102 | ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1103 |  | 
|  | 1104 | return 0; | 
|  | 1105 | } | 
|  | 1106 |  | 
|  | 1107 | int iwlagn_txq_check_empty(struct iwl_priv *priv, | 
|  | 1108 | int sta_id, u8 tid, int txq_id) | 
|  | 1109 | { | 
|  | 1110 | struct iwl_queue *q = &priv->txq[txq_id].q; | 
|  | 1111 | u8 *addr = priv->stations[sta_id].sta.sta.addr; | 
|  | 1112 | struct iwl_tid_data *tid_data = &priv->stations[sta_id].tid[tid]; | 
|  | 1113 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1114 | WARN_ON(!spin_is_locked(&priv->sta_lock)); | 
|  | 1115 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1116 | switch (priv->stations[sta_id].tid[tid].agg.state) { | 
|  | 1117 | case IWL_EMPTYING_HW_QUEUE_DELBA: | 
|  | 1118 | /* We are reclaiming the last packet of the */ | 
|  | 1119 | /* aggregated HW queue */ | 
|  | 1120 | if ((txq_id  == tid_data->agg.txq_id) && | 
|  | 1121 | (q->read_ptr == q->write_ptr)) { | 
|  | 1122 | u16 ssn = SEQ_TO_SN(tid_data->seq_number); | 
|  | 1123 | int tx_fifo = get_fifo_from_tid(tid); | 
|  | 1124 | IWL_DEBUG_HT(priv, "HW queue empty: continue DELBA flow\n"); | 
|  | 1125 | priv->cfg->ops->lib->txq_agg_disable(priv, txq_id, | 
|  | 1126 | ssn, tx_fifo); | 
|  | 1127 | tid_data->agg.state = IWL_AGG_OFF; | 
|  | 1128 | ieee80211_stop_tx_ba_cb_irqsafe(priv->vif, addr, tid); | 
|  | 1129 | } | 
|  | 1130 | break; | 
|  | 1131 | case IWL_EMPTYING_HW_QUEUE_ADDBA: | 
|  | 1132 | /* We are reclaiming the last packet of the queue */ | 
|  | 1133 | if (tid_data->tfds_in_queue == 0) { | 
|  | 1134 | IWL_DEBUG_HT(priv, "HW queue empty: continue ADDBA flow\n"); | 
|  | 1135 | tid_data->agg.state = IWL_AGG_ON; | 
|  | 1136 | ieee80211_start_tx_ba_cb_irqsafe(priv->vif, addr, tid); | 
|  | 1137 | } | 
|  | 1138 | break; | 
|  | 1139 | } | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1140 |  | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1141 | return 0; | 
|  | 1142 | } | 
|  | 1143 |  | 
|  | 1144 | static void iwlagn_tx_status(struct iwl_priv *priv, struct sk_buff *skb) | 
|  | 1145 | { | 
|  | 1146 | struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; | 
|  | 1147 | struct ieee80211_sta *sta; | 
|  | 1148 | struct iwl_station_priv *sta_priv; | 
|  | 1149 |  | 
| Johannes Berg | 6db6340 | 2010-06-07 21:20:38 +0200 | [diff] [blame] | 1150 | rcu_read_lock(); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1151 | sta = ieee80211_find_sta(priv->vif, hdr->addr1); | 
|  | 1152 | if (sta) { | 
|  | 1153 | sta_priv = (void *)sta->drv_priv; | 
|  | 1154 | /* avoid atomic ops if this isn't a client */ | 
|  | 1155 | if (sta_priv->client && | 
|  | 1156 | atomic_dec_return(&sta_priv->pending_frames) == 0) | 
|  | 1157 | ieee80211_sta_block_awake(priv->hw, sta, false); | 
|  | 1158 | } | 
| Johannes Berg | 6db6340 | 2010-06-07 21:20:38 +0200 | [diff] [blame] | 1159 | rcu_read_unlock(); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1160 |  | 
|  | 1161 | ieee80211_tx_status_irqsafe(priv->hw, skb); | 
|  | 1162 | } | 
|  | 1163 |  | 
|  | 1164 | int iwlagn_tx_queue_reclaim(struct iwl_priv *priv, int txq_id, int index) | 
|  | 1165 | { | 
|  | 1166 | struct iwl_tx_queue *txq = &priv->txq[txq_id]; | 
|  | 1167 | struct iwl_queue *q = &txq->q; | 
|  | 1168 | struct iwl_tx_info *tx_info; | 
|  | 1169 | int nfreed = 0; | 
|  | 1170 | struct ieee80211_hdr *hdr; | 
|  | 1171 |  | 
|  | 1172 | if ((index >= q->n_bd) || (iwl_queue_used(q, index) == 0)) { | 
|  | 1173 | IWL_ERR(priv, "Read index for DMA queue txq id (%d), index %d, " | 
|  | 1174 | "is out of range [0-%d] %d %d.\n", txq_id, | 
|  | 1175 | index, q->n_bd, q->write_ptr, q->read_ptr); | 
|  | 1176 | return 0; | 
|  | 1177 | } | 
|  | 1178 |  | 
|  | 1179 | for (index = iwl_queue_inc_wrap(index, q->n_bd); | 
|  | 1180 | q->read_ptr != index; | 
|  | 1181 | q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd)) { | 
|  | 1182 |  | 
|  | 1183 | tx_info = &txq->txb[txq->q.read_ptr]; | 
| Johannes Berg | ff0d91c | 2010-05-17 02:37:34 -0700 | [diff] [blame] | 1184 | iwlagn_tx_status(priv, tx_info->skb); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1185 |  | 
| Johannes Berg | ff0d91c | 2010-05-17 02:37:34 -0700 | [diff] [blame] | 1186 | hdr = (struct ieee80211_hdr *)tx_info->skb->data; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1187 | if (hdr && ieee80211_is_data_qos(hdr->frame_control)) | 
|  | 1188 | nfreed++; | 
| Johannes Berg | ff0d91c | 2010-05-17 02:37:34 -0700 | [diff] [blame] | 1189 | tx_info->skb = NULL; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1190 |  | 
|  | 1191 | if (priv->cfg->ops->lib->txq_inval_byte_cnt_tbl) | 
|  | 1192 | priv->cfg->ops->lib->txq_inval_byte_cnt_tbl(priv, txq); | 
|  | 1193 |  | 
|  | 1194 | priv->cfg->ops->lib->txq_free_tfd(priv, txq); | 
|  | 1195 | } | 
|  | 1196 | return nfreed; | 
|  | 1197 | } | 
|  | 1198 |  | 
|  | 1199 | /** | 
|  | 1200 | * iwlagn_tx_status_reply_compressed_ba - Update tx status from block-ack | 
|  | 1201 | * | 
|  | 1202 | * Go through block-ack's bitmap of ACK'd frames, update driver's record of | 
|  | 1203 | * ACK vs. not.  This gets sent to mac80211, then to rate scaling algo. | 
|  | 1204 | */ | 
|  | 1205 | static int iwlagn_tx_status_reply_compressed_ba(struct iwl_priv *priv, | 
|  | 1206 | struct iwl_ht_agg *agg, | 
|  | 1207 | struct iwl_compressed_ba_resp *ba_resp) | 
|  | 1208 |  | 
|  | 1209 | { | 
|  | 1210 | int i, sh, ack; | 
|  | 1211 | u16 seq_ctl = le16_to_cpu(ba_resp->seq_ctl); | 
|  | 1212 | u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); | 
| Daniel Halperin | 02cd8de | 2010-05-24 18:41:30 -0700 | [diff] [blame] | 1213 | u64 bitmap, sent_bitmap; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1214 | int successes = 0; | 
|  | 1215 | struct ieee80211_tx_info *info; | 
|  | 1216 |  | 
|  | 1217 | if (unlikely(!agg->wait_for_ba))  { | 
|  | 1218 | IWL_ERR(priv, "Received BA when not expected\n"); | 
|  | 1219 | return -EINVAL; | 
|  | 1220 | } | 
|  | 1221 |  | 
|  | 1222 | /* Mark that the expected block-ack response arrived */ | 
|  | 1223 | agg->wait_for_ba = 0; | 
|  | 1224 | IWL_DEBUG_TX_REPLY(priv, "BA %d %d\n", agg->start_idx, ba_resp->seq_ctl); | 
|  | 1225 |  | 
|  | 1226 | /* Calculate shift to align block-ack bits with our Tx window bits */ | 
|  | 1227 | sh = agg->start_idx - SEQ_TO_INDEX(seq_ctl >> 4); | 
|  | 1228 | if (sh < 0) /* tbw something is wrong with indices */ | 
|  | 1229 | sh += 0x100; | 
|  | 1230 |  | 
|  | 1231 | /* don't use 64-bit values for now */ | 
|  | 1232 | bitmap = le64_to_cpu(ba_resp->bitmap) >> sh; | 
|  | 1233 |  | 
|  | 1234 | if (agg->frame_count > (64 - sh)) { | 
|  | 1235 | IWL_DEBUG_TX_REPLY(priv, "more frames than bitmap size"); | 
|  | 1236 | return -1; | 
|  | 1237 | } | 
|  | 1238 |  | 
|  | 1239 | /* check for success or failure according to the | 
|  | 1240 | * transmitted bitmap and block-ack bitmap */ | 
| Daniel Halperin | 02cd8de | 2010-05-24 18:41:30 -0700 | [diff] [blame] | 1241 | sent_bitmap = bitmap & agg->bitmap; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1242 |  | 
|  | 1243 | /* For each frame attempted in aggregation, | 
|  | 1244 | * update driver's record of tx frame's status. */ | 
| Daniel Halperin | 02cd8de | 2010-05-24 18:41:30 -0700 | [diff] [blame] | 1245 | i = 0; | 
|  | 1246 | while (sent_bitmap) { | 
|  | 1247 | ack = sent_bitmap & 1ULL; | 
|  | 1248 | successes += ack; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1249 | IWL_DEBUG_TX_REPLY(priv, "%s ON i=%d idx=%d raw=%d\n", | 
|  | 1250 | ack ? "ACK" : "NACK", i, (agg->start_idx + i) & 0xff, | 
|  | 1251 | agg->start_idx + i); | 
| Daniel Halperin | 02cd8de | 2010-05-24 18:41:30 -0700 | [diff] [blame] | 1252 | sent_bitmap >>= 1; | 
|  | 1253 | ++i; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1254 | } | 
|  | 1255 |  | 
| Johannes Berg | ff0d91c | 2010-05-17 02:37:34 -0700 | [diff] [blame] | 1256 | info = IEEE80211_SKB_CB(priv->txq[scd_flow].txb[agg->start_idx].skb); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1257 | memset(&info->status, 0, sizeof(info->status)); | 
|  | 1258 | info->flags |= IEEE80211_TX_STAT_ACK; | 
|  | 1259 | info->flags |= IEEE80211_TX_STAT_AMPDU; | 
| Daniel Halperin | e3a3cd8 | 2010-04-18 09:27:58 -0700 | [diff] [blame] | 1260 | info->status.ampdu_ack_len = successes; | 
| Daniel Halperin | e3a3cd8 | 2010-04-18 09:27:58 -0700 | [diff] [blame] | 1261 | info->status.ampdu_len = agg->frame_count; | 
| Wey-Yi Guy | 8d80108 | 2010-03-17 13:34:36 -0700 | [diff] [blame] | 1262 | iwlagn_hwrate_to_tx_control(priv, agg->rate_n_flags, info); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1263 |  | 
|  | 1264 | IWL_DEBUG_TX_REPLY(priv, "Bitmap %llx\n", (unsigned long long)bitmap); | 
|  | 1265 |  | 
|  | 1266 | return 0; | 
|  | 1267 | } | 
|  | 1268 |  | 
|  | 1269 | /** | 
| Wey-Yi Guy | 8d80108 | 2010-03-17 13:34:36 -0700 | [diff] [blame] | 1270 | * translate ucode response to mac80211 tx status control values | 
|  | 1271 | */ | 
|  | 1272 | void iwlagn_hwrate_to_tx_control(struct iwl_priv *priv, u32 rate_n_flags, | 
|  | 1273 | struct ieee80211_tx_info *info) | 
|  | 1274 | { | 
|  | 1275 | struct ieee80211_tx_rate *r = &info->control.rates[0]; | 
|  | 1276 |  | 
|  | 1277 | info->antenna_sel_tx = | 
|  | 1278 | ((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); | 
|  | 1279 | if (rate_n_flags & RATE_MCS_HT_MSK) | 
|  | 1280 | r->flags |= IEEE80211_TX_RC_MCS; | 
|  | 1281 | if (rate_n_flags & RATE_MCS_GF_MSK) | 
|  | 1282 | r->flags |= IEEE80211_TX_RC_GREEN_FIELD; | 
|  | 1283 | if (rate_n_flags & RATE_MCS_HT40_MSK) | 
|  | 1284 | r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; | 
|  | 1285 | if (rate_n_flags & RATE_MCS_DUP_MSK) | 
|  | 1286 | r->flags |= IEEE80211_TX_RC_DUP_DATA; | 
|  | 1287 | if (rate_n_flags & RATE_MCS_SGI_MSK) | 
|  | 1288 | r->flags |= IEEE80211_TX_RC_SHORT_GI; | 
|  | 1289 | r->idx = iwlagn_hwrate_to_mac80211_idx(rate_n_flags, info->band); | 
|  | 1290 | } | 
|  | 1291 |  | 
|  | 1292 | /** | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1293 | * iwlagn_rx_reply_compressed_ba - Handler for REPLY_COMPRESSED_BA | 
|  | 1294 | * | 
|  | 1295 | * Handles block-acknowledge notification from device, which reports success | 
|  | 1296 | * of frames sent via aggregation. | 
|  | 1297 | */ | 
|  | 1298 | void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, | 
|  | 1299 | struct iwl_rx_mem_buffer *rxb) | 
|  | 1300 | { | 
|  | 1301 | struct iwl_rx_packet *pkt = rxb_addr(rxb); | 
|  | 1302 | struct iwl_compressed_ba_resp *ba_resp = &pkt->u.compressed_ba; | 
|  | 1303 | struct iwl_tx_queue *txq = NULL; | 
|  | 1304 | struct iwl_ht_agg *agg; | 
|  | 1305 | int index; | 
|  | 1306 | int sta_id; | 
|  | 1307 | int tid; | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1308 | unsigned long flags; | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1309 |  | 
|  | 1310 | /* "flow" corresponds to Tx queue */ | 
|  | 1311 | u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); | 
|  | 1312 |  | 
|  | 1313 | /* "ssn" is start of block-ack Tx window, corresponds to index | 
|  | 1314 | * (in Tx queue's circular buffer) of first TFD/frame in window */ | 
|  | 1315 | u16 ba_resp_scd_ssn = le16_to_cpu(ba_resp->scd_ssn); | 
|  | 1316 |  | 
|  | 1317 | if (scd_flow >= priv->hw_params.max_txq_num) { | 
|  | 1318 | IWL_ERR(priv, | 
|  | 1319 | "BUG_ON scd_flow is bigger than number of queues\n"); | 
|  | 1320 | return; | 
|  | 1321 | } | 
|  | 1322 |  | 
|  | 1323 | txq = &priv->txq[scd_flow]; | 
|  | 1324 | sta_id = ba_resp->sta_id; | 
|  | 1325 | tid = ba_resp->tid; | 
|  | 1326 | agg = &priv->stations[sta_id].tid[tid].agg; | 
| Shanyu Zhao | b561e82 | 2010-06-01 17:13:58 -0700 | [diff] [blame] | 1327 | if (unlikely(agg->txq_id != scd_flow)) { | 
|  | 1328 | IWL_ERR(priv, "BA scd_flow %d does not match txq_id %d\n", | 
|  | 1329 | scd_flow, agg->txq_id); | 
|  | 1330 | return; | 
|  | 1331 | } | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1332 |  | 
|  | 1333 | /* Find index just before block-ack window */ | 
|  | 1334 | index = iwl_queue_dec_wrap(ba_resp_scd_ssn & 0xff, txq->q.n_bd); | 
|  | 1335 |  | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1336 | spin_lock_irqsave(&priv->sta_lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1337 |  | 
|  | 1338 | IWL_DEBUG_TX_REPLY(priv, "REPLY_COMPRESSED_BA [%d] Received from %pM, " | 
|  | 1339 | "sta_id = %d\n", | 
|  | 1340 | agg->wait_for_ba, | 
|  | 1341 | (u8 *) &ba_resp->sta_addr_lo32, | 
|  | 1342 | ba_resp->sta_id); | 
|  | 1343 | IWL_DEBUG_TX_REPLY(priv, "TID = %d, SeqCtl = %d, bitmap = 0x%llx, scd_flow = " | 
|  | 1344 | "%d, scd_ssn = %d\n", | 
|  | 1345 | ba_resp->tid, | 
|  | 1346 | ba_resp->seq_ctl, | 
|  | 1347 | (unsigned long long)le64_to_cpu(ba_resp->bitmap), | 
|  | 1348 | ba_resp->scd_flow, | 
|  | 1349 | ba_resp->scd_ssn); | 
| Frans Pop | 91dd6c2 | 2010-03-24 14:19:58 -0700 | [diff] [blame] | 1350 | IWL_DEBUG_TX_REPLY(priv, "DAT start_idx = %d, bitmap = 0x%llx\n", | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1351 | agg->start_idx, | 
|  | 1352 | (unsigned long long)agg->bitmap); | 
|  | 1353 |  | 
|  | 1354 | /* Update driver's record of ACK vs. not for each frame in window */ | 
|  | 1355 | iwlagn_tx_status_reply_compressed_ba(priv, agg, ba_resp); | 
|  | 1356 |  | 
|  | 1357 | /* Release all TFDs before the SSN, i.e. all TFDs in front of | 
|  | 1358 | * block-ack window (we assume that they've been successfully | 
|  | 1359 | * transmitted ... if not, it's too late anyway). */ | 
|  | 1360 | if (txq->q.read_ptr != (ba_resp_scd_ssn & 0xff)) { | 
|  | 1361 | /* calculate mac80211 ampdu sw queue to wake */ | 
|  | 1362 | int freed = iwlagn_tx_queue_reclaim(priv, scd_flow, index); | 
|  | 1363 | iwl_free_tfds_in_queue(priv, sta_id, tid, freed); | 
|  | 1364 |  | 
|  | 1365 | if ((iwl_queue_space(&txq->q) > txq->q.low_mark) && | 
|  | 1366 | priv->mac80211_registered && | 
|  | 1367 | (agg->state != IWL_EMPTYING_HW_QUEUE_DELBA)) | 
|  | 1368 | iwl_wake_queue(priv, txq->swq_id); | 
|  | 1369 |  | 
|  | 1370 | iwlagn_txq_check_empty(priv, sta_id, tid, scd_flow); | 
|  | 1371 | } | 
| Reinette Chatre | 9c5ac09 | 2010-05-05 02:26:06 -0700 | [diff] [blame] | 1372 |  | 
|  | 1373 | spin_unlock_irqrestore(&priv->sta_lock, flags); | 
| Wey-Yi Guy | 74bcdb3 | 2010-03-17 13:34:34 -0700 | [diff] [blame] | 1374 | } |