[iwlwifi][uprev] port pcie/tx-gen2.c

Use Fuchisa iobuf for packet buffer DMA.

Bug: b/299517585
Tested: NUC 7 is still working.

Change-Id: Id384e4a71ca8294ea35bd32ce2e8a4ed66bb4bbd
Reviewed-on: https://fuchsia-review.googlesource.com/c/drivers/wlan/intel/iwlwifi/+/915717
Reviewed-by: Zhiyi Chen <zhiyichen@google.com>
Commit-Queue: Louis Lo <yjlou@google.com>
diff --git a/third_party/iwlwifi/pcie/tx-gen2.c b/third_party/iwlwifi/pcie/tx-gen2.c
index bb79005..2e4192f 100644
--- a/third_party/iwlwifi/pcie/tx-gen2.c
+++ b/third_party/iwlwifi/pcie/tx-gen2.c
@@ -36,13 +36,8 @@
 #include "third_party/iwlwifi/iwl-debug.h"
 #include "third_party/iwlwifi/iwl-io.h"
 #include "third_party/iwlwifi/pcie/internal.h"
+#include "third_party/iwlwifi/queue/tx.h"
 
-#if 1   // NEED_PORTING
-int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
-  // TODO(fxbug.dev/95472): Will continue on fxr/815268.
-  return ZX_ERR_NOT_SUPPORTED;
-}
-#else
 /*************** HOST COMMAND QUEUE FUNCTIONS   *****/
 
 /*
@@ -50,11 +45,11 @@
  * @priv: device private data point
  * @cmd: a pointer to the ucode command structure
  *
- * The function returns < 0 values to indicate the operation
- * failed. On success, it returns the index (>= 0) of command in the
- * command queue.
+ * The function returns ZX_ERR_* values to indicate the operation failed. On success, opposite to
+ * the original driver, the index (>= 0) of command in the command queue is NOT returned (since the
+ * caller doesn't use it anyway).
  */
-int iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans,
+zx_status_t iwl_pcie_gen2_enqueue_hcmd(struct iwl_trans *trans,
 			       struct iwl_host_cmd *cmd)
 {
 	struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
@@ -70,7 +65,8 @@
 	const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD];
 	u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD];
 	struct iwl_tfh_tfd *tfd;
-	unsigned long flags;
+	__UNUSED unsigned long flags;
+	zx_status_t ret = ZX_OK;
 
 	copy_size = sizeof(struct iwl_cmd_header_wide);
 	cmd_size = sizeof(struct iwl_cmd_header_wide);
@@ -95,8 +91,9 @@
 
 		if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) {
 			had_nocopy = true;
-			if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) {
-				idx = -EINVAL;
+			if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) {
+				IWL_WARN(trans, "hcmd contains DFL_DUP but DFL_NOCOPY is asserted too.\n");
+				ret = ZX_ERR_INVALID_ARGS;
 				goto free_dup_buf;
 			}
 		} else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) {
@@ -107,19 +104,21 @@
 			had_nocopy = true;
 
 			/* only allowed once */
-			if (WARN_ON(dup_buf)) {
-				idx = -EINVAL;
+			if (dup_buf) {
+				IWL_WARN(trans, "Only allowed once: dup_buf=%p\n", dup_buf);
+				ret = ZX_ERR_INVALID_ARGS;
 				goto free_dup_buf;
 			}
 
-			dup_buf = kmemdup(cmddata[i], cmdlen[i],
-					  GFP_ATOMIC);
+			dup_buf = calloc(1, cmdlen[i]);
 			if (!dup_buf)
-				return -ENOMEM;
+				return ZX_ERR_NO_MEMORY;
+			memcpy(dup_buf, cmddata[i], cmdlen[i]);
 		} else {
 			/* NOCOPY must not be followed by normal! */
-			if (WARN_ON(had_nocopy)) {
-				idx = -EINVAL;
+			if (had_nocopy) {
+				IWL_WARN(trans, "NOCOPY must not be followed by normal!\n");
+				ret = ZX_ERR_INVALID_ARGS;
 				goto free_dup_buf;
 			}
 			copy_size += cmdlen[i];
@@ -132,10 +131,10 @@
 	 * 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(copy_size > TFD_MAX_PAYLOAD_SIZE,
-		 "Command %s (%#x) is too large (%d bytes)\n",
-		 iwl_get_cmd_string(trans, cmd->id), cmd->id, copy_size)) {
-		idx = -EINVAL;
+	if (copy_size > TFD_MAX_PAYLOAD_SIZE) {
+		IWL_WARN(trans, "Command %s (%#x) is too large (%d bytes)\n",
+		         iwl_get_cmd_string(trans, cmd->id), cmd->id, copy_size);
+		ret = ZX_ERR_INVALID_ARGS;
 		goto free_dup_buf;
 	}
 
@@ -150,11 +149,11 @@
 
 		IWL_ERR(trans, "No space in command queue\n");
 		iwl_op_mode_cmd_queue_full(trans->op_mode);
-		idx = -ENOSPC;
+		ret = ZX_ERR_NO_SPACE;
 		goto free_dup_buf;
 	}
 
-	out_cmd = txq->entries[idx].cmd;
+	out_cmd = iwl_iobuf_virtual(txq->entries[idx].cmd);
 	out_meta = &txq->entries[idx].meta;
 
 	/* re-initialize to NULL */
@@ -221,24 +220,17 @@
 		     cmd_size, txq->write_ptr, idx, trans->txqs.cmd.q_id);
 
 	/* start the TFD with the minimum copy bytes */
+	struct iwl_pcie_first_tb_buf* tb_bufs = iwl_iobuf_virtual(txq->first_tb_bufs);
 	tb0_size = min_t(int, copy_size, IWL_FIRST_TB_SIZE);
-	memcpy(&txq->first_tb_bufs[idx], out_cmd, tb0_size);
-	iwl_txq_gen2_set_tb(trans, tfd, iwl_txq_get_first_tb_dma(txq, idx),
-			    tb0_size);
+	memcpy(&tb_bufs[idx], out_cmd, tb0_size);
+	dma_addr_t first_tb_dma = iwl_txq_get_first_tb_dma(txq, idx);
+	iwl_txq_gen2_set_tb(trans, tfd, first_tb_dma, tb0_size, NULL);
 
 	/* map first command fragment, if any remains */
 	if (copy_size > tb0_size) {
-		phys_addr = dma_map_single(trans->dev,
-					   (u8 *)out_cmd + tb0_size,
-					   copy_size - tb0_size,
-					   DMA_TO_DEVICE);
-		if (dma_mapping_error(trans->dev, phys_addr)) {
-			idx = -ENOMEM;
-			iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd);
-			goto out;
-		}
+		phys_addr = first_tb_dma + tb0_size;
 		iwl_txq_gen2_set_tb(trans, tfd, phys_addr,
-				    copy_size - tb0_size);
+				    copy_size - tb0_size, NULL);
 	}
 
 	/* map the remaining (adjusted) nocopy/dup fragments */
@@ -252,27 +244,36 @@
 			continue;
 		if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP)
 			data = dup_buf;
-		phys_addr = dma_map_single(trans->dev, data,
-					   cmdlen[i], DMA_TO_DEVICE);
-		if (dma_mapping_error(trans->dev, phys_addr)) {
-			idx = -ENOMEM;
-			iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd);
-			goto out;
-		}
-		iwl_txq_gen2_set_tb(trans, tfd, phys_addr, cmdlen[i]);
+
+		// Leverage the 'dup_io_buf' for the DMA address used for 'iwl_txq_gen2_set_tb()'.
+		// Check more comments in 'iwl_pcie_enqueue_hcmd()' in pcie/tx.c.
+		struct iwl_iobuf* dup_io_buf = txq->entries[idx].dup_io_buf;
+		ZX_ASSERT(dup_io_buf == NULL);
+		uint16_t dup_len = cmdlen[i];
+		iwl_iobuf_allocate_contiguous(&trans_pcie->pci_dev->dev, dup_len, &dup_io_buf);
+		void* virt_addr = iwl_iobuf_virtual(dup_io_buf);
+		memcpy(virt_addr, data, dup_len);
+		phys_addr = iwl_iobuf_physical(dup_io_buf);
+		iwl_txq_gen2_set_tb(trans, tfd, phys_addr, dup_len, NULL);
+		iwl_iobuf_cache_flush(dup_io_buf, 0, dup_len);
+		txq->entries[idx].dup_io_buf = dup_io_buf;
 	}
 
 	BUILD_BUG_ON(IWL_TFH_NUM_TBS > sizeof(out_meta->tbs) * BITS_PER_BYTE);
 	out_meta->flags = cmd->flags;
+#if 0  // NEEDS_PORTING
 	if (WARN_ON_ONCE(txq->entries[idx].free_buf))
 		kfree_sensitive(txq->entries[idx].free_buf);
 	txq->entries[idx].free_buf = dup_buf;
 
 	trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr_wide);
+#endif  // NEEDS_PORTING
 
 	/* start timer if queue currently empty */
-	if (txq->read_ptr == txq->write_ptr && txq->wd_timeout)
-		mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout);
+	if (txq->read_ptr == txq->write_ptr && txq->wd_timeout) {
+		iwl_irq_timer_stop(txq->stuck_timer);
+		iwl_irq_timer_start(txq->stuck_timer, txq->wd_timeout);
+	}
 
 	spin_lock(&trans_pcie->reg_lock);
 	/* Increment and update queue's write index */
@@ -280,11 +281,8 @@
 	iwl_txq_inc_wr_ptr(trans, txq);
 	spin_unlock(&trans_pcie->reg_lock);
 
-out:
 	spin_unlock_irqrestore(&txq->lock, flags);
 free_dup_buf:
-	if (idx < 0)
-		kfree(dup_buf);
-	return idx;
+	free(dup_buf);
+	return ret;
 }
-#endif