From 39644e9ac5329dc92d9547976c8f30f18da90097 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Thu, 15 Sep 2011 11:46:29 -0700 Subject: [PATCH] iwlagn: unmap cmd queue's tfds as BIDI If the driver is unloaded while there is still a host command in flight, its tfd will be freed by iwl_tx_queue_free. This function is called for both types of queues: Tx queues and cmd queue. This didn't take in count the fact that in Tx queues, tfds are mapped as TO_DEVICE (besides the first TB), whereas in cmd queue, all TBs are mapped as BIDI. Hence, tx_queue_free unmapped the second (and higher) TB of each tfd in the cmd queue as TO_DEVICE, whereas they must be freed as BIDI. This means that if a multi TFD is in flight while we unload the driver (which is quite unlikely but can happen), we will get the warning below. This patch fixes this. [ 445.234060] ------------[ cut here ]------------ [ 445.236273] WARNING: at lib/dma-debug.c:861 check_unmap+0x337/0x780() [ 445.236654] iwlagn 0000:02:00.0: DMA-API: device driver frees DMA memory with different direction [device address=0x0000000126950540] [size=8 bytes] [mapped with DMA_BIDIRECTIONAL] [unmapped with DMA_TO_DEVICE] [ 445.236654] Modules linked in: ... [ 445.236654] Pid: 1415, comm: modprobe Not tainted 3.1.0-rc4-wl-65912-g5215ff1-dirty #79 [ 445.236654] Call Trace: [ 445.236654] [] warn_slowpath_common+0x71/0xa0 [ 445.236654] [] warn_slowpath_fmt+0x47/0x50 [ 445.236654] [] check_unmap+0x337/0x780 [ 445.236654] [] ? free_one_page+0x156/0x320 [ 445.236654] [] debug_dma_unmap_page+0x5a/0x60 [ 445.236654] [] iwlagn_unmap_tfd.isra.11+0x121/0x1c0 [iwlagn] [ 445.236654] [] iwlagn_txq_free_tfd+0x42/0x70 [iwlagn] [ 445.236654] [] iwl_tx_queue_unmap+0x4e/0x70 [iwlagn] [ 445.236654] [] iwl_trans_pcie_tx_free+0x10d/0x440 [iwlagn] [ 445.236654] [] ? destroy_workqueue+0xb9/0x1e0 [ 445.236654] [] iwl_trans_pcie_free+0x2a/0x2c0 [iwlagn] [ 445.236654] [] iwl_remove+0x149/0x17e [iwlagn] [ 445.236654] [] iwl_pci_remove+0x1f/0x65 [iwlagn] [ 445.236654] [] pci_device_remove+0x47/0x120 [ 445.236654] [] __device_release_driver+0x7c/0xe0 [ 445.236654] [] driver_detach+0xc8/0xd0 [ 445.236654] [] bus_remove_driver+0x88/0xe0 [ 445.236654] [] driver_unregister+0x62/0xa0 [ 445.236654] [] pci_unregister_driver+0x44/0xc0 [ 445.236654] [] iwl_pci_unregister_driver+0x15/0x20 [iwlagn] [ 445.236654] [] iwl_exit+0x9/0xa74 [iwlagn] [ 445.236654] [] sys_delete_module+0x184/0x240 [ 445.236654] [] ? retint_swapgs+0xe/0x13 [ 445.236654] [] ? trace_hardirqs_on_thunk+0x3a/0x3f [ 445.236654] [] system_call_fastpath+0x16/0x1b [ 445.236654] ---[ end trace 1fbc362b7dbe5d74 ]--- [ 445.236654] Mapped at: [ 445.236654] [] debug_dma_map_page+0x8b/0x150 [ 445.236654] [] iwl_enqueue_hcmd+0x837/0xa40 [iwlagn] [ 445.236654] [] iwl_trans_pcie_send_cmd+0x8d/0x580 [iwlagn] [ 445.236654] [] iwl_send_calib_results+0x75/0xd0 [iwlagn] [ 445.236654] [] iwlagn_alive_notify+0x196/0x1f0 [iwlagn] [ 445.386500] iwlagn 0000:02:00.0: PCI INT A disabled Reported-by: Johannes Berg Signed-off-by: Emmanuel Grumbach Signed-off-by: Wey-Yi Guy Signed-off-by: John W. Linville --- drivers/net/wireless/iwlwifi/iwl-trans-int-pcie.h | 2 +- drivers/net/wireless/iwlwifi/iwl-trans-tx-pcie.c | 12 ++++++++---- drivers/net/wireless/iwlwifi/iwl-trans.c | 12 +++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-int-pcie.h b/drivers/net/wireless/iwlwifi/iwl-trans-int-pcie.h index 8047e955a27b..fc3375473541 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-int-pcie.h +++ b/drivers/net/wireless/iwlwifi/iwl-trans-int-pcie.h @@ -307,7 +307,7 @@ void iwl_trans_pcie_tx_agg_setup(struct iwl_trans *trans, enum iwl_rxon_context_id ctx, int sta_id, int tid, int frame_limit); void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq, - int index); + int index, enum dma_data_direction dma_dir); int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, struct sk_buff_head *skbs); int iwl_queue_space(const struct iwl_queue *q); diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-tx-pcie.c b/drivers/net/wireless/iwlwifi/iwl-trans-tx-pcie.c index ca686dbf5893..c7ddb2204a6e 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-tx-pcie.c +++ b/drivers/net/wireless/iwlwifi/iwl-trans-tx-pcie.c @@ -207,17 +207,17 @@ static void iwlagn_unmap_tfd(struct iwl_trans *trans, struct iwl_cmd_meta *meta, * @trans - transport private data * @txq - tx queue * @index - the index of the TFD to be freed + *@dma_dir - the direction of the DMA mapping * * 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_trans *trans, struct iwl_tx_queue *txq, - int index) + int index, enum dma_data_direction dma_dir) { struct iwl_tfd *tfd_tmp = txq->tfds; - iwlagn_unmap_tfd(trans, &txq->meta[index], &tfd_tmp[index], - DMA_TO_DEVICE); + iwlagn_unmap_tfd(trans, &txq->meta[index], &tfd_tmp[index], dma_dir); /* free SKB */ if (txq->skbs) { @@ -1119,6 +1119,10 @@ int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, int last_to_free; int freed = 0; + /* This function is not meant to release cmd queue*/ + if (WARN_ON(txq_id == trans->shrd->cmd_queue)) + return 0; + /*Since we free until index _not_ inclusive, the one before index is * the last we will free. This one must be used */ last_to_free = iwl_queue_dec_wrap(index, q->n_bd); @@ -1151,7 +1155,7 @@ int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, iwlagn_txq_inval_byte_cnt_tbl(trans, txq); - iwlagn_txq_free_tfd(trans, txq, txq->q.read_ptr); + iwlagn_txq_free_tfd(trans, txq, txq->q.read_ptr, DMA_TO_DEVICE); freed++; } return freed; diff --git a/drivers/net/wireless/iwlwifi/iwl-trans.c b/drivers/net/wireless/iwlwifi/iwl-trans.c index b2e89077c684..228c861848cf 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans.c +++ b/drivers/net/wireless/iwlwifi/iwl-trans.c @@ -411,13 +411,23 @@ static void iwl_tx_queue_unmap(struct iwl_trans *trans, int txq_id) struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); struct iwl_tx_queue *txq = &trans_pcie->txq[txq_id]; struct iwl_queue *q = &txq->q; + enum dma_data_direction dma_dir; if (!q->n_bd) return; + /* In the command queue, all the TBs are mapped as BIDI + * so unmap them as such. + */ + if (txq_id == trans->shrd->cmd_queue) + dma_dir = DMA_BIDIRECTIONAL; + else + dma_dir = DMA_TO_DEVICE; + while (q->write_ptr != q->read_ptr) { /* The read_ptr needs to bound by q->n_window */ - iwlagn_txq_free_tfd(trans, txq, get_cmd_index(q, q->read_ptr)); + iwlagn_txq_free_tfd(trans, txq, get_cmd_index(q, q->read_ptr), + dma_dir); q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd); } }