diff options
author | Johannes Berg <johannes.berg@intel.com> | 2012-10-19 08:24:43 -0400 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2012-10-29 06:27:05 -0400 |
commit | f4feb8ac6e666d2ca37cf722166bbfadf2c6adf8 (patch) | |
tree | 950d1e7ab7ef3098f41b095f4e59d65520c6aeea /drivers/net/wireless/iwlwifi | |
parent | 86052a77067df53ad48800e74984f84806baddbd (diff) |
iwlwifi: support host command with copied data
In addition to the NOCOPY flag, add a DUP flag that
tells the transport to kmemdup() the buffer and free
it after the command completes.
Currently this is only supported for a single buffer
in a given command, but that could be extended if it
should be needed.
Reviewed-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'drivers/net/wireless/iwlwifi')
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-trans.h | 6 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/pcie/internal.h | 2 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/pcie/rx.c | 3 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/pcie/trans.c | 1 | ||||
-rw-r--r-- | drivers/net/wireless/iwlwifi/pcie/tx.c | 57 |
5 files changed, 59 insertions, 10 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-trans.h b/drivers/net/wireless/iwlwifi/iwl-trans.h index f75ea6d73ffc..76c52378f8f7 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans.h +++ b/drivers/net/wireless/iwlwifi/iwl-trans.h | |||
@@ -221,14 +221,18 @@ struct iwl_device_cmd { | |||
221 | /** | 221 | /** |
222 | * struct iwl_hcmd_dataflag - flag for each one of the chunks of the command | 222 | * struct iwl_hcmd_dataflag - flag for each one of the chunks of the command |
223 | * | 223 | * |
224 | * IWL_HCMD_DFL_NOCOPY: By default, the command is copied to the host command's | 224 | * @IWL_HCMD_DFL_NOCOPY: By default, the command is copied to the host command's |
225 | * ring. The transport layer doesn't map the command's buffer to DMA, but | 225 | * ring. The transport layer doesn't map the command's buffer to DMA, but |
226 | * rather copies it to an previously allocated DMA buffer. This flag tells | 226 | * rather copies it to an previously allocated DMA buffer. This flag tells |
227 | * the transport layer not to copy the command, but to map the existing | 227 | * the transport layer not to copy the command, but to map the existing |
228 | * buffer. This can save memcpy and is worth with very big comamnds. | 228 | * buffer. This can save memcpy and is worth with very big comamnds. |
229 | * @IWL_HCMD_DFL_DUP: Only valid without NOCOPY, duplicate the memory for this | ||
230 | * chunk internally and free it again after the command completes. This | ||
231 | * can (currently) be used only once per command. | ||
229 | */ | 232 | */ |
230 | enum iwl_hcmd_dataflag { | 233 | enum iwl_hcmd_dataflag { |
231 | IWL_HCMD_DFL_NOCOPY = BIT(0), | 234 | IWL_HCMD_DFL_NOCOPY = BIT(0), |
235 | IWL_HCMD_DFL_DUP = BIT(1), | ||
232 | }; | 236 | }; |
233 | 237 | ||
234 | /** | 238 | /** |
diff --git a/drivers/net/wireless/iwlwifi/pcie/internal.h b/drivers/net/wireless/iwlwifi/pcie/internal.h index 6ce58f03bc53..ae0f87e0e585 100644 --- a/drivers/net/wireless/iwlwifi/pcie/internal.h +++ b/drivers/net/wireless/iwlwifi/pcie/internal.h | |||
@@ -186,6 +186,8 @@ struct iwl_pcie_tx_queue_entry { | |||
186 | struct iwl_device_cmd *cmd; | 186 | struct iwl_device_cmd *cmd; |
187 | struct iwl_device_cmd *copy_cmd; | 187 | struct iwl_device_cmd *copy_cmd; |
188 | struct sk_buff *skb; | 188 | struct sk_buff *skb; |
189 | /* buffer to free after command completes */ | ||
190 | const void *free_buf; | ||
189 | struct iwl_cmd_meta meta; | 191 | struct iwl_cmd_meta meta; |
190 | }; | 192 | }; |
191 | 193 | ||
diff --git a/drivers/net/wireless/iwlwifi/pcie/rx.c b/drivers/net/wireless/iwlwifi/pcie/rx.c index 137af4c46a6c..3f03f6e322c3 100644 --- a/drivers/net/wireless/iwlwifi/pcie/rx.c +++ b/drivers/net/wireless/iwlwifi/pcie/rx.c | |||
@@ -452,6 +452,9 @@ static void iwl_rx_handle_rxbuf(struct iwl_trans *trans, | |||
452 | /* The original command isn't needed any more */ | 452 | /* The original command isn't needed any more */ |
453 | kfree(txq->entries[cmd_index].copy_cmd); | 453 | kfree(txq->entries[cmd_index].copy_cmd); |
454 | txq->entries[cmd_index].copy_cmd = NULL; | 454 | txq->entries[cmd_index].copy_cmd = NULL; |
455 | /* nor is the duplicated part of the command */ | ||
456 | kfree(txq->entries[cmd_index].free_buf); | ||
457 | txq->entries[cmd_index].free_buf = NULL; | ||
455 | } | 458 | } |
456 | 459 | ||
457 | /* | 460 | /* |
diff --git a/drivers/net/wireless/iwlwifi/pcie/trans.c b/drivers/net/wireless/iwlwifi/pcie/trans.c index a6a518116c7f..b8a155af12cc 100644 --- a/drivers/net/wireless/iwlwifi/pcie/trans.c +++ b/drivers/net/wireless/iwlwifi/pcie/trans.c | |||
@@ -496,6 +496,7 @@ static void iwl_tx_queue_free(struct iwl_trans *trans, int txq_id) | |||
496 | for (i = 0; i < txq->q.n_window; i++) { | 496 | for (i = 0; i < txq->q.n_window; i++) { |
497 | kfree(txq->entries[i].cmd); | 497 | kfree(txq->entries[i].cmd); |
498 | kfree(txq->entries[i].copy_cmd); | 498 | kfree(txq->entries[i].copy_cmd); |
499 | kfree(txq->entries[i].free_buf); | ||
499 | } | 500 | } |
500 | 501 | ||
501 | /* De-alloc circular buffer of TFDs */ | 502 | /* De-alloc circular buffer of TFDs */ |
diff --git a/drivers/net/wireless/iwlwifi/pcie/tx.c b/drivers/net/wireless/iwlwifi/pcie/tx.c index 5db03145186c..9cb30ae5e9a1 100644 --- a/drivers/net/wireless/iwlwifi/pcie/tx.c +++ b/drivers/net/wireless/iwlwifi/pcie/tx.c | |||
@@ -517,8 +517,9 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
517 | struct iwl_queue *q = &txq->q; | 517 | struct iwl_queue *q = &txq->q; |
518 | struct iwl_device_cmd *out_cmd; | 518 | struct iwl_device_cmd *out_cmd; |
519 | struct iwl_cmd_meta *out_meta; | 519 | struct iwl_cmd_meta *out_meta; |
520 | void *dup_buf = NULL; | ||
520 | dma_addr_t phys_addr; | 521 | dma_addr_t phys_addr; |
521 | u32 idx; | 522 | int idx; |
522 | u16 copy_size, cmd_size; | 523 | u16 copy_size, cmd_size; |
523 | bool had_nocopy = false; | 524 | bool had_nocopy = false; |
524 | int i; | 525 | int i; |
@@ -535,10 +536,33 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
535 | continue; | 536 | continue; |
536 | if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { | 537 | if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { |
537 | had_nocopy = true; | 538 | had_nocopy = true; |
539 | if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { | ||
540 | idx = -EINVAL; | ||
541 | goto free_dup_buf; | ||
542 | } | ||
543 | } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { | ||
544 | /* | ||
545 | * This is also a chunk that isn't copied | ||
546 | * to the static buffer so set had_nocopy. | ||
547 | */ | ||
548 | had_nocopy = true; | ||
549 | |||
550 | /* only allowed once */ | ||
551 | if (WARN_ON(dup_buf)) { | ||
552 | idx = -EINVAL; | ||
553 | goto free_dup_buf; | ||
554 | } | ||
555 | |||
556 | dup_buf = kmemdup(cmd->data[i], cmd->len[i], | ||
557 | GFP_ATOMIC); | ||
558 | if (!dup_buf) | ||
559 | return -ENOMEM; | ||
538 | } else { | 560 | } else { |
539 | /* NOCOPY must not be followed by normal! */ | 561 | /* NOCOPY must not be followed by normal! */ |
540 | if (WARN_ON(had_nocopy)) | 562 | if (WARN_ON(had_nocopy)) { |
541 | return -EINVAL; | 563 | idx = -EINVAL; |
564 | goto free_dup_buf; | ||
565 | } | ||
542 | copy_size += cmd->len[i]; | 566 | copy_size += cmd->len[i]; |
543 | } | 567 | } |
544 | cmd_size += cmd->len[i]; | 568 | cmd_size += cmd->len[i]; |
@@ -553,8 +577,10 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
553 | if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, | 577 | if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, |
554 | "Command %s (%#x) is too large (%d bytes)\n", | 578 | "Command %s (%#x) is too large (%d bytes)\n", |
555 | trans_pcie_get_cmd_string(trans_pcie, cmd->id), | 579 | trans_pcie_get_cmd_string(trans_pcie, cmd->id), |
556 | cmd->id, copy_size)) | 580 | cmd->id, copy_size)) { |
557 | return -EINVAL; | 581 | idx = -EINVAL; |
582 | goto free_dup_buf; | ||
583 | } | ||
558 | 584 | ||
559 | spin_lock_bh(&txq->lock); | 585 | spin_lock_bh(&txq->lock); |
560 | 586 | ||
@@ -563,7 +589,8 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
563 | 589 | ||
564 | IWL_ERR(trans, "No space in command queue\n"); | 590 | IWL_ERR(trans, "No space in command queue\n"); |
565 | iwl_op_mode_cmd_queue_full(trans->op_mode); | 591 | iwl_op_mode_cmd_queue_full(trans->op_mode); |
566 | return -ENOSPC; | 592 | idx = -ENOSPC; |
593 | goto free_dup_buf; | ||
567 | } | 594 | } |
568 | 595 | ||
569 | idx = get_cmd_index(q, q->write_ptr); | 596 | idx = get_cmd_index(q, q->write_ptr); |
@@ -587,7 +614,8 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
587 | for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { | 614 | for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { |
588 | if (!cmd->len[i]) | 615 | if (!cmd->len[i]) |
589 | continue; | 616 | continue; |
590 | if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) | 617 | if (cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | |
618 | IWL_HCMD_DFL_DUP)) | ||
591 | break; | 619 | break; |
592 | memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], cmd->len[i]); | 620 | memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], cmd->len[i]); |
593 | cmd_pos += cmd->len[i]; | 621 | cmd_pos += cmd->len[i]; |
@@ -629,11 +657,16 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
629 | iwlagn_txq_attach_buf_to_tfd(trans, txq, phys_addr, copy_size, 1); | 657 | iwlagn_txq_attach_buf_to_tfd(trans, txq, phys_addr, copy_size, 1); |
630 | 658 | ||
631 | for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { | 659 | for (i = 0; i < IWL_MAX_CMD_TFDS; i++) { |
660 | const void *data = cmd->data[i]; | ||
661 | |||
632 | if (!cmd->len[i]) | 662 | if (!cmd->len[i]) |
633 | continue; | 663 | continue; |
634 | if (!(cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY)) | 664 | if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | |
665 | IWL_HCMD_DFL_DUP))) | ||
635 | continue; | 666 | continue; |
636 | phys_addr = dma_map_single(trans->dev, (void *)cmd->data[i], | 667 | if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) |
668 | data = dup_buf; | ||
669 | phys_addr = dma_map_single(trans->dev, (void *)data, | ||
637 | cmd->len[i], DMA_BIDIRECTIONAL); | 670 | cmd->len[i], DMA_BIDIRECTIONAL); |
638 | if (dma_mapping_error(trans->dev, phys_addr)) { | 671 | if (dma_mapping_error(trans->dev, phys_addr)) { |
639 | iwl_unmap_tfd(trans, out_meta, | 672 | iwl_unmap_tfd(trans, out_meta, |
@@ -648,6 +681,9 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
648 | } | 681 | } |
649 | 682 | ||
650 | out_meta->flags = cmd->flags; | 683 | out_meta->flags = cmd->flags; |
684 | if (WARN_ON_ONCE(txq->entries[idx].free_buf)) | ||
685 | kfree(txq->entries[idx].free_buf); | ||
686 | txq->entries[idx].free_buf = dup_buf; | ||
651 | 687 | ||
652 | txq->need_update = 1; | 688 | txq->need_update = 1; |
653 | 689 | ||
@@ -664,6 +700,9 @@ static int iwl_enqueue_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) | |||
664 | 700 | ||
665 | out: | 701 | out: |
666 | spin_unlock_bh(&txq->lock); | 702 | spin_unlock_bh(&txq->lock); |
703 | free_dup_buf: | ||
704 | if (idx < 0) | ||
705 | kfree(dup_buf); | ||
667 | return idx; | 706 | return idx; |
668 | } | 707 | } |
669 | 708 | ||