Commit a41adefc authored by Johannes Berg's avatar Johannes Berg Committed by Greg Kroah-Hartman

iwlwifi: always copy first 16 bytes of commands

commit 8a964f44 upstream.

The FH hardware will always write back to the scratch field
in commands, even host commands not just TX commands, which
can overwrite parts of the command. This is problematic if
the command is re-used (with IWL_HCMD_DFL_NOCOPY) and can
cause calibration issues.

Address this problem by always putting at least the first
16 bytes into the buffer we also use for the command header
and therefore make the DMA engine write back into this.

For commands that are smaller than 16 bytes also always map
enough memory for the DMA engine to write back to.
Reviewed-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
[bwh: Backported to 3.2:
 - Adjust context
 - Drop the IWL_HCMD_DFL_DUP handling
 - Fix descriptor addresses and lengths for tracepoint, but otherwise
   leave it unchanged]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
[wujg: Backported to 3.4: adjust context]
Signed-off-by: default avatarJianguo Wu <wujianguo@huawei.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f2da4ceb
...@@ -209,6 +209,15 @@ struct iwl_queue { ...@@ -209,6 +209,15 @@ struct iwl_queue {
#define TFD_TX_CMD_SLOTS 256 #define TFD_TX_CMD_SLOTS 256
#define TFD_CMD_SLOTS 32 #define TFD_CMD_SLOTS 32
/*
* The FH will write back to the first TB only, so we need
* to copy some data into the buffer regardless of whether
* it should be mapped or not. This indicates how much to
* copy, even for HCMDs it must be big enough to fit the
* DRAM scratch from the TX cmd, at least 16 bytes.
*/
#define IWL_HCMD_MIN_COPY_SIZE 16
struct iwl_tx_queue { struct iwl_tx_queue {
struct iwl_queue q; struct iwl_queue q;
struct iwl_tfd *tfds; struct iwl_tfd *tfds;
......
...@@ -677,10 +677,12 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -677,10 +677,12 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
struct iwl_cmd_meta *out_meta; struct iwl_cmd_meta *out_meta;
dma_addr_t phys_addr; dma_addr_t phys_addr;
u32 idx; u32 idx;
u16 copy_size, cmd_size; u16 copy_size, cmd_size, dma_size;
bool had_nocopy = false; bool had_nocopy = false;
int i; int i;
u8 *cmd_dest; u8 *cmd_dest;
const u8 *cmddata[IWL_MAX_CMD_TFDS];
u16 cmdlen[IWL_MAX_CMD_TFDS];
#ifdef CONFIG_IWLWIFI_DEVICE_TRACING #ifdef CONFIG_IWLWIFI_DEVICE_TRACING
const void *trace_bufs[IWL_MAX_CMD_TFDS + 1] = {}; const void *trace_bufs[IWL_MAX_CMD_TFDS + 1] = {};
int trace_lens[IWL_MAX_CMD_TFDS + 1] = {}; int trace_lens[IWL_MAX_CMD_TFDS + 1] = {};
...@@ -699,15 +701,30 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -699,15 +701,30 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
BUILD_BUG_ON(IWL_MAX_CMD_TFDS > IWL_NUM_OF_TBS - 1); BUILD_BUG_ON(IWL_MAX_CMD_TFDS > IWL_NUM_OF_TBS - 1);
for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { for (i = 0; i < IWL_MAX_CMD_TFDS; i++) {
cmddata[i] = cmd->data[i];
cmdlen[i] = cmd->len[i];
if (!cmd->len[i]) if (!cmd->len[i])
continue; continue;
/* need at least IWL_HCMD_MIN_COPY_SIZE copied */
if (copy_size < IWL_HCMD_MIN_COPY_SIZE) {
int copy = IWL_HCMD_MIN_COPY_SIZE - copy_size;
if (copy > cmdlen[i])
copy = cmdlen[i];
cmdlen[i] -= copy;
cmddata[i] += copy;
copy_size += copy;
}
if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) {
had_nocopy = true; had_nocopy = true;
} else { } else {
/* NOCOPY must not be followed by normal! */ /* NOCOPY must not be followed by normal! */
if (WARN_ON(had_nocopy)) if (WARN_ON(had_nocopy))
return -EINVAL; return -EINVAL;
copy_size += cmd->len[i]; copy_size += cmdlen[i];
} }
cmd_size += cmd->len[i]; cmd_size += cmd->len[i];
} }
...@@ -750,13 +767,30 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -750,13 +767,30 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
/* and copy the data that needs to be copied */ /* and copy the data that needs to be copied */
cmd_dest = out_cmd->payload; cmd_dest = out_cmd->payload;
copy_size = sizeof(out_cmd->hdr);
for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { for (i = 0; i < IWL_MAX_CMD_TFDS; i++) {
if (!cmd->len[i]) int copy = 0;
if (!cmd->len)
continue; continue;
if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY)
break; /* need at least IWL_HCMD_MIN_COPY_SIZE copied */
memcpy(cmd_dest, cmd->data[i], cmd->len[i]); if (copy_size < IWL_HCMD_MIN_COPY_SIZE) {
cmd_dest += cmd->len[i]; copy = IWL_HCMD_MIN_COPY_SIZE - copy_size;
if (copy > cmd->len[i])
copy = cmd->len[i];
}
/* copy everything if not nocopy/dup */
if (!(cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY))
copy = cmd->len[i];
if (copy) {
memcpy(cmd_dest, cmd->data[i], copy);
cmd_dest += copy;
copy_size += copy;
}
} }
IWL_DEBUG_HC(trans, "Sending command %s (#%x), seq: 0x%04X, " IWL_DEBUG_HC(trans, "Sending command %s (#%x), seq: 0x%04X, "
...@@ -766,7 +800,14 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -766,7 +800,14 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
le16_to_cpu(out_cmd->hdr.sequence), cmd_size, le16_to_cpu(out_cmd->hdr.sequence), cmd_size,
q->write_ptr, idx, trans_pcie->cmd_queue); q->write_ptr, idx, trans_pcie->cmd_queue);
phys_addr = dma_map_single(trans->dev, &out_cmd->hdr, copy_size, /*
* If the entire command is smaller than IWL_HCMD_MIN_COPY_SIZE, we must
* still map at least that many bytes for the hardware to write back to.
* We have enough space, so that's not a problem.
*/
dma_size = max_t(u16, copy_size, IWL_HCMD_MIN_COPY_SIZE);
phys_addr = dma_map_single(trans->dev, &out_cmd->hdr, dma_size,
DMA_BIDIRECTIONAL); DMA_BIDIRECTIONAL);
if (unlikely(dma_mapping_error(trans->dev, phys_addr))) { if (unlikely(dma_mapping_error(trans->dev, phys_addr))) {
idx = -ENOMEM; idx = -ENOMEM;
...@@ -774,7 +815,7 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -774,7 +815,7 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
} }
dma_unmap_addr_set(out_meta, mapping, phys_addr); dma_unmap_addr_set(out_meta, mapping, phys_addr);
dma_unmap_len_set(out_meta, len, copy_size); dma_unmap_len_set(out_meta, len, dma_size);
iwlagn_txq_attach_buf_to_tfd(trans, txq, iwlagn_txq_attach_buf_to_tfd(trans, txq,
phys_addr, copy_size, 1); phys_addr, copy_size, 1);
...@@ -801,10 +842,10 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) ...@@ -801,10 +842,10 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd)
} }
iwlagn_txq_attach_buf_to_tfd(trans, txq, phys_addr, iwlagn_txq_attach_buf_to_tfd(trans, txq, phys_addr,
cmd->len[i], 0); cmdlen[i], 0);
#ifdef CONFIG_IWLWIFI_DEVICE_TRACING #ifdef CONFIG_IWLWIFI_DEVICE_TRACING
trace_bufs[trace_idx] = cmd->data[i]; trace_bufs[trace_idx] = cmddata[i];
trace_lens[trace_idx] = cmd->len[i]; trace_lens[trace_idx] = cmdlen[i];
trace_idx++; trace_idx++;
#endif #endif
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment