summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/iwlwifi/iwl-tx.c
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2011-05-13 11:57:40 -0700
committerWey-Yi Guy <wey-yi.w.guy@intel.com>2011-05-13 12:00:41 -0700
commit4ce7cc2b09553a91d4aea014c39674685715173a (patch)
tree0a2e4b8ae8852a6404c479a7b605ae4b0af6b72d /drivers/net/wireless/iwlwifi/iwl-tx.c
parent4c42db0f04e55d48f0ea9f424144a5211b7a155c (diff)
downloadblackbird-op-linux-4ce7cc2b09553a91d4aea014c39674685715173a.tar.gz
blackbird-op-linux-4ce7cc2b09553a91d4aea014c39674685715173a.zip
iwlagn: support multiple TBs per command
The current "huge" command handling is a bit confusing, and very limited since only one command may be huge at a time. Additionally, we often copy data around quite pointlessly since we could instead map the existing scan buffer for example and use it directly. This patch makes that possible. The first change is that multiple buffers may be given to each command (this change was prepared earlier so callsites don't need to change). Each of those can be mapped attached to a TB in the TFD, and the command header can use a TB (the first one) in the TFD as well. Doing this allows getting rid of huge commands in favour of mapping existing buffers. The beacon transmission is also optimised to not copy the SKB at all but use multiple TBs. Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Diffstat (limited to 'drivers/net/wireless/iwlwifi/iwl-tx.c')
-rw-r--r--drivers/net/wireless/iwlwifi/iwl-tx.c231
1 files changed, 124 insertions, 107 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-tx.c b/drivers/net/wireless/iwlwifi/iwl-tx.c
index a47558f0ee3d..2f6b38cfcc13 100644
--- a/drivers/net/wireless/iwlwifi/iwl-tx.c
+++ b/drivers/net/wireless/iwlwifi/iwl-tx.c
@@ -125,25 +125,13 @@ static inline u8 iwl_tfd_get_num_tbs(struct iwl_tfd *tfd)
return tfd->num_tbs & 0x1f;
}
-/**
- * iwlagn_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr]
- * @priv - driver private data
- * @txq - tx queue
- *
- * Does NOT advance any TFD circular buffer read/write indexes
- * Does NOT free the TFD itself (which is within circular buffer)
- */
-void iwlagn_txq_free_tfd(struct iwl_priv *priv, struct iwl_tx_queue *txq)
+static void iwlagn_unmap_tfd(struct iwl_priv *priv, struct iwl_cmd_meta *meta,
+ struct iwl_tfd *tfd)
{
- struct iwl_tfd *tfd_tmp = (struct iwl_tfd *)txq->tfds;
- struct iwl_tfd *tfd;
struct pci_dev *dev = priv->pci_dev;
- int index = txq->q.read_ptr;
int i;
int num_tbs;
- tfd = &tfd_tmp[index];
-
/* Sanity check on number of chunks */
num_tbs = iwl_tfd_get_num_tbs(tfd);
@@ -156,14 +144,30 @@ void iwlagn_txq_free_tfd(struct iwl_priv *priv, struct iwl_tx_queue *txq)
/* Unmap tx_cmd */
if (num_tbs)
pci_unmap_single(dev,
- dma_unmap_addr(&txq->meta[index], mapping),
- dma_unmap_len(&txq->meta[index], len),
+ dma_unmap_addr(meta, mapping),
+ dma_unmap_len(meta, len),
PCI_DMA_BIDIRECTIONAL);
/* Unmap chunks, if any. */
for (i = 1; i < num_tbs; i++)
pci_unmap_single(dev, iwl_tfd_tb_get_addr(tfd, i),
iwl_tfd_tb_get_len(tfd, i), PCI_DMA_TODEVICE);
+}
+
+/**
+ * iwlagn_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr]
+ * @priv - driver private data
+ * @txq - tx queue
+ *
+ * Does NOT advance any TFD circular buffer read/write indexes
+ * Does NOT free the TFD itself (which is within circular buffer)
+ */
+void iwlagn_txq_free_tfd(struct iwl_priv *priv, struct iwl_tx_queue *txq)
+{
+ struct iwl_tfd *tfd_tmp = txq->tfds;
+ int index = txq->q.read_ptr;
+
+ iwlagn_unmap_tfd(priv, &txq->meta[index], &tfd_tmp[index]);
/* free SKB */
if (txq->txb) {
@@ -189,7 +193,7 @@ int iwlagn_txq_attach_buf_to_tfd(struct iwl_priv *priv,
u32 num_tbs;
q = &txq->q;
- tfd_tmp = (struct iwl_tfd *)txq->tfds;
+ tfd_tmp = txq->tfds;
tfd = &tfd_tmp[q->write_ptr];
if (reset)
@@ -303,7 +307,7 @@ void iwl_cmd_queue_unmap(struct iwl_priv *priv)
return;
while (q->read_ptr != q->write_ptr) {
- i = get_cmd_index(q, q->read_ptr, 0);
+ i = get_cmd_index(q, q->read_ptr);
if (txq->meta[i].flags & CMD_MAPPED) {
pci_unmap_single(priv->pci_dev,
@@ -315,15 +319,6 @@ void iwl_cmd_queue_unmap(struct iwl_priv *priv)
q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd);
}
-
- i = q->n_window;
- if (txq->meta[i].flags & CMD_MAPPED) {
- pci_unmap_single(priv->pci_dev,
- dma_unmap_addr(&txq->meta[i], mapping),
- dma_unmap_len(&txq->meta[i], len),
- PCI_DMA_BIDIRECTIONAL);
- txq->meta[i].flags = 0;
- }
}
/**
@@ -343,7 +338,7 @@ void iwl_cmd_queue_free(struct iwl_priv *priv)
iwl_cmd_queue_unmap(priv);
/* De-alloc array of command/tx buffers */
- for (i = 0; i <= TFD_CMD_SLOTS; i++)
+ for (i = 0; i < TFD_CMD_SLOTS; i++)
kfree(txq->cmd[i]);
/* De-alloc circular buffer of TFDs */
@@ -483,33 +478,17 @@ int iwl_tx_queue_init(struct iwl_priv *priv, struct iwl_tx_queue *txq,
{
int i, len;
int ret;
- int actual_slots = slots_num;
-
- /*
- * Alloc buffer array for commands (Tx or other types of commands).
- * For the command queue (#4/#9), allocate command space + one big
- * command for scan, since scan command is very huge; the system will
- * not have two scans at the same time, so only one is needed.
- * For normal Tx queues (all other queues), no super-size command
- * space is needed.
- */
- if (txq_id == priv->cmd_queue)
- actual_slots++;
- txq->meta = kzalloc(sizeof(struct iwl_cmd_meta) * actual_slots,
+ txq->meta = kzalloc(sizeof(struct iwl_cmd_meta) * slots_num,
GFP_KERNEL);
- txq->cmd = kzalloc(sizeof(struct iwl_device_cmd *) * actual_slots,
+ txq->cmd = kzalloc(sizeof(struct iwl_device_cmd *) * slots_num,
GFP_KERNEL);
if (!txq->meta || !txq->cmd)
goto out_free_arrays;
len = sizeof(struct iwl_device_cmd);
- for (i = 0; i < actual_slots; i++) {
- /* only happens for cmd queue */
- if (i == slots_num)
- len = IWL_MAX_CMD_SIZE;
-
+ for (i = 0; i < slots_num; i++) {
txq->cmd[i] = kmalloc(len, GFP_KERNEL);
if (!txq->cmd[i])
goto err;
@@ -544,7 +523,7 @@ int iwl_tx_queue_init(struct iwl_priv *priv, struct iwl_tx_queue *txq,
return 0;
err:
- for (i = 0; i < actual_slots; i++)
+ for (i = 0; i < slots_num; i++)
kfree(txq->cmd[i]);
out_free_arrays:
kfree(txq->meta);
@@ -592,23 +571,44 @@ int iwl_enqueue_hcmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd)
dma_addr_t phys_addr;
unsigned long flags;
u32 idx;
- u16 fix_size;
+ u16 copy_size, cmd_size;
bool is_ct_kill = false;
+ bool had_nocopy = false;
+ int i;
+ u8 *cmd_dest;
+#ifdef CONFIG_IWLWIFI_DEVICE_TRACING
+ const void *trace_bufs[IWL_MAX_CMD_TFDS + 1] = {};
+ int trace_lens[IWL_MAX_CMD_TFDS + 1] = {};
+ int trace_idx;
+#endif
- fix_size = (u16)(cmd->len[0] + sizeof(out_cmd->hdr));
+ copy_size = sizeof(out_cmd->hdr);
+ cmd_size = sizeof(out_cmd->hdr);
+
+ /* need one for the header if the first is NOCOPY */
+ BUILD_BUG_ON(IWL_MAX_CMD_TFDS > IWL_NUM_OF_TBS - 1);
+
+ for (i = 0; i < IWL_MAX_CMD_TFDS; i++) {
+ if (!cmd->len[i])
+ continue;
+ if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) {
+ had_nocopy = true;
+ } else {
+ /* NOCOPY must not be followed by normal! */
+ if (WARN_ON(had_nocopy))
+ return -EINVAL;
+ copy_size += cmd->len[i];
+ }
+ cmd_size += cmd->len[i];
+ }
/*
* If any of the command structures end up being larger than
- * the TFD_MAX_PAYLOAD_SIZE, and it sent as a 'small' command then
- * we will need to increase the size of the TFD entries
- * Also, check to see if command buffer should not exceed the size
- * of device_cmd and max_cmd_size.
+ * the TFD_MAX_PAYLOAD_SIZE and they aren't dynamically
+ * allocated into separate TFDs, then we will need to
+ * increase the size of the buffers.
*/
- if (WARN_ON((fix_size > TFD_MAX_PAYLOAD_SIZE) &&
- !(cmd->flags & CMD_SIZE_HUGE)))
- return -EINVAL;
-
- if (WARN_ON(fix_size > IWL_MAX_CMD_SIZE))
+ if (WARN_ON(copy_size > TFD_MAX_PAYLOAD_SIZE))
return -EINVAL;
if (iwl_is_rfkill(priv) || iwl_is_ctkill(priv)) {
@@ -617,14 +617,6 @@ int iwl_enqueue_hcmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd)
return -EIO;
}
- /*
- * As we only have a single huge buffer, check that the command
- * is synchronous (otherwise buffers could end up being reused).
- */
-
- if (WARN_ON((cmd->flags & CMD_ASYNC) && (cmd->flags & CMD_SIZE_HUGE)))
- return -EINVAL;
-
spin_lock_irqsave(&priv->hcmd_lock, flags);
if (iwl_queue_space(q) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) {
@@ -639,7 +631,7 @@ int iwl_enqueue_hcmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd)
return -ENOSPC;
}
- idx = get_cmd_index(q, q->write_ptr, cmd->flags & CMD_SIZE_HUGE);
+ idx = get_cmd_index(q, q->write_ptr);
out_cmd = txq->cmd[idx];
out_meta = &txq->meta[idx];
@@ -654,55 +646,84 @@ int iwl_enqueue_hcmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd)
if (cmd->flags & CMD_ASYNC)
out_meta->callback = cmd->callback;
- out_cmd->hdr.cmd = cmd->id;
- memcpy(&out_cmd->cmd.payload, cmd->data[0], cmd->len[0]);
-
- /* At this point, the out_cmd now has all of the incoming cmd
- * information */
+ /* set up the header */
+ out_cmd->hdr.cmd = cmd->id;
out_cmd->hdr.flags = 0;
out_cmd->hdr.sequence = cpu_to_le16(QUEUE_TO_SEQ(priv->cmd_queue) |
- INDEX_TO_SEQ(q->write_ptr));
- if (cmd->flags & CMD_SIZE_HUGE)
- out_cmd->hdr.sequence |= SEQ_HUGE_FRAME;
-
-#ifdef CONFIG_IWLWIFI_DEBUG
- switch (out_cmd->hdr.cmd) {
- case REPLY_TX_LINK_QUALITY_CMD:
- case SENSITIVITY_CMD:
- IWL_DEBUG_HC_DUMP(priv, "Sending command %s (#%x), seq: 0x%04X, "
- "%d bytes at %d[%d]:%d\n",
- get_cmd_string(out_cmd->hdr.cmd),
- out_cmd->hdr.cmd,
- le16_to_cpu(out_cmd->hdr.sequence), fix_size,
- q->write_ptr, idx, priv->cmd_queue);
- break;
- default:
- IWL_DEBUG_HC(priv, "Sending command %s (#%x), seq: 0x%04X, "
- "%d bytes at %d[%d]:%d\n",
- get_cmd_string(out_cmd->hdr.cmd),
- out_cmd->hdr.cmd,
- le16_to_cpu(out_cmd->hdr.sequence), fix_size,
- q->write_ptr, idx, priv->cmd_queue);
+ INDEX_TO_SEQ(q->write_ptr));
+
+ /* and copy the data that needs to be copied */
+
+ cmd_dest = &out_cmd->cmd.payload[0];
+ for (i = 0; i < IWL_MAX_CMD_TFDS; i++) {
+ if (!cmd->len[i])
+ continue;
+ if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY)
+ break;
+ memcpy(cmd_dest, cmd->data[i], cmd->len[i]);
+ cmd_dest += cmd->len[i];
}
-#endif
+
+ IWL_DEBUG_HC(priv, "Sending command %s (#%x), seq: 0x%04X, "
+ "%d bytes at %d[%d]:%d\n",
+ get_cmd_string(out_cmd->hdr.cmd),
+ out_cmd->hdr.cmd,
+ le16_to_cpu(out_cmd->hdr.sequence), cmd_size,
+ q->write_ptr, idx, priv->cmd_queue);
+
phys_addr = pci_map_single(priv->pci_dev, &out_cmd->hdr,
- fix_size, PCI_DMA_BIDIRECTIONAL);
+ copy_size, PCI_DMA_BIDIRECTIONAL);
if (unlikely(pci_dma_mapping_error(priv->pci_dev, phys_addr))) {
idx = -ENOMEM;
goto out;
}
dma_unmap_addr_set(out_meta, mapping, phys_addr);
- dma_unmap_len_set(out_meta, len, fix_size);
+ dma_unmap_len_set(out_meta, len, copy_size);
+
+ iwlagn_txq_attach_buf_to_tfd(priv, txq, phys_addr, copy_size, 1);
+#ifdef CONFIG_IWLWIFI_DEVICE_TRACING
+ trace_bufs[0] = &out_cmd->hdr;
+ trace_lens[0] = copy_size;
+ trace_idx = 1;
+#endif
+
+ for (i = 0; i < IWL_MAX_CMD_TFDS; i++) {
+ if (!cmd->len[i])
+ continue;
+ if (!(cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY))
+ continue;
+ phys_addr = pci_map_single(priv->pci_dev, (void *)cmd->data[i],
+ cmd->len[i], PCI_DMA_TODEVICE);
+ if (pci_dma_mapping_error(priv->pci_dev, phys_addr)) {
+ iwlagn_unmap_tfd(priv, out_meta,
+ &txq->tfds[q->write_ptr]);
+ idx = -ENOMEM;
+ goto out;
+ }
+
+ iwlagn_txq_attach_buf_to_tfd(priv, txq, phys_addr,
+ cmd->len[i], 0);
+#ifdef CONFIG_IWLWIFI_DEVICE_TRACING
+ trace_bufs[trace_idx] = cmd->data[i];
+ trace_lens[trace_idx] = cmd->len[i];
+ trace_idx++;
+#endif
+ }
out_meta->flags = cmd->flags | CMD_MAPPED;
txq->need_update = 1;
- trace_iwlwifi_dev_hcmd(priv, &out_cmd->hdr, fix_size, cmd->flags);
-
- iwlagn_txq_attach_buf_to_tfd(priv, txq, phys_addr, fix_size, 1);
+ /* check that tracing gets all possible blocks */
+ BUILD_BUG_ON(IWL_MAX_CMD_TFDS + 1 != 3);
+#ifdef CONFIG_IWLWIFI_DEVICE_TRACING
+ trace_iwlwifi_dev_hcmd(priv, cmd->flags,
+ trace_bufs[0], trace_lens[0],
+ trace_bufs[1], trace_lens[1],
+ trace_bufs[2], trace_lens[2]);
+#endif
/* Increment and update queue's write index */
q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd);
@@ -761,7 +782,6 @@ void iwl_tx_cmd_complete(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb)
int txq_id = SEQ_TO_QUEUE(sequence);
int index = SEQ_TO_INDEX(sequence);
int cmd_index;
- bool huge = !!(pkt->hdr.sequence & SEQ_HUGE_FRAME);
struct iwl_device_cmd *cmd;
struct iwl_cmd_meta *meta;
struct iwl_tx_queue *txq = &priv->txq[priv->cmd_queue];
@@ -779,14 +799,11 @@ void iwl_tx_cmd_complete(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb)
return;
}
- cmd_index = get_cmd_index(&txq->q, index, huge);
+ cmd_index = get_cmd_index(&txq->q, index);
cmd = txq->cmd[cmd_index];
meta = &txq->meta[cmd_index];
- pci_unmap_single(priv->pci_dev,
- dma_unmap_addr(meta, mapping),
- dma_unmap_len(meta, len),
- PCI_DMA_BIDIRECTIONAL);
+ iwlagn_unmap_tfd(priv, meta, &txq->tfds[index]);
/* Input error checking is done when commands are added to queue. */
if (meta->flags & CMD_WANT_SKB) {
OpenPOWER on IntegriCloud