[PATCH v4 for-next 15/24] RDMA/hfi2: Add SDMA infrastructure

Dennis Dalessandro <[email protected]>
Newsgroups org.kernel.vger.linux-rdma
Message-ID <178612542204.3164235.3244411732093438012.stgit@awdrv-04>
Add the software DMA engine and I/O wait queue management.

Co-developed-by: Dean Luick <[email protected]>
Signed-off-by: Dean Luick <[email protected]>
Co-developed-by: Bendan Cunningham <[email protected]>
Signed-off-by: Breandan Cunningham <[email protected]>
Co-developed-by: Douglas Miller <[email protected]>
Signed-off-by: Douglas Miller <[email protected]>
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Dennis Dalessandro <[email protected]>
---
  Changes since v2:
  - sdma.c: move trace_hfi2_sdma_progress() before complete_tx() in
    sdma_flush_descq() to prevent trace-after-free.
  - sdma.c: add bounds check before elem->sde[elem->ctr++] in
    hfi2_sdma_set_cpu_to_sde_map() to prevent heap overflow.
  - sdma.c: add synchronize_rcu() before freeing rhashtable node in
    hfi2_sdma_set_cpu_to_sde_map().
  - sdma.c: add cancel_work_sync() for flush_worker, err_halt_worker,
    and sdma_hw_clean_up_work in hfi2_sdma_exit() to prevent UAF on
    engine teardown.
  - Remove unused data_addr variable.
---
 drivers/infiniband/hw/hfi2/iowait.c |  129 +
 drivers/infiniband/hw/hfi2/sdma.c   | 4008 +++++++++++++++++++++++++++++++++++
 2 files changed, 4137 insertions(+)
 create mode 100644 drivers/infiniband/hw/hfi2/iowait.c
 create mode 100644 drivers/infiniband/hw/hfi2/sdma.c

diff --git a/drivers/infiniband/hw/hfi2/iowait.c b/drivers/infiniband/hw/hfi2/iowait.c
new file mode 100644
index 000000000000..9371a0dcb34c
--- /dev/null
+++ b/drivers/infiniband/hw/hfi2/iowait.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Copyright(c) 2018 Intel Corporation.
+ * Copyright(c) 2025-2026 Cornelis Networks, Inc.
+ *
+ */
+#include "iowait.h"
+#include "trace_iowait.h"
+
+/* 1 priority == 16 starve_cnt */
+#define IOWAIT_PRIORITY_STARVE_SHIFT 4
+
+void hfi2_iowait_set_flag(struct iowait *wait, u32 flag)
+{
+	trace_hfi2_iowait_set(wait, flag);
+	set_bit(flag, &wait->flags);
+}
+
+bool hfi2_iowait_flag_set(struct iowait *wait, u32 flag)
+{
+	return test_bit(flag, &wait->flags);
+}
+
+inline void hfi2_iowait_clear_flag(struct iowait *wait, u32 flag)
+{
+	trace_hfi2_iowait_clear(wait, flag);
+	clear_bit(flag, &wait->flags);
+}
+
+/*
+ * hfi2_iowait_init() - initialize wait structure
+ * @wait: wait struct to initialize
+ * @tx_limit: limit for overflow queuing
+ * @func: restart function for workqueue
+ * @sleep: sleep function for no space
+ * @resume: wakeup function for no space
+ *
+ * This function initializes the iowait
+ * structure embedded in the QP or PQ.
+ *
+ */
+void hfi2_iowait_init(struct iowait *wait, u32 tx_limit,
+		 void (*func)(struct work_struct *work),
+		 void (*tidfunc)(struct work_struct *work),
+		 int (*sleep)(struct sdma_engine *sde,
+			      struct iowait_work *wait,
+			      struct sdma_txreq *tx,
+			      uint seq,
+			      bool pkts_sent),
+		 void (*wakeup)(struct iowait *wait, int reason),
+		 void (*sdma_drained)(struct iowait *wait),
+		 void (*init_priority)(struct iowait *wait))
+{
+	int i;
+
+	wait->count = 0;
+	INIT_LIST_HEAD(&wait->list);
+	init_waitqueue_head(&wait->wait_dma);
+	init_waitqueue_head(&wait->wait_pio);
+	atomic_set(&wait->sdma_busy, 0);
+	atomic_set(&wait->pio_busy, 0);
+	wait->tx_limit = tx_limit;
+	wait->sleep = sleep;
+	wait->wakeup = wakeup;
+	wait->sdma_drained = sdma_drained;
+	wait->init_priority = init_priority;
+	wait->flags = 0;
+	for (i = 0; i < IOWAIT_SES; i++) {
+		wait->wait[i].iow = wait;
+		INIT_LIST_HEAD(&wait->wait[i].tx_head);
+		if (i == IOWAIT_IB_SE)
+			INIT_WORK(&wait->wait[i].iowork, func);
+		else
+			INIT_WORK(&wait->wait[i].iowork, tidfunc);
+	}
+}
+
+/**
+ * hfi2_iowait_cancel_work - cancel all work in iowait
+ * @w: the iowait struct
+ */
+void hfi2_iowait_cancel_work(struct iowait *w)
+{
+	cancel_work_sync(&iowait_get_ib_work(w)->iowork);
+	/* Make sure that the iowork for TID RDMA is used */
+	if (iowait_get_tid_work(w)->iowork.func)
+		cancel_work_sync(&iowait_get_tid_work(w)->iowork);
+}
+
+/**
+ * hfi2_iowait_set_work_flag - set work flag based on leg
+ * @w: the iowait work struct
+ */
+int hfi2_iowait_set_work_flag(struct iowait_work *w)
+{
+	if (w == &w->iow->wait[IOWAIT_IB_SE]) {
+		hfi2_iowait_set_flag(w->iow, IOWAIT_PENDING_IB);
+		return IOWAIT_IB_SE;
+	}
+	hfi2_iowait_set_flag(w->iow, IOWAIT_PENDING_TID);
+	return IOWAIT_TID_SE;
+}
+
+/**
+ * hfi2_iowait_priority_update_top - update the top priority entry
+ * @w: the iowait struct
+ * @top: a pointer to the top priority entry
+ * @idx: the index of the current iowait in an array
+ * @top_idx: the array index for the iowait entry that has the top priority
+ *
+ * This function is called to compare the priority of a given
+ * iowait with the given top priority entry. The top index will
+ * be returned.
+ */
+uint hfi2_iowait_priority_update_top(struct iowait *w,
+				struct iowait *top,
+				uint idx, uint top_idx)
+{
+	u8 cnt, tcnt;
+
+	/* Convert priority into starve_cnt and compare the total.*/
+	cnt = (w->priority << IOWAIT_PRIORITY_STARVE_SHIFT) + w->starved_cnt;
+	tcnt = (top->priority << IOWAIT_PRIORITY_STARVE_SHIFT) +
+		top->starved_cnt;
+	if (cnt > tcnt)
+		return idx;
+	else
+		return top_idx;
+}
diff --git a/drivers/infiniband/hw/hfi2/sdma.c b/drivers/infiniband/hw/hfi2/sdma.c
new file mode 100644
index 000000000000..699f1b832f0a
--- /dev/null
+++ b/drivers/infiniband/hw/hfi2/sdma.c
@@ -0,0 +1,4008 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright(c) 2015 - 2018 Intel Corporation.
+ * Copyright(c) 2025-2026 Cornelis Networks, Inc.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/seqlock.h>
+#include <linux/netdevice.h>
+#include <linux/moduleparam.h>
+#include <linux/bitops.h>
+#include <linux/timer.h>
+#include <linux/vmalloc.h>
+#include <linux/highmem.h>
+
+#include "hfi2.h"
+#include "common.h"
+#include "qp.h"
+#include "sdma.h"
+#include "iowait.h"
+#include "trace.h"
+
+/* must be a power of 2 >= 64 <= 32768 */
+#define SDMA_DESCQ_CNT 2048
+#define SDMA_DESC_INTR 64
+#define INVALID_TAIL 0xffff
+#define SDMA_PAD max_t(size_t, MAX_16B_PADDING, sizeof(u32))
+
+static uint sdma_descq_cnt = SDMA_DESCQ_CNT;
+
+static uint sdma_idle_cnt = 250;
+
+uint hfi2_mod_num_sdma;
+
+static uint sdma_desct_intr = SDMA_DESC_INTR;
+
+static uint enable_jkr_sdma_mem_init;
+
+static uint sdma_single_descriptor;
+
+static int sdma_threshold = -1;
+
+static int pad_sdma_desc = -1;
+
+static int sdma_align = -1;
+
+#define JKR_DEFAULT_SDMA_CREDITS_LIMIT 1568
+/*
+ * -1 => module parameter not set during load, use
+ * JKR_DEFAULT_SDMA_CREDITS_LIMIT.
+ *
+ * Use this to distinguish between user intentionally setting
+ * jkr_sdma_credits_limit > 6272/num_sdma and the per-SDMA credit limit
+ * happening to be > 6272/num_sdma because of value of num_sdma.
+ */
+static int jkr_sdma_credits_limit = -1;
+
+#define SDMA_WAIT_BATCH_SIZE 20
+/* max wait time for a SDMA engine to indicate it has halted */
+#define SDMA_ERR_HALT_TIMEOUT 10 /* ms */
+#define SIM_SDMA_ERR_HALT_TIMEOUT 1000 /* ms */
+
+#define SD(name) SEND_DMA_##name
+/* all SDMA engine errors that cause a halt */
+#define ALL_SDMA_ENG_HALT_ERRS                                    \
+	(SD(ENG_ERR_STATUS_SDMA_WRONG_DW_ERR_SMASK) |             \
+	 SD(ENG_ERR_STATUS_SDMA_GEN_MISMATCH_ERR_SMASK) |         \
+	 SD(ENG_ERR_STATUS_SDMA_TOO_LONG_ERR_SMASK) |             \
+	 SD(ENG_ERR_STATUS_SDMA_TAIL_OUT_OF_BOUNDS_ERR_SMASK) |   \
+	 SD(ENG_ERR_STATUS_SDMA_FIRST_DESC_ERR_SMASK) |           \
+	 SD(ENG_ERR_STATUS_SDMA_MEM_READ_ERR_SMASK) |             \
+	 SD(ENG_ERR_STATUS_SDMA_HALT_ERR_SMASK) |                 \
+	 SD(ENG_ERR_STATUS_SDMA_LENGTH_MISMATCH_ERR_SMASK) |      \
+	 SD(ENG_ERR_STATUS_SDMA_PACKET_DESC_OVERFLOW_ERR_SMASK) | \
+	 SD(ENG_ERR_STATUS_SDMA_HEADER_SELECT_ERR_SMASK) |        \
+	 SD(ENG_ERR_STATUS_SDMA_HEADER_ADDRESS_ERR_SMASK) |       \
+	 SD(ENG_ERR_STATUS_SDMA_HEADER_LENGTH_ERR_SMASK) |        \
+	 SD(ENG_ERR_STATUS_SDMA_TIMEOUT_ERR_SMASK) |              \
+	 SD(ENG_ERR_STATUS_SDMA_DESC_TABLE_UNC_ERR_SMASK) |       \
+	 SD(ENG_ERR_STATUS_SDMA_ASSEMBLY_UNC_ERR_SMASK) |         \
+	 SD(ENG_ERR_STATUS_SDMA_PACKET_TRACKING_UNC_ERR_SMASK) |  \
+	 SD(ENG_ERR_STATUS_SDMA_HEADER_STORAGE_UNC_ERR_SMASK) |   \
+	 SD(ENG_ERR_STATUS_SDMA_HEADER_REQUEST_FIFO_UNC_ERR_SMASK))
+
+/* all SDMA engine errors that are correctable */
+#define ALL_SDMA_ENG_COR_ERRS                                             \
+	(SEND_DMA_ENG_ERR_STATUS_SDMA_HEADER_REQUEST_FIFO_COR_ERR_SMASK | \
+	 SEND_DMA_ENG_ERR_STATUS_SDMA_HEADER_STORAGE_COR_ERR_SMASK |      \
+	 SEND_DMA_ENG_ERR_STATUS_SDMA_PACKET_TRACKING_COR_ERR_SMASK |     \
+	 SEND_DMA_ENG_ERR_STATUS_SDMA_ASSEMBLY_COR_ERR_SMASK |            \
+	 SEND_DMA_ENG_ERR_STATUS_SDMA_DESC_TABLE_COR_ERR_SMASK)
+
+/* sdma_sendctrl operations */
+#define SDMA_SENDCTRL_OP_ENABLE BIT(0)
+#define SDMA_SENDCTRL_OP_INTENABLE BIT(1)
+#define SDMA_SENDCTRL_OP_HALT BIT(2)
+#define SDMA_SENDCTRL_OP_CLEANUP BIT(3)
+
+/* handle long defines */
+#define SDMA_EGRESS_PACKET_OCCUPANCY_SMASK \
+	SEND_EGRESS_SEND_DMA_STATUS_SDMA_EGRESS_PACKET_OCCUPANCY_SMASK
+#define SDMA_EGRESS_PACKET_OCCUPANCY_SHIFT \
+	SEND_EGRESS_SEND_DMA_STATUS_SDMA_EGRESS_PACKET_OCCUPANCY_SHIFT
+
+static const char *const sdma_state_names[] = {
+	[sdma_state_s00_hw_down] = "s00_HwDown",
+	[sdma_state_s10_hw_start_up_halt_wait] = "s10_HwStartUpHaltWait",
+	[sdma_state_s15_hw_start_up_clean_wait] = "s15_HwStartUpCleanWait",
+	[sdma_state_s20_idle] = "s20_Idle",
+	[sdma_state_s30_sw_clean_up_wait] = "s30_SwCleanUpWait",
+	[sdma_state_s40_hw_clean_up_wait] = "s40_HwCleanUpWait",
+	[sdma_state_s50_hw_halt_wait] = "s50_HwHaltWait",
+	[sdma_state_s60_idle_halt_wait] = "s60_IdleHaltWait",
+	[sdma_state_s80_hw_freeze] = "s80_HwFreeze",
+	[sdma_state_s82_freeze_sw_clean] = "s82_FreezeSwClean",
+	[sdma_state_s99_running] = "s99_Running",
+};
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+static const char *const sdma_event_names[] = {
+	[sdma_event_e00_go_hw_down] = "e00_GoHwDown",
+	[sdma_event_e10_go_hw_start] = "e10_GoHwStart",
+	[sdma_event_e15_hw_halt_done] = "e15_HwHaltDone",
+	[sdma_event_e25_hw_clean_up_done] = "e25_HwCleanUpDone",
+	[sdma_event_e30_go_running] = "e30_GoRunning",
+	[sdma_event_e40_sw_cleaned] = "e40_SwCleaned",
+	[sdma_event_e50_hw_cleaned] = "e50_HwCleaned",
+	[sdma_event_e60_hw_halted] = "e60_HwHalted",
+	[sdma_event_e70_go_idle] = "e70_GoIdle",
+	[sdma_event_e80_hw_freeze] = "e80_HwFreeze",
+	[sdma_event_e81_hw_frozen] = "e81_HwFrozen",
+	[sdma_event_e82_hw_unfreeze] = "e82_HwUnfreeze",
+	[sdma_event_e85_link_down] = "e85_LinkDown",
+	[sdma_event_e90_sw_halted] = "e90_SwHalted",
+};
+#endif
+
+static const struct sdma_set_state_action sdma_action_table[] = {
+	[sdma_state_s00_hw_down] = {
+		.go_s99_running_tofalse = 1,
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s10_hw_start_up_halt_wait] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 1,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s15_hw_start_up_clean_wait] = {
+		.op_enable = 0,
+		.op_intenable = 1,
+		.op_halt = 0,
+		.op_cleanup = 1,
+	},
+	[sdma_state_s20_idle] = {
+		.op_enable = 0,
+		.op_intenable = 1,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s30_sw_clean_up_wait] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s40_hw_clean_up_wait] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 1,
+	},
+	[sdma_state_s50_hw_halt_wait] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s60_idle_halt_wait] = {
+		.go_s99_running_tofalse = 1,
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 1,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s80_hw_freeze] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s82_freeze_sw_clean] = {
+		.op_enable = 0,
+		.op_intenable = 0,
+		.op_halt = 0,
+		.op_cleanup = 0,
+	},
+	[sdma_state_s99_running] = {
+		.op_enable = 1,
+		.op_intenable = 1,
+		.op_halt = 0,
+		.op_cleanup = 0,
+		.go_s99_running_totrue = 1,
+	},
+};
+
+#define SDMA_TAIL_UPDATE_THRESH 0x1F
+
+/* declare all statics here rather than keep sorting */
+static void sdma_complete(struct kref *);
+static void sdma_finalput(struct sdma_state *);
+static void sdma_get(struct sdma_state *);
+static void sdma_hw_clean_up_worker(struct work_struct *);
+static void sdma_put(struct sdma_state *);
+static void sdma_set_state(struct sdma_engine *, enum sdma_states);
+static void sdma_start_hw_clean_up(struct sdma_engine *);
+static void sdma_sw_clean_up_worker(struct work_struct *);
+static void sdma_sendctrl(struct sdma_engine *sde, unsigned int op);
+static void init_sdma_regs(struct sdma_engine *, uint);
+static void sdma_process_event(struct sdma_engine *sde, enum sdma_events event);
+static void __sdma_process_event(struct sdma_engine *sde,
+				 enum sdma_events event);
+static void dump_sdma_state(struct sdma_engine *sde);
+static void sdma_make_progress(struct sdma_engine *sde, u64 status);
+static void sdma_desc_avail(struct sdma_engine *sde, uint avail);
+static void sdma_flush_descq(struct sdma_engine *sde);
+static void sdma_rht_free(void *ptr, void *arg);
+static int prime_sdma_memories(struct hfi2_devdata *dd);
+
+/**
+ * sdma_state_name() - return state string from enum
+ * @state: state
+ */
+static const char *sdma_state_name(enum sdma_states state)
+{
+	return sdma_state_names[state];
+}
+
+static void sdma_get(struct sdma_state *ss)
+{
+	kref_get(&ss->kref);
+}
+
+static void sdma_complete(struct kref *kref)
+{
+	struct sdma_state *ss = container_of(kref, struct sdma_state, kref);
+
+	complete(&ss->comp);
+}
+
+static void sdma_put(struct sdma_state *ss)
+{
+	kref_put(&ss->kref, sdma_complete);
+}
+
+static void sdma_finalput(struct sdma_state *ss)
+{
+	sdma_put(ss);
+	wait_for_completion(&ss->comp);
+}
+
+static inline void write_sde_csr(struct sdma_engine *sde, u32 offset0,
+				 u64 value)
+{
+	write_sdma_csr(sde->dd, sde->this_idx, offset0, value);
+}
+
+static inline u64 read_sde_csr(struct sdma_engine *sde, u32 offset0)
+{
+	return read_sdma_csr(sde->dd, sde->this_idx, offset0);
+}
+
+static inline void write_sdecfg_csr(struct sdma_engine *sde, u32 offset,
+				    u64 value)
+{
+	write_sdmacfg_csr(sde->dd, sde->this_idx, offset, value);
+}
+
+static inline u64 read_sdecfg_csr(struct sdma_engine *sde, u32 offset)
+{
+	return read_sdmacfg_csr(sde->dd, sde->this_idx, offset);
+}
+
+static inline void __iomem *get_sdma_csr_addr(const struct hfi2_devdata *dd,
+					      int eng, u32 offset)
+{
+	return hfi2_get_csr_addr(dd,
+				 offset + (dd->params->txe_sdma_stride * eng));
+}
+
+/*
+ * sdma_wait_for_packet_egress() - wait for the Launch FIFO occupancy for
+ * sdma engine 'sde' to drop to 0.
+ */
+static void sdma_wait_for_packet_egress(struct sdma_engine *sde, int pause)
+{
+	u64 off = 8 * sde->this_idx;
+	struct hfi2_devdata *dd = sde->dd;
+	struct hfi2_pportdata *ppd;
+	int lcnt;
+	int pidx;
+	u64 reg_prev;
+	u64 reg;
+
+	for (pidx = 0; pidx < dd->num_pports; pidx++) {
+		ppd = dd->pport + pidx;
+		lcnt = 0;
+		reg = 0;
+
+		while (1) {
+			reg_prev = reg;
+			reg = read_eport_csr(
+				dd, pidx,
+				off + dd->params->send_egress_send_dma_status_reg);
+
+			reg &= SDMA_EGRESS_PACKET_OCCUPANCY_SMASK;
+			reg >>= SDMA_EGRESS_PACKET_OCCUPANCY_SHIFT;
+			if (reg == 0)
+				break;
+			/* counter is reset if accupancy count changes */
+			if (reg != reg_prev)
+				lcnt = 0;
+			if (lcnt++ > 500) {
+				/* timed out - bounce the link */
+				dd_dev_err(
+					dd,
+					"%s: engine %u timeout waiting for packets to egress, remaining count %u, bouncing link\n",
+					__func__, sde->this_idx, (u32)reg);
+				queue_work(ppd->link_wq,
+					   &ppd->link_bounce_work);
+				break;
+			}
+			udelay(1);
+		}
+	}
+}
+
+/*
+ * hfi2_sdma_wait() - wait for packet egress to complete for all SDMA engines,
+ * and pause for credit return.
+ */
+void hfi2_sdma_wait(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	int i;
+
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; i++) {
+		struct sdma_engine *sde = &dd->per_sdma[i];
+
+		sdma_wait_for_packet_egress(sde, 0);
+	}
+}
+
+static inline void sdma_set_desc_cnt(struct sdma_engine *sde, unsigned int cnt)
+{
+	u64 reg;
+
+	if (!(sde->dd->flags & HFI2_HAS_SDMA_TIMEOUT))
+		return;
+	reg = cnt;
+	reg &= SD(DESC_CNT_CNT_MASK);
+	reg <<= SD(DESC_CNT_CNT_SHIFT);
+	write_sde_csr(sde, sde->dd->params->send_dma_desc_cnt_reg, reg);
+}
+
+static inline void complete_tx(struct sdma_engine *sde, struct sdma_txreq *tx,
+			       int res)
+{
+	/* protect against complete modifying */
+	struct iowait *wait = tx->wait;
+	callback_t complete = tx->complete;
+
+#ifdef CONFIG_HFI2_DEBUG_SDMA_ORDER
+	trace_hfi2_sdma_out_sn(sde, tx->sn);
+	if (WARN_ON_ONCE(sde->head_sn != tx->sn))
+		dd_dev_err(sde->dd, "expected %llu got %llu\n", sde->head_sn,
+			   tx->sn);
+	sde->head_sn++;
+#endif
+	__hfi2_sdma_txclean(sde->dd, tx);
+	if (complete)
+		(*complete)(tx, res);
+	if (iowait_sdma_dec(wait))
+		iowait_drain_wakeup(wait);
+}
+
+/*
+ * Complete all the sdma requests with a SDMA_TXREQ_S_ABORTED status
+ *
+ * Depending on timing there can be txreqs in two places:
+ * - in the descq ring
+ * - in the flush list
+ *
+ * To avoid ordering issues the descq ring needs to be flushed
+ * first followed by the flush list.
+ *
+ * This routine is called from two places
+ * - From a work queue item
+ * - Directly from the state machine just before setting the
+ *   state to running
+ *
+ * Must be called with head_lock held
+ *
+ */
+static void sdma_flush(struct sdma_engine *sde)
+{
+	struct sdma_txreq *txp, *txp_next;
+	LIST_HEAD(flushlist);
+	unsigned long flags;
+	uint seq;
+
+	/* flush from head to tail */
+	sdma_flush_descq(sde);
+	spin_lock_irqsave(&sde->flushlist_lock, flags);
+	/* copy flush list */
+	list_splice_init(&sde->flushlist, &flushlist);
+	spin_unlock_irqrestore(&sde->flushlist_lock, flags);
+	/* flush from flush list */
+	list_for_each_entry_safe(txp, txp_next, &flushlist, list)
+		complete_tx(sde, txp, SDMA_TXREQ_S_ABORTED);
+	/* wakeup QPs orphaned on the dmawait list */
+	do {
+		struct iowait *w, *nw;
+
+		seq = read_seqbegin(&sde->waitlock);
+		if (!list_empty(&sde->dmawait)) {
+			write_seqlock(&sde->waitlock);
+			list_for_each_entry_safe(w, nw, &sde->dmawait, list) {
+				if (w->wakeup) {
+					w->wakeup(w, SDMA_AVAIL_REASON);
+					list_del_init(&w->list);
+				}
+			}
+			write_sequnlock(&sde->waitlock);
+		}
+	} while (read_seqretry(&sde->waitlock, seq));
+}
+
+/*
+ * Fields a work request for flushing the descq ring
+ * and the flush list
+ *
+ * If the engine has been brought to running during
+ * the scheduling delay, the flush is ignored, assuming
+ * that the process of bringing the engine to running
+ * would have done this flush prior to going to running.
+ *
+ */
+static void sdma_field_flush(struct work_struct *work)
+{
+	unsigned long flags;
+	struct sdma_engine *sde =
+		container_of(work, struct sdma_engine, flush_worker);
+
+	write_seqlock_irqsave(&sde->head_lock, flags);
+	if (!__sdma_running(sde))
+		sdma_flush(sde);
+	write_sequnlock_irqrestore(&sde->head_lock, flags);
+}
+
+static void sdma_err_halt_wait(struct work_struct *work)
+{
+	struct sdma_engine *sde =
+		container_of(work, struct sdma_engine, err_halt_worker);
+	u64 statuscsr;
+	unsigned long timeout;
+
+	if (unlikely(sde->dd->icode == ICODE_FUNCTIONAL_SIMULATOR))
+		timeout = jiffies + msecs_to_jiffies(SIM_SDMA_ERR_HALT_TIMEOUT);
+	else
+		timeout = jiffies + msecs_to_jiffies(SDMA_ERR_HALT_TIMEOUT);
+	while (1) {
+		statuscsr =
+			read_sde_csr(sde, sde->dd->params->send_dma_status_reg);
+		statuscsr &= SD(STATUS_ENG_HALTED_SMASK);
+		if (statuscsr)
+			break;
+		if (time_after(jiffies, timeout)) {
+			dd_dev_err(
+				sde->dd,
+				"SDMA engine %d - timeout waiting for engine to halt\n",
+				sde->this_idx);
+			/*
+			 * Continue anyway.  This could happen if there was
+			 * an uncorrectable error in the wrong spot.
+			 */
+			break;
+		}
+		usleep_range(80, 120);
+	}
+
+	sdma_process_event(sde, sdma_event_e15_hw_halt_done);
+}
+
+static void sdma_err_progress_check_schedule(struct sdma_engine *sde)
+{
+	struct hfi2_devrsrcs *dr = &sde->dd->rsrcs;
+
+	if (!hfi2_is_bx(sde->dd) && HFI2_CAP_IS_KSET(SDMA_AHG)) {
+		unsigned int index;
+		struct hfi2_devdata *dd = sde->dd;
+
+		for (index = dr->first_sdma_engine;
+		     index < dr->last_sdma_engine; index++) {
+			struct sdma_engine *curr_sdma = &dd->per_sdma[index];
+
+			if (curr_sdma != sde)
+				curr_sdma->progress_check_head =
+					curr_sdma->descq_head;
+		}
+		dd_dev_err(sde->dd, "SDMA engine %d - check scheduled\n",
+			   sde->this_idx);
+		mod_timer(&sde->err_progress_check_timer, jiffies + 10);
+	}
+}
+
+static void sdma_err_progress_check(struct timer_list *t)
+{
+	unsigned int index;
+	struct sdma_engine *sde =
+		timer_container_of(sde, t, err_progress_check_timer);
+	struct hfi2_devrsrcs *dr = &sde->dd->rsrcs;
+
+	dd_dev_err(sde->dd, "SDE progress check event\n");
+	for (index = dr->first_sdma_engine; index < dr->last_sdma_engine;
+	     index++) {
+		struct sdma_engine *curr_sde = &sde->dd->per_sdma[index];
+		unsigned long flags;
+
+		/* check progress on each engine except the current one */
+		if (curr_sde == sde)
+			continue;
+		/*
+		 * We must lock interrupts when acquiring sde->lock,
+		 * to avoid a deadlock if interrupt triggers and spins on
+		 * the same lock on same CPU
+		 */
+		spin_lock_irqsave(&curr_sde->tail_lock, flags);
+		write_seqlock(&curr_sde->head_lock);
+
+		/* skip non-running queues */
+		if (curr_sde->state.current_state != sdma_state_s99_running) {
+			write_sequnlock(&curr_sde->head_lock);
+			spin_unlock_irqrestore(&curr_sde->tail_lock, flags);
+			continue;
+		}
+
+		if ((curr_sde->descq_head != curr_sde->descq_tail) &&
+		    (curr_sde->descq_head == curr_sde->progress_check_head))
+			__sdma_process_event(curr_sde,
+					     sdma_event_e90_sw_halted);
+		write_sequnlock(&curr_sde->head_lock);
+		spin_unlock_irqrestore(&curr_sde->tail_lock, flags);
+	}
+	schedule_work(&sde->err_halt_worker);
+}
+
+static void sdma_hw_clean_up_worker(struct work_struct *work)
+{
+	struct sdma_engine *sde =
+		container_of(work, struct sdma_engine, sdma_hw_clean_up_work);
+	u64 statuscsr;
+	u32 count = 0;
+
+	while (1) {
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+		dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n",
+			   sde->this_idx, slashstrip(__FILE__), __LINE__,
+			   __func__);
+#endif
+		statuscsr =
+			read_sde_csr(sde, sde->dd->params->send_dma_status_reg);
+		statuscsr &= SD(STATUS_ENG_CLEANED_UP_SMASK);
+		if (statuscsr)
+			break;
+		if (++count > 100) {
+			dd_dev_err(
+				sde->dd,
+				"SDMA engine %d - timeout waiting for engine to clean\n",
+				sde->this_idx);
+			break;
+		}
+		udelay(10);
+	}
+
+	sdma_process_event(sde, sdma_event_e25_hw_clean_up_done);
+}
+
+static inline struct sdma_txreq *get_txhead(struct sdma_engine *sde)
+{
+	return sde->tx_ring[sde->tx_head & sde->sdma_mask];
+}
+
+/*
+ * flush ring for recovery
+ */
+static void sdma_flush_descq(struct sdma_engine *sde)
+{
+	u16 head, tail;
+	int progress = 0;
+	struct sdma_txreq *txp = get_txhead(sde);
+
+	/* The reason for some of the complexity of this code is that
+	 * not all descriptors have corresponding txps.  So, we have to
+	 * be able to skip over descs until we wander into the range of
+	 * the next txp on the list.
+	 */
+	head = sde->descq_head & sde->sdma_mask;
+	tail = sde->descq_tail & sde->sdma_mask;
+	while (head != tail) {
+		/* advance head, wrap if needed */
+		head = ++sde->descq_head & sde->sdma_mask;
+		/* if now past this txp's descs, do the callback */
+		if (txp && txp->next_descq_idx == head) {
+			/* remove from list */
+			sde->tx_ring[sde->tx_head++ & sde->sdma_mask] = NULL;
+			trace_hfi2_sdma_progress(sde, head, tail, txp);
+			complete_tx(sde, txp, SDMA_TXREQ_S_ABORTED);
+			txp = get_txhead(sde);
+		}
+		progress++;
+	}
+	if (progress)
+		sdma_desc_avail(sde, sdma_descq_freecnt(sde));
+}
+
+static void sdma_sw_clean_up_worker(struct work_struct *work)
+{
+	struct sdma_engine *sde =
+		container_of(work, struct sdma_engine, sdma_sw_clean_up_work);
+	unsigned long flags;
+
+	spin_lock_irqsave(&sde->tail_lock, flags);
+	write_seqlock(&sde->head_lock);
+
+	/*
+	 * At this point, the following should always be true:
+	 * - We are halted, so no more descriptors are getting retired.
+	 * - We are not running, so no one is submitting new work.
+	 * - Only we can send the e40_sw_cleaned, so we can't start
+	 *   running again until we say so.  So, the active list and
+	 *   descq are ours to play with.
+	 */
+
+	/*
+	 * In the error clean up sequence, software clean must be called
+	 * before the hardware clean so we can use the hardware head in
+	 * the progress routine.  A hardware clean or SPC unfreeze will
+	 * reset the hardware head.
+	 *
+	 * Process all retired requests. The progress routine will use the
+	 * latest physical hardware head - we are not running so speed does
+	 * not matter.
+	 */
+	sdma_make_progress(sde, 0);
+
+	sdma_flush(sde);
+
+	/*
+	 * Reset our notion of head and tail.
+	 * Note that the HW registers have been reset via an earlier
+	 * clean up.
+	 */
+	sde->descq_tail = 0;
+	sde->descq_head = 0;
+	sde->desc_avail = sdma_descq_freecnt(sde);
+	*sde->head_dma = 0;
+
+	__sdma_process_event(sde, sdma_event_e40_sw_cleaned);
+
+	write_sequnlock(&sde->head_lock);
+	spin_unlock_irqrestore(&sde->tail_lock, flags);
+}
+
+static void sdma_sw_tear_down(struct sdma_engine *sde)
+{
+	struct sdma_state *ss = &sde->state;
+
+	/* Releasing this reference means the state machine has stopped. */
+	sdma_put(ss);
+
+	/* stop waiting for all unfreeze events to complete */
+	atomic_set(&sde->dd->sdma_unfreeze_count, -1);
+	wake_up_interruptible(&sde->dd->sdma_unfreeze_wq);
+}
+
+static void sdma_start_hw_clean_up(struct sdma_engine *sde)
+{
+	queue_work(sde->dd->hfi2_wq, &sde->sdma_hw_clean_up_work);
+}
+
+static void sdma_set_state(struct sdma_engine *sde, enum sdma_states next_state)
+{
+	struct sdma_state *ss = &sde->state;
+	const struct sdma_set_state_action *action = sdma_action_table;
+	unsigned int op = 0;
+
+	trace_hfi2_sdma_state(sde, sdma_state_names[ss->current_state],
+			      sdma_state_names[next_state]);
+
+	/* debugging bookkeeping */
+	ss->previous_state = ss->current_state;
+	ss->previous_op = ss->current_op;
+	ss->current_state = next_state;
+
+	if (ss->previous_state != sdma_state_s99_running &&
+	    next_state == sdma_state_s99_running)
+		sdma_flush(sde);
+
+	if (action[next_state].op_enable)
+		op |= SDMA_SENDCTRL_OP_ENABLE;
+
+	if (action[next_state].op_intenable)
+		op |= SDMA_SENDCTRL_OP_INTENABLE;
+
+	if (action[next_state].op_halt)
+		op |= SDMA_SENDCTRL_OP_HALT;
+
+	if (action[next_state].op_cleanup)
+		op |= SDMA_SENDCTRL_OP_CLEANUP;
+
+	if (action[next_state].go_s99_running_tofalse)
+		ss->go_s99_running = 0;
+
+	if (action[next_state].go_s99_running_totrue)
+		ss->go_s99_running = 1;
+
+	ss->current_op = op;
+	sdma_sendctrl(sde, ss->current_op);
+}
+
+/**
+ * hfi2_sdma_get_descq_cnt() - called when device probed
+ *
+ * Return a validated descq count.
+ *
+ * This is currently only used in the verbs initialization to build the tx
+ * list.
+ *
+ * This will probably be deleted in favor of a more scalable approach to
+ * alloc tx's.
+ *
+ */
+u16 hfi2_sdma_get_descq_cnt(void)
+{
+	u16 count = sdma_descq_cnt;
+
+	if (!count)
+		return SDMA_DESCQ_CNT;
+	/* count must be a power of 2 greater than 64 and less than
+	 * 32768.   Otherwise return default.
+	 */
+	if (!is_power_of_2(count))
+		return SDMA_DESCQ_CNT;
+	if (count < 64 || count > 32768)
+		return SDMA_DESCQ_CNT;
+	return count;
+}
+
+/**
+ * hfi2_sdma_engine_get_vl() - return vl for a given sdma engine
+ * @ppd: port structure
+ * @sde: sdma engine
+ *
+ * This function returns the vl mapped to a given engine, or an error if
+ * the mapping can't be found. The mapping fields are protected by RCU.
+ */
+int hfi2_sdma_engine_get_vl(struct hfi2_pportdata *ppd, struct sdma_engine *sde)
+{
+	struct sdma_vl_map *m;
+	u8 vl;
+
+	if (sde->this_idx >= TXE_NUM_SDMA_ENGINES)
+		return -EINVAL;
+
+	rcu_read_lock();
+	m = rcu_dereference(ppd->sdma_map);
+	if (unlikely(!m)) {
+		rcu_read_unlock();
+		return -EINVAL;
+	}
+	vl = m->engine_to_vl[sde->this_idx];
+	rcu_read_unlock();
+
+	return vl;
+}
+
+/**
+ * hfi2_sdma_select_engine_vl() - select sdma engine
+ * @ppd: port structure
+ * @selector: a spreading factor
+ * @vl: this vl
+ *
+ * This function returns an engine based on the selector and a vl.  The
+ * mapping fields are protected by RCU.
+ */
+struct sdma_engine *hfi2_sdma_select_engine_vl(struct hfi2_pportdata *ppd,
+					       u32 selector, u8 vl)
+{
+	struct hfi2_devdata *dd = ppd->dd;
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	struct sdma_vl_map *m;
+	struct sdma_map_elem *e;
+	struct sdma_engine *rval;
+
+	/* NOTE This should only happen if SC->VL changed after the initial
+	 *      checks on the QP/AH
+	 *      Default will return engine 0 below
+	 */
+	if (vl >= hfi2_num_vls) {
+		rval = NULL;
+		goto done;
+	}
+
+	rcu_read_lock();
+	m = rcu_dereference(ppd->sdma_map);
+	if (unlikely(!m)) {
+		rcu_read_unlock();
+		return &dd->per_sdma[dr->first_sdma_engine];
+	}
+	e = m->map[vl & m->mask];
+	rval = e->sde[selector & e->mask];
+	rcu_read_unlock();
+
+done:
+	rval = !rval ? &dd->per_sdma[dr->first_sdma_engine] : rval;
+	trace_hfi2_sdma_engine_select(dd, selector, vl, rval->this_idx);
+	return rval;
+}
+
+/**
+ * hfi2_sdma_select_engine_sc() - select sdma engine
+ * @ppd: port structure
+ * @selector: a spreading factor
+ * @sc5: the 5 bit sc
+ *
+ * This function returns an engine based on the selector and an sc.
+ */
+struct sdma_engine *hfi2_sdma_select_engine_sc(struct hfi2_pportdata *ppd,
+					       u32 selector, u8 sc5)
+{
+	u8 vl = sc_to_vlt(ppd, sc5);
+
+	return hfi2_sdma_select_engine_vl(ppd, selector, vl);
+}
+
+struct sdma_rht_map_elem {
+	u32 mask;
+	u8 ctr;
+	struct sdma_engine *sde[TXE_NUM_SDMA_ENGINES];
+};
+
+struct sdma_rht_node {
+	unsigned long cpu_id;
+	struct sdma_rht_map_elem
+		*port_map[LARGEST_NUM_PORTS][HFI2_MAX_VLS_SUPPORTED];
+	struct rhash_head node;
+};
+
+#define NR_CPUS_HINT 192
+
+static const struct rhashtable_params sdma_rht_params = {
+	.nelem_hint = NR_CPUS_HINT,
+	.head_offset = offsetof(struct sdma_rht_node, node),
+	.key_offset = offsetof(struct sdma_rht_node, cpu_id),
+	.key_len = sizeof_field(struct sdma_rht_node, cpu_id),
+	.max_size = NR_CPUS,
+	.min_size = 8,
+	.automatic_shrinking = true,
+};
+
+/*
+ * hfi2_sdma_select_user_engine() - select sdma engine based on user setup
+ * @ppd: port structure
+ * @selector: a spreading factor
+ * @vl: this vl
+ *
+ * This function returns an sdma engine for a user sdma request.
+ * User defined sdma engine affinity setting is honored when applicable,
+ * otherwise system default sdma engine mapping is used. To ensure correct
+ * ordering, the mapping from <selector, vl> to sde must remain unchanged.
+ */
+struct sdma_engine *hfi2_sdma_select_user_engine(struct hfi2_pportdata *ppd,
+						 u32 selector, u8 vl)
+{
+	struct hfi2_devdata *dd = ppd->dd;
+	struct sdma_rht_node *rht_node;
+	struct sdma_rht_map_elem *map;
+	struct sdma_engine *sde = NULL;
+	unsigned long cpu_id;
+
+	/*
+	 * To ensure that always the same sdma engine(s) will be
+	 * selected make sure the process is pinned to this CPU only.
+	 */
+	if (current->nr_cpus_allowed != 1)
+		goto out;
+
+	rcu_read_lock();
+	cpu_id = smp_processor_id();
+	rht_node = rhashtable_lookup(dd->sdma_rht, &cpu_id, sdma_rht_params);
+
+	if (!rht_node)
+		goto unlock;
+	map = rht_node->port_map[ppd->hw_pidx][vl];
+	if (!map)
+		goto unlock;
+	sde = map->sde[selector & map->mask];
+unlock:
+	rcu_read_unlock();
+
+	if (sde)
+		return sde;
+
+out:
+	return hfi2_sdma_select_engine_vl(ppd, selector, vl);
+}
+
+static void sdma_populate_sde_map(struct sdma_rht_map_elem *map)
+{
+	int i;
+
+	for (i = 0; i < roundup_pow_of_two(map->ctr ?: 1) - map->ctr; i++)
+		map->sde[map->ctr + i] = map->sde[i];
+}
+
+static void sdma_cleanup_sde_map(struct sdma_rht_map_elem *map,
+				 struct sdma_engine *sde)
+{
+	unsigned int i, pow;
+
+	/* only need to check the first ctr entries for a match */
+	for (i = 0; i < map->ctr; i++) {
+		if (map->sde[i] == sde) {
+			memmove(&map->sde[i], &map->sde[i + 1],
+				(map->ctr - i - 1) * sizeof(map->sde[0]));
+			map->ctr--;
+			pow = roundup_pow_of_two(map->ctr ?: 1);
+			map->mask = pow - 1;
+			sdma_populate_sde_map(map);
+			break;
+		}
+	}
+}
+
+/*
+ * Prevents concurrent reads and writes of the sdma engine cpu_mask
+ */
+static DEFINE_MUTEX(process_to_sde_mutex);
+
+ssize_t hfi2_sdma_set_cpu_to_sde_map(struct sdma_engine *sde, const char *buf,
+				     size_t count)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	cpumask_var_t mask, new_mask;
+	unsigned long cpu;
+	int ret, vl, sz;
+	int pidx;
+	struct sdma_rht_node *rht_node;
+
+	ret = zalloc_cpumask_var(&mask, GFP_KERNEL);
+	if (!ret)
+		return -ENOMEM;
+
+	ret = zalloc_cpumask_var(&new_mask, GFP_KERNEL);
+	if (!ret) {
+		free_cpumask_var(mask);
+		return -ENOMEM;
+	}
+	ret = cpulist_parse(buf, mask);
+	if (ret)
+		goto out_free;
+
+	if (!cpumask_subset(mask, cpu_online_mask)) {
+		dd_dev_warn(sde->dd, "Invalid CPU mask\n");
+		ret = -EINVAL;
+		goto out_free;
+	}
+
+	sz = sizeof(struct sdma_rht_map_elem);
+
+	mutex_lock(&process_to_sde_mutex);
+
+	for_each_cpu(cpu, mask) {
+		struct sdma_rht_map_elem *elem;
+		bool do_insert;
+
+		/* Check if we have this already mapped */
+		if (cpumask_test_cpu(cpu, &sde->cpu_mask)) {
+			cpumask_set_cpu(cpu, new_mask);
+			continue;
+		}
+
+		rht_node = rhashtable_lookup_fast(dd->sdma_rht, &cpu,
+						  sdma_rht_params);
+
+		do_insert = false;
+		if (!rht_node) {
+			rht_node = kzalloc_obj(rht_node, GFP_KERNEL);
+			if (!rht_node) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			rht_node->cpu_id = cpu;
+			/* insert later to allow free if there is an error */
+			do_insert = true;
+		}
+
+		for (pidx = 0; pidx < dd->num_pports; pidx++) {
+			vl = hfi2_sdma_engine_get_vl(&dd->pport[pidx], sde);
+			if (unlikely(vl < 0 || vl >= HFI2_MAX_VLS_SUPPORTED)) {
+				ret = -EINVAL;
+				goto fail_new;
+			}
+
+			elem = rht_node->port_map[pidx][vl];
+			if (!elem) {
+				elem = kzalloc(sz, GFP_KERNEL);
+				if (!elem) {
+					ret = -ENOMEM;
+					goto fail_new;
+				}
+				rht_node->port_map[pidx][vl] = elem;
+			}
+
+			if (WARN_ON_ONCE(elem->ctr >= TXE_NUM_SDMA_ENGINES)) {
+				ret = -EINVAL;
+				goto fail_new;
+			}
+			elem->sde[elem->ctr++] = sde;
+			elem->mask = roundup_pow_of_two(elem->ctr) - 1;
+
+			/* Populate the sde map table */
+			sdma_populate_sde_map(elem);
+		}
+
+		if (do_insert) {
+			ret = rhashtable_insert_fast(
+				dd->sdma_rht, &rht_node->node, sdma_rht_params);
+		}
+		if (ret) {
+fail_new:
+			/* completely free node if not inserted yet */
+			if (do_insert)
+				sdma_rht_free(rht_node, dd);
+			dd_dev_err(
+				sde->dd,
+				"Failed to set process to sde affinity for cpu %lu\n",
+				cpu);
+			goto out;
+		}
+
+		cpumask_set_cpu(cpu, new_mask);
+	}
+
+	/* Clean up old mappings */
+	for_each_online_cpu(cpu) {
+		struct sdma_rht_node *rht_node;
+		bool empty = true;
+
+		/* Don't cleanup sdes that are set in the new mask */
+		if (cpumask_test_cpu(cpu, mask))
+			continue;
+
+		rht_node = rhashtable_lookup_fast(dd->sdma_rht, &cpu,
+						  sdma_rht_params);
+		if (!rht_node)
+			continue;
+
+		for (pidx = 0; pidx < dd->num_pports; pidx++) {
+			int i;
+
+			/* Remove mappings for old sde */
+			for (i = 0; i < HFI2_MAX_VLS_SUPPORTED; i++)
+				if (rht_node->port_map[pidx][i])
+					sdma_cleanup_sde_map(
+						rht_node->port_map[pidx][i],
+						sde);
+
+			/* check for populated entries */
+			for (i = 0; i < HFI2_MAX_VLS_SUPPORTED; i++) {
+				if (!rht_node->port_map[pidx][i])
+					continue;
+
+				if (rht_node->port_map[pidx][i]->ctr) {
+					empty = false;
+					break;
+				}
+			}
+		}
+
+		if (empty) {
+			ret = rhashtable_remove_fast(
+				dd->sdma_rht, &rht_node->node, sdma_rht_params);
+			WARN_ON(ret);
+			if (!ret) {
+				synchronize_rcu();
+				sdma_rht_free(rht_node, dd);
+			}
+		}
+	}
+
+	cpumask_copy(&sde->cpu_mask, new_mask);
+out:
+	mutex_unlock(&process_to_sde_mutex);
+out_free:
+	free_cpumask_var(mask);
+	free_cpumask_var(new_mask);
+	return ret ?: strnlen(buf, PAGE_SIZE);
+}
+
+ssize_t hfi2_sdma_get_cpu_to_sde_map(struct sdma_engine *sde, char *buf)
+{
+	mutex_lock(&process_to_sde_mutex);
+	if (cpumask_empty(&sde->cpu_mask))
+		snprintf(buf, PAGE_SIZE, "%s\n", "empty");
+	else
+		cpumap_print_to_pagebuf(true, buf, &sde->cpu_mask);
+	mutex_unlock(&process_to_sde_mutex);
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static void sdma_rht_free(void *ptr, void *arg)
+{
+	struct sdma_rht_node *rht_node = ptr;
+	struct hfi2_devdata *dd = arg;
+	int pidx;
+	int i;
+
+	for (pidx = 0; pidx < dd->num_pports; pidx++) {
+		for (i = 0; i < HFI2_MAX_VLS_SUPPORTED; i++)
+			kfree(rht_node->port_map[pidx][i]);
+	}
+	kfree(rht_node);
+}
+
+/**
+ * hfi2_sdma_seqfile_dump_cpu_list() - debugfs dump the cpu to sdma mappings
+ * @s: seq file
+ * @dd: hfi2_devdata
+ * @cpuid: cpu id
+ *
+ * This routine dumps the process to sde mappings per cpu
+ */
+void hfi2_sdma_seqfile_dump_cpu_list(struct seq_file *s,
+				     struct hfi2_devdata *dd,
+				     unsigned long cpuid)
+{
+	struct sdma_rht_node *rht_node;
+	int i, j;
+	int pidx;
+
+	rht_node =
+		rhashtable_lookup_fast(dd->sdma_rht, &cpuid, sdma_rht_params);
+	if (!rht_node)
+		return;
+
+	for (pidx = 0; pidx < dd->num_pports; pidx++) {
+		if (pidx == 0)
+			seq_printf(s, "cpu%3lu: ", cpuid);
+		else
+			seq_puts(s, "        ");
+		seq_printf(s, "pidx %d: ", pidx);
+
+		for (i = 0; i < HFI2_MAX_VLS_SUPPORTED; i++) {
+			if (!rht_node->port_map[pidx][i] ||
+			    !rht_node->port_map[pidx][i]->ctr)
+				continue;
+
+			seq_printf(s, " vl%d: [", i);
+
+			for (j = 0; j < rht_node->port_map[pidx][i]->ctr; j++) {
+				if (!rht_node->port_map[pidx][i]->sde[j])
+					continue;
+
+				if (j > 0)
+					seq_puts(s, ",");
+
+				seq_printf(s, " sdma%2d",
+					   rht_node->port_map[pidx][i]
+						   ->sde[j]
+						   ->this_idx);
+			}
+			seq_puts(s, " ]");
+		}
+
+		seq_puts(s, "\n");
+	}
+}
+
+/*
+ * Free the indicated map struct
+ */
+static void sdma_map_free(struct sdma_vl_map *m)
+{
+	int i;
+
+	for (i = 0; m && i < m->actual_vls; i++)
+		kfree(m->map[i]);
+	kfree(m);
+}
+
+/*
+ * Handle RCU callback
+ */
+static void sdma_map_rcu_callback(struct rcu_head *list)
+{
+	struct sdma_vl_map *m = container_of(list, struct sdma_vl_map, list);
+
+	sdma_map_free(m);
+}
+
+/**
+ * hfi2_sdma_map_init - called when # vls change
+ * @ppd: port structure
+ * @hfi2_num_vls: number of vls
+ * @vl_engines: per vl engine mapping (optional)
+ *
+ * This routine changes the mapping of VL to SDMA engine.
+ *
+ * vl_engines is used to specify a non-uniform vl/engine loading. NULL
+ * implies auto computing the loading and giving each VLs a uniform
+ * distribution of engines per VL.
+ *
+ * The auto algorithm computes the sde_per_vl and the number of extra
+ * engines.  Any extra engines are added from the last VL on down.
+ *
+ * rcu locking is used here to control access to the mapping fields.
+ *
+ * If either the hfi2_num_vls or num_sdma are non-power of 2, the array sizes
+ * in the struct sdma_vl_map and the struct sdma_map_elem are rounded
+ * up to the next highest power of 2 and the first entry is reused
+ * in a round robin fashion.
+ *
+ * If an error occurs the map change is not done and the mapping is
+ * not changed.
+ */
+int hfi2_sdma_map_init(struct hfi2_pportdata *ppd, u8 hfi2_num_vls,
+		       u8 *vl_engines)
+{
+	struct hfi2_devdata *dd = ppd->dd;
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	int i, j;
+	int extra, sde_per_vl;
+	int engine = dr->first_sdma_engine;
+	int num_sdma = dr->last_sdma_engine - dr->first_sdma_engine;
+	u8 lvl_engines[OPA_MAX_VLS];
+	struct sdma_vl_map *oldmap, *newmap;
+
+	if (!(dd->flags & HFI2_HAS_SEND_DMA))
+		return 0;
+
+	if (!vl_engines) {
+		/* truncate divide */
+		sde_per_vl = num_sdma / hfi2_num_vls;
+		/* extras */
+		extra = num_sdma % hfi2_num_vls;
+		vl_engines = lvl_engines;
+		/* add extras from last vl down */
+		for (i = hfi2_num_vls - 1; i >= 0; i--, extra--)
+			vl_engines[i] = sde_per_vl + (extra > 0 ? 1 : 0);
+	}
+	/* build new map */
+	newmap = kzalloc(sizeof(struct sdma_vl_map) +
+				 roundup_pow_of_two(hfi2_num_vls) *
+					 sizeof(struct sdma_map_elem *),
+			 GFP_KERNEL);
+	if (!newmap)
+		goto bail;
+	newmap->actual_vls = hfi2_num_vls;
+	newmap->vls = roundup_pow_of_two(hfi2_num_vls);
+	newmap->mask = (1 << ilog2(newmap->vls)) - 1;
+	/* initialize back-map */
+	for (i = 0; i < TXE_NUM_SDMA_ENGINES; i++)
+		newmap->engine_to_vl[i] = -1;
+	for (i = 0; i < newmap->vls; i++) {
+		/* save for wrap around */
+		int first_engine = engine;
+
+		if (i < newmap->actual_vls) {
+			int sz = roundup_pow_of_two(vl_engines[i]);
+
+			/* only allocate once */
+			newmap->map[i] = kzalloc(
+				sizeof(struct sdma_map_elem) +
+					sz * sizeof(struct sdma_engine *),
+				GFP_KERNEL);
+			if (!newmap->map[i])
+				goto bail;
+			newmap->map[i]->mask = (1 << ilog2(sz)) - 1;
+			/* assign engines */
+			for (j = 0; j < sz; j++) {
+				newmap->map[i]->sde[j] = &dd->per_sdma[engine];
+				if (++engine >= first_engine + vl_engines[i])
+					/* wrap back to first engine */
+					engine = first_engine;
+			}
+			/* assign back-map */
+			for (j = 0; j < vl_engines[i]; j++)
+				newmap->engine_to_vl[first_engine + j] = i;
+		} else {
+			/* just re-use entry without allocating */
+			newmap->map[i] = newmap->map[i % hfi2_num_vls];
+		}
+		engine = first_engine + vl_engines[i];
+	}
+	/* newmap in hand, save old map */
+	spin_lock_irq(&dd->sde_map_lock);
+	oldmap = rcu_dereference_protected(ppd->sdma_map,
+					   lockdep_is_held(&dd->sde_map_lock));
+
+	/* publish newmap */
+	rcu_assign_pointer(ppd->sdma_map, newmap);
+
+	spin_unlock_irq(&dd->sde_map_lock);
+	/* success, free any old map after grace period */
+	if (oldmap)
+		call_rcu(&oldmap->list, sdma_map_rcu_callback);
+	return 0;
+bail:
+	/* free any partial allocation */
+	sdma_map_free(newmap);
+	return -ENOMEM;
+}
+
+/**
+ * hfi2_sdma_clean - Clean up allocated memory
+ * @dd:          struct hfi2_devdata
+ * @num_engines: num sdma engines
+ *
+ * This routine can be called regardless of the success of
+ * hfi2_sdma_init()
+ */
+void hfi2_sdma_clean(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	size_t i;
+	struct sdma_engine *sde;
+	int pidx;
+
+	if (dd->sdma_pad_dma) {
+		dma_free_coherent(&dd->pcidev->dev, SDMA_PAD,
+				  (void *)dd->sdma_pad_dma, dd->sdma_pad_phys);
+		dd->sdma_pad_dma = NULL;
+		dd->sdma_pad_phys = 0;
+	}
+	if (dd->sdma_heads_dma) {
+		dma_free_coherent(&dd->pcidev->dev, dd->sdma_heads_size,
+				  (void *)dd->sdma_heads_dma,
+				  dd->sdma_heads_phys);
+		dd->sdma_heads_dma = NULL;
+		dd->sdma_heads_phys = 0;
+	}
+	for (i = dr->first_sdma_engine;
+	     dd->per_sdma && i < dr->last_sdma_engine; ++i) {
+		sde = &dd->per_sdma[i];
+
+		sde->head_dma = NULL;
+		sde->head_phys = 0;
+
+		if (sde->descq) {
+			dma_free_coherent(&dd->pcidev->dev,
+					  sde->descq_cnt * sizeof(u64[2]),
+					  sde->descq, sde->descq_phys);
+			sde->descq = NULL;
+			sde->descq_phys = 0;
+		}
+		kvfree(sde->tx_ring);
+		sde->tx_ring = NULL;
+	}
+	kfree(dd->per_sdma);
+	dd->per_sdma = NULL;
+
+	for (pidx = 0; pidx < dd->num_pports; pidx++) {
+		struct hfi2_pportdata *ppd = dd->pport + pidx;
+		struct sdma_vl_map *map;
+
+		if (rcu_access_pointer(ppd->sdma_map)) {
+			spin_lock_irq(&dd->sde_map_lock);
+			map = rcu_access_pointer(ppd->sdma_map);
+			RCU_INIT_POINTER(ppd->sdma_map, NULL);
+			spin_unlock_irq(&dd->sde_map_lock);
+			synchronize_rcu();
+			sdma_map_free(map);
+		}
+	}
+
+	if (dd->sdma_rht) {
+		rhashtable_free_and_destroy(dd->sdma_rht, sdma_rht_free, dd);
+		kfree(dd->sdma_rht);
+		dd->sdma_rht = NULL;
+	}
+}
+
+static u32 sdma_per_engine_credits(struct hfi2_devdata *dd, u32 num_engines)
+{
+	u32 per_sdma_credits =
+		chip_sdma_mem_size(dd) / (num_engines * SDMA_BLOCK_SIZE);
+	u32 limit = jkr_sdma_credits_limit < 0 ?
+			    JKR_DEFAULT_SDMA_CREDITS_LIMIT :
+			    jkr_sdma_credits_limit;
+
+	if (dd->params->chip_type != CHIP_JKR || !jkr_sdma_credits_limit)
+		goto rounddown;
+
+	if (limit > per_sdma_credits) {
+		/*
+		 * Only warn user if they actually set jkr_sdma_credits_limit,
+		 * not if jkr_sdma_credits_limit > per_sdma_credits because of
+		 * num_sdma.
+		 */
+		if (jkr_sdma_credits_limit > 0)
+			dd_dev_info(
+				dd,
+				"Ignoring jkr_sdma_credits_limit (%u) > per_sdma_credits (%u)\n",
+				limit, per_sdma_credits);
+	} else if (limit < 2) {
+		/* Min 2 to make sure JKR doesn't round down to 0 */
+		dd_dev_info(dd, "Ignoring jkr_sdma_credits_limit %u < 2\n",
+			    limit);
+	} else {
+		u32 was = per_sdma_credits;
+
+		per_sdma_credits = limit & ~1;
+		dd_dev_info(
+			dd,
+			"Setting per_sdma_credits to %u (was %u) from jkr_sdma_credits_limit module param\n",
+			per_sdma_credits, was);
+	}
+rounddown:
+	/* non-WFR hardware requires an even number of credits */
+	if (dd->params->chip_type != CHIP_WFR && (per_sdma_credits & 1))
+		per_sdma_credits &= ~1;
+
+	return per_sdma_credits;
+}
+
+/* no-op SDMA descriptor */
+static struct hw_sdma_desc sdma_pad;
+
+/**
+ * hfi2_sdma_init() - called when device probed
+ * @dd: hfi2_devdata
+ *
+ * Initializes each sde and its csrs.
+ * Interrupts are not required to be enabled.
+ *
+ * Returns:
+ * 0 - success, -errno on failure
+ */
+int hfi2_sdma_init(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	unsigned int this_idx;
+	unsigned int start_bit;
+	struct sdma_engine *sde;
+	struct rhashtable *tmp_sdma_rht;
+	u16 descq_cnt;
+	u64 offset;
+	struct hfi2_pportdata *ppd;
+	u32 per_sdma_credits = 0; /* not used on VFs */
+	u32 chip_engines;
+	uint idle_cnt = sdma_idle_cnt;
+	size_t num_engines = dd->num_sdma;
+	int ret = -ENOMEM;
+	int pidx;
+
+	if (prime_sdma_memories(dd))
+		return -EIO;
+
+	if (num_engines == 0)
+		return 0;
+
+	if (!dd->is_vf) {
+		dd_dev_info(dd, "SDMA hfi2_mod_num_sdma: %u\n",
+			    hfi2_mod_num_sdma);
+		dd_dev_info(dd, "SDMA chip_sdma_engines: %u\n",
+			    chip_sdma_engines(dd));
+		dd_dev_info(dd, "SDMA chip_sdma_mem_size: %u\n",
+			    chip_sdma_mem_size(dd));
+
+		per_sdma_credits = sdma_per_engine_credits(dd, num_engines);
+	}
+
+	dd->sdma_threshold = sdma_threshold;
+	dd->pad_sdma_desc = pad_sdma_desc;
+	dd->sdma_align = sdma_align;
+	/* fill in defaults if parameter not specified */
+	if (dd->params->chip_type == CHIP_JKR) {
+		bool amd = boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
+
+		if (amd) {
+			if (dd->sdma_threshold == -1)
+				dd->sdma_threshold = 32;
+			if (dd->pad_sdma_desc == -1)
+				dd->pad_sdma_desc = 32;
+			if (dd->sdma_align == -1)
+				dd->sdma_align = ALIGN_256_HEAD_TAIL;
+		} else {
+			if (dd->sdma_threshold == -1)
+				dd->sdma_threshold = 0;
+			if (dd->pad_sdma_desc == -1)
+				dd->pad_sdma_desc = 0;
+			if (dd->sdma_align == -1)
+				dd->sdma_align = ALIGN_NONE;
+		}
+	} else {
+		if (dd->sdma_threshold == -1)
+			dd->sdma_threshold = 0;
+		if (dd->pad_sdma_desc == -1)
+			dd->pad_sdma_desc = 0;
+		if (dd->sdma_align == -1)
+			dd->sdma_align = ALIGN_NONE;
+	}
+	/* sanity check parameters */
+	switch (dd->pad_sdma_desc) {
+	case 0:
+	case 4:
+	case 8:
+	case 16:
+	case 32:
+		break;
+	default:
+		dd_dev_err(
+			dd,
+			"Invalid pad_sdma_desc parameter %d, setting to zero\n",
+			dd->pad_sdma_desc);
+		dd->pad_sdma_desc = 0;
+		break;
+	}
+	switch (dd->sdma_align) {
+	case ALIGN_NONE:
+	case ALIGN_256_ALL:
+	case ALIGN_256_HEAD_TAIL:
+	case ALIGN_256_TAIL:
+		break;
+	default:
+		dd_dev_err(dd,
+			   "Invalid sdma_align parameter %d, setting to %d\n",
+			   dd->sdma_align, ALIGN_256_HEAD_TAIL);
+		dd->sdma_align = ALIGN_256_HEAD_TAIL;
+		break;
+	}
+	dd_dev_info(dd, "SDMA threshold,pad,align: %d,%d,%d\n",
+		    dd->sdma_threshold, dd->pad_sdma_desc, dd->sdma_align);
+
+	/* set up freeze waitqueue */
+	init_waitqueue_head(&dd->sdma_unfreeze_wq);
+	atomic_set(&dd->sdma_unfreeze_count, 0);
+
+	atomic_set(&dd->sdma_print_tag, 0);
+	descq_cnt = hfi2_sdma_get_descq_cnt();
+	dd_dev_info(dd, "SDMA engines %zu descq_cnt %u\n", num_engines,
+		    descq_cnt);
+
+	/* alloc memory for array of send engines */
+	dd->per_sdma = kcalloc_node(num_engines, sizeof(*dd->per_sdma),
+				    GFP_KERNEL, dd->node);
+	if (!dd->per_sdma)
+		return ret;
+
+	idle_cnt = hfi2_ns_to_cclock(dd, idle_cnt);
+	if (idle_cnt)
+		dd->default_desc1 = SDMA_DESC1_HEAD_TO_HOST_FLAG;
+	else
+		dd->default_desc1 = SDMA_DESC1_INT_REQ_FLAG;
+
+	if (!sdma_desct_intr)
+		sdma_desct_intr = SDMA_DESC_INTR;
+
+	/*
+	 * The driver is coded to assume that all masks fit in a single
+	 * 64-bit interrupt source vector entry.  Enforce that here.
+	 */
+	start_bit = dd->params->is_sdma_start % 64;
+	if (start_bit + (3 * TXE_NUM_SDMA_ENGINES) > 64) {
+		dd_dev_err(dd, "invalid SDMA interrupt masks\n");
+		return -EINVAL;
+	}
+
+	/* Allocate memory for SendDMA descriptor FIFOs */
+	for (this_idx = dr->first_sdma_engine; this_idx < dr->last_sdma_engine;
+	     ++this_idx) {
+		sde = &dd->per_sdma[this_idx];
+		sde->dd = dd;
+		sde->this_idx = this_idx;
+		sde->descq_cnt = descq_cnt;
+		sde->desc_avail = sdma_descq_freecnt(sde);
+		sde->sdma_shift = ilog2(descq_cnt);
+		sde->sdma_mask = (1 << sde->sdma_shift) - 1;
+
+		/* Create a mask specifically for each interrupt source */
+		sde->int_mask = (u64)1 << (0 * TXE_NUM_SDMA_ENGINES + this_idx +
+					   start_bit);
+		sde->progress_mask = (u64)1 << (1 * TXE_NUM_SDMA_ENGINES +
+						this_idx + start_bit);
+		sde->idle_mask = (u64)1 << (2 * TXE_NUM_SDMA_ENGINES +
+					    this_idx + start_bit);
+		/* Create a combined mask to cover all 3 interrupt sources */
+		sde->imask = sde->int_mask | sde->progress_mask |
+			     sde->idle_mask;
+
+		spin_lock_init(&sde->tail_lock);
+		seqlock_init(&sde->head_lock);
+		spin_lock_init(&sde->senddmactrl_lock);
+		spin_lock_init(&sde->flushlist_lock);
+		seqlock_init(&sde->waitlock);
+		/* insure there is always a zero bit */
+		sde->ahg_bits = 0xfffffffe00000000ULL;
+
+		sdma_set_state(sde, sdma_state_s00_hw_down);
+
+		/* set up reference counting */
+		kref_init(&sde->state.kref);
+		init_completion(&sde->state.comp);
+
+		INIT_LIST_HEAD(&sde->flushlist);
+		INIT_LIST_HEAD(&sde->dmawait);
+
+		sde->tail_csr = get_sdma_csr_addr(
+			dd, this_idx, dd->params->send_dma_tail_reg);
+
+		INIT_WORK(&sde->sdma_hw_clean_up_work, sdma_hw_clean_up_worker);
+		INIT_WORK(&sde->sdma_sw_clean_up_work, sdma_sw_clean_up_worker);
+		INIT_WORK(&sde->err_halt_worker, sdma_err_halt_wait);
+		INIT_WORK(&sde->flush_worker, sdma_field_flush);
+
+		sde->progress_check_head = 0;
+
+		timer_setup(&sde->err_progress_check_timer,
+			    sdma_err_progress_check, 0);
+
+		sde->descq = dma_alloc_coherent(&dd->pcidev->dev,
+						descq_cnt * sizeof(u64[2]),
+						&sde->descq_phys, GFP_KERNEL);
+		if (!sde->descq)
+			goto bail;
+		sde->tx_ring = kvzalloc_node(
+			array_size(descq_cnt, sizeof(struct sdma_txreq *)),
+			GFP_KERNEL, dd->node);
+		if (!sde->tx_ring)
+			goto bail;
+	}
+	if (!dd->is_vf) {
+		/* Clear SendDmaCfgMemory on disabled engines */
+		chip_engines = chip_sdma_engines(dd);
+		for (this_idx = num_engines; this_idx < chip_engines;
+		     ++this_idx)
+			write_sdmacfg_csr(dd, this_idx,
+					  dd->params->send_dma_cfg_memory_reg,
+					  0);
+	}
+
+	dd->sdma_heads_size =
+		L1_CACHE_BYTES * (dr->last_sdma_engine - dr->first_sdma_engine);
+	/* Allocate memory for DMA of head registers to memory */
+	dd->sdma_heads_dma =
+		dma_alloc_coherent(&dd->pcidev->dev, dd->sdma_heads_size,
+				   &dd->sdma_heads_phys, GFP_KERNEL);
+	if (!dd->sdma_heads_dma) {
+		dd_dev_err(dd, "failed to allocate SendDMA head memory\n");
+		goto bail;
+	}
+
+	/* Allocate memory for pad */
+	dd->sdma_pad_dma = dma_alloc_coherent(&dd->pcidev->dev, SDMA_PAD,
+					      &dd->sdma_pad_phys, GFP_KERNEL);
+	if (!dd->sdma_pad_dma) {
+		dd_dev_err(dd, "failed to allocate SendDMA pad memory\n");
+		goto bail;
+	}
+
+	/* setup credits for all SDMA engines, only on PF0 (before SiIdx is set) */
+	if (!dd->is_vf) {
+		for (this_idx = 0; this_idx < dd->num_sdma; ++this_idx) {
+			/* dd->per_sdma[this_idx] are not initialized for all engines */
+			write_sdmacfg_csr(
+				dd, this_idx,
+				dd->params->send_dma_cfg_memory_reg,
+				((u64)per_sdma_credits
+				 << SD(MEMORY_SDMA_MEMORY_CNT_SHIFT)) |
+					((u64)(per_sdma_credits * this_idx)
+					 << SD(MEMORY_SDMA_MEMORY_INDEX_SHIFT)));
+		}
+	}
+
+	/* assign each engine to a different cacheline and init registers */
+	offset = 0;
+	for (this_idx = dr->first_sdma_engine; this_idx < dr->last_sdma_engine;
+	     ++this_idx) {
+		sde = &dd->per_sdma[this_idx];
+		sde->head_dma = (void *)dd->sdma_heads_dma + offset;
+		sde->head_phys = dd->sdma_heads_phys + offset;
+		offset += L1_CACHE_BYTES;
+		init_sdma_regs(sde, idle_cnt);
+	}
+	dd->flags |= HFI2_HAS_SEND_DMA;
+	dd->flags |= idle_cnt ? HFI2_HAS_SDMA_TIMEOUT : 0;
+
+	for (pidx = 0; pidx < dd->num_pports; pidx++) {
+		ppd = dd->pport + pidx;
+		ret = hfi2_sdma_map_init(ppd, ppd->vls_operational, NULL);
+		if (ret < 0)
+			goto bail;
+	}
+
+	tmp_sdma_rht = kzalloc(sizeof(*tmp_sdma_rht), GFP_KERNEL);
+	if (!tmp_sdma_rht) {
+		ret = -ENOMEM;
+		goto bail;
+	}
+
+	ret = rhashtable_init(tmp_sdma_rht, &sdma_rht_params);
+	if (ret < 0) {
+		kfree(tmp_sdma_rht);
+		goto bail;
+	}
+
+	dd->sdma_rht = tmp_sdma_rht;
+
+	dd_dev_info(dd, "SDMA num_sdma: %u\n", dd->num_sdma);
+	return 0;
+
+bail:
+	hfi2_sdma_clean(dd);
+	return ret;
+}
+
+/**
+ * hfi2_sdma_all_running() - called when the link goes up
+ * @dd: hfi2_devdata
+ *
+ * This routine moves all engines to the running state.
+ */
+void hfi2_sdma_all_running(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	struct sdma_engine *sde;
+	unsigned int i;
+
+	/* move all engines to running */
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i) {
+		sde = &dd->per_sdma[i];
+		sdma_process_event(sde, sdma_event_e30_go_running);
+	}
+}
+
+/**
+ * hfi2_sdma_start() - called to kick off state processing for all engines
+ * @dd: hfi2_devdata
+ *
+ * This routine is for kicking off the state processing for all required
+ * sdma engines.  Interrupts need to be working at this point.
+ *
+ */
+void hfi2_sdma_start(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	unsigned i;
+	struct sdma_engine *sde;
+
+	/* kick off the engines state processing */
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i) {
+		sde = &dd->per_sdma[i];
+		sdma_process_event(sde, sdma_event_e10_go_hw_start);
+	}
+
+	/* tell all engines to go running */
+	hfi2_sdma_all_running(dd);
+}
+
+/**
+ * hfi2_sdma_exit() - used when module is removed
+ * @dd: hfi2_devdata
+ */
+void hfi2_sdma_exit(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	unsigned this_idx;
+	struct sdma_engine *sde;
+
+	/*
+	 * In the mean time, can only exit engines known to us.
+	 */
+	for (this_idx = dr->first_sdma_engine;
+	     dd->per_sdma && this_idx < dr->last_sdma_engine; ++this_idx) {
+		sde = &dd->per_sdma[this_idx];
+		if (!list_empty(&sde->dmawait))
+			dd_dev_err(dd, "sde %u: dmawait list not empty!\n",
+				   sde->this_idx);
+		sdma_process_event(sde, sdma_event_e00_go_hw_down);
+
+		timer_delete_sync(&sde->err_progress_check_timer);
+
+		cancel_work_sync(&sde->flush_worker);
+		cancel_work_sync(&sde->err_halt_worker);
+		cancel_work_sync(&sde->sdma_hw_clean_up_work);
+
+		/*
+		 * This waits for the state machine to exit so it is not
+		 * necessary to kill the sdma_sw_clean_up_worker to make sure
+		 * it is not running.
+		 */
+		sdma_finalput(&sde->state);
+	}
+}
+
+/*
+ * unmap the indicated descriptor
+ */
+static inline void sdma_unmap_desc(struct hfi2_devdata *dd,
+				   struct sdma_desc *descp, u8 map_type)
+{
+	switch (map_type) {
+	case SDMA_MAP_SINGLE:
+		dma_unmap_single(&dd->pcidev->dev, sdma_mapping_addr(dd, descp),
+				 sdma_mapping_len(dd, descp), DMA_TO_DEVICE);
+		break;
+	case SDMA_MAP_PAGE:
+		dma_unmap_page(&dd->pcidev->dev, sdma_mapping_addr(dd, descp),
+			       sdma_mapping_len(dd, descp), DMA_TO_DEVICE);
+		break;
+	}
+}
+
+/*
+ * return the mode as indicated by the first
+ * descriptor in the tx.
+ */
+static inline u8 ahg_mode(struct sdma_txreq *tx)
+{
+	return (tx->descp[0].qw[1] & SDMA_DESC1_HEADER_MODE_SMASK) >>
+	       SDMA_DESC1_HEADER_MODE_SHIFT;
+}
+
+/**
+ * __hfi2_sdma_txclean() - clean tx of mappings, descp *kmalloc's
+ * @dd: hfi2_devdata for unmapping
+ * @tx: tx request to clean
+ *
+ * This is used in the progress routine to clean the tx or
+ * by the ULP to toss an in-process tx build.
+ *
+ * The code can be called multiple times without issue.
+ *
+ */
+void __hfi2_sdma_txclean(struct hfi2_devdata *dd, struct sdma_txreq *tx)
+{
+	u16 i;
+
+	if (tx->num_desc) {
+		u8 skip = 0, mode = ahg_mode(tx);
+
+		/* unmap first */
+		sdma_unmap_desc(dd, &tx->descp[0], sdma_get_map_type(tx, 0));
+		/* determine number of AHG descriptors to skip */
+		if (mode > SDMA_AHG_APPLY_UPDATE1)
+			skip = mode >> 1;
+		for (i = 1 + skip; i < tx->num_desc; i++)
+			sdma_unmap_desc(dd, &tx->descp[i],
+					sdma_get_map_type(tx, i));
+		tx->num_desc = 0;
+	}
+	kfree(tx->coalesce_buf);
+	tx->coalesce_buf = NULL;
+	/* kmalloc'ed descp */
+	if (unlikely(tx->descp != tx->descs)) {
+		kfree(tx->descp);
+		tx->descp = tx->descs;
+		tx->desc_limit = ARRAY_SIZE(tx->descs);
+	}
+}
+
+static inline u16 sdma_gethead(struct sdma_engine *sde)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	int use_dmahead;
+	u16 hwhead;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx,
+		   slashstrip(__FILE__), __LINE__, __func__);
+#endif
+
+retry:
+	use_dmahead = HFI2_CAP_IS_KSET(USE_SDMA_HEAD) && __sdma_running(sde) &&
+		      (dd->flags & HFI2_HAS_SDMA_TIMEOUT);
+	hwhead = use_dmahead ?
+			 (u16)le64_to_cpu(*sde->head_dma) :
+			 (u16)read_sde_csr(sde, dd->params->send_dma_head_reg);
+
+	if (unlikely(HFI2_CAP_IS_KSET(SDMA_HEAD_CHECK))) {
+		u16 cnt;
+		u16 swtail;
+		u16 swhead;
+		int sane;
+
+		swhead = sde->descq_head & sde->sdma_mask;
+		/* this code is really bad for cache line trading */
+		swtail = READ_ONCE(sde->descq_tail) & sde->sdma_mask;
+		cnt = sde->descq_cnt;
+
+		if (swhead < swtail)
+			/* not wrapped */
+			sane = (hwhead >= swhead) & (hwhead <= swtail);
+		else if (swhead > swtail)
+			/* wrapped around */
+			sane = ((hwhead >= swhead) && (hwhead < cnt)) ||
+			       (hwhead <= swtail);
+		else
+			/* empty */
+			sane = (hwhead == swhead);
+
+		if (unlikely(!sane)) {
+			dd_dev_err(
+				dd,
+				"SDMA(%u) bad head (%s) hwhd=%u swhd=%u swtl=%u cnt=%u\n",
+				sde->this_idx, use_dmahead ? "dma" : "kreg",
+				hwhead, swhead, swtail, cnt);
+			if (use_dmahead) {
+				/* try one more time, using csr */
+				use_dmahead = 0;
+				goto retry;
+			}
+			/* proceed as if no progress */
+			hwhead = swhead;
+		}
+	}
+	return hwhead;
+}
+
+/*
+ * This is called when there are send DMA descriptors that might be
+ * available.
+ *
+ * This is called with head_lock held.
+ */
+static void sdma_desc_avail(struct sdma_engine *sde, uint avail)
+{
+	struct iowait *wait, *nw, *twait;
+	struct iowait *waits[SDMA_WAIT_BATCH_SIZE];
+	uint i, n = 0, seq, tidx = 0;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx,
+		   slashstrip(__FILE__), __LINE__, __func__);
+	dd_dev_err(sde->dd, "avail: %u\n", avail);
+#endif
+
+	do {
+		seq = read_seqbegin(&sde->waitlock);
+		if (!list_empty(&sde->dmawait)) {
+			/* at least one item */
+			write_seqlock(&sde->waitlock);
+			/* Harvest waiters wanting DMA descriptors */
+			list_for_each_entry_safe(wait, nw, &sde->dmawait,
+						 list) {
+				u32 num_desc;
+
+				if (!wait->wakeup)
+					continue;
+				if (n == ARRAY_SIZE(waits))
+					break;
+				iowait_init_priority(wait);
+				num_desc = iowait_get_all_desc(wait);
+				if (num_desc > avail)
+					break;
+				avail -= num_desc;
+				/* Find the top-priority wait memeber */
+				if (n) {
+					twait = waits[tidx];
+					tidx = hfi2_iowait_priority_update_top(
+						wait, twait, n, tidx);
+				}
+				list_del_init(&wait->list);
+				waits[n++] = wait;
+			}
+			write_sequnlock(&sde->waitlock);
+			break;
+		}
+	} while (read_seqretry(&sde->waitlock, seq));
+
+	/* Schedule the top-priority entry first */
+	if (n)
+		waits[tidx]->wakeup(waits[tidx], SDMA_AVAIL_REASON);
+
+	for (i = 0; i < n; i++)
+		if (i != tidx)
+			waits[i]->wakeup(waits[i], SDMA_AVAIL_REASON);
+}
+
+/* head_lock must be held */
+static void sdma_make_progress(struct sdma_engine *sde, u64 status)
+{
+	struct sdma_txreq *txp = NULL;
+	int progress = 0;
+	u16 hwhead, swhead;
+	int idle_check_done = 0;
+
+	hwhead = sdma_gethead(sde);
+
+	/* The reason for some of the complexity of this code is that
+	 * not all descriptors have corresponding txps.  So, we have to
+	 * be able to skip over descs until we wander into the range of
+	 * the next txp on the list.
+	 */
+
+retry:
+	txp = get_txhead(sde);
+	swhead = sde->descq_head & sde->sdma_mask;
+	trace_hfi2_sdma_progress(sde, hwhead, swhead, txp);
+	while (swhead != hwhead) {
+		/* advance head, wrap if needed */
+		swhead = ++sde->descq_head & sde->sdma_mask;
+
+		/* if now past this txp's descs, do the callback */
+		if (txp && txp->next_descq_idx == swhead) {
+			/* remove from list */
+			sde->tx_ring[sde->tx_head++ & sde->sdma_mask] = NULL;
+			complete_tx(sde, txp, SDMA_TXREQ_S_OK);
+			/* see if there is another txp */
+			txp = get_txhead(sde);
+		}
+		trace_hfi2_sdma_progress(sde, hwhead, swhead, txp);
+		progress++;
+	}
+
+	/*
+	 * The SDMA idle interrupt is not guaranteed to be ordered with respect
+	 * to updates to the dma_head location in host memory. The head
+	 * value read might not be fully up to date. If there are pending
+	 * descriptors and the SDMA idle interrupt fired then read from the
+	 * CSR SDMA head instead to get the latest value from the hardware.
+	 * The hardware SDMA head should be read at most once in this invocation
+	 * of sdma_make_progress(..) which is ensured by idle_check_done flag
+	 */
+	if ((status & sde->idle_mask) && !idle_check_done) {
+		u16 swtail;
+
+		swtail = READ_ONCE(sde->descq_tail) & sde->sdma_mask;
+		if (swtail != hwhead) {
+			hwhead = (u16)read_sde_csr(
+				sde, sde->dd->params->send_dma_head_reg);
+			idle_check_done = 1;
+			goto retry;
+		}
+	}
+
+	sde->last_status = status;
+	if (progress)
+		sdma_desc_avail(sde, sdma_descq_freecnt(sde));
+}
+
+bool hfi2_sdma_work_pending(struct sdma_engine *sde)
+{
+	u16 hwhead, swhead;
+
+	hwhead = sdma_gethead(sde);
+	swhead = sde->descq_head & sde->sdma_mask;
+	return (swhead != hwhead);
+}
+
+/*
+ * hfi2_sdma_engine_interrupt() - interrupt handler for engine
+ * @sde: sdma engine
+ * @status: sdma interrupt reason
+ *
+ * Status is a mask of the 3 possible interrupts for this engine.  It will
+ * contain bits _only_ for this SDMA engine.  It will contain at least one
+ * bit, it may contain more.
+ */
+void hfi2_sdma_engine_interrupt(struct sdma_engine *sde, u64 status)
+{
+	unsigned long flags;
+
+	trace_hfi2_sdma_engine_interrupt(sde, status);
+	write_seqlock_irqsave(&sde->head_lock, flags);
+	sdma_set_desc_cnt(sde, sdma_desct_intr);
+	if (status & sde->idle_mask)
+		sde->idle_int_cnt++;
+	else if (status & sde->progress_mask)
+		sde->progress_int_cnt++;
+	else if (status & sde->int_mask)
+		sde->sdma_int_cnt++;
+	sdma_make_progress(sde, status);
+	write_sequnlock_irqrestore(&sde->head_lock, flags);
+}
+
+/**
+ * hfi2_sdma_engine_error() - error handler for engine
+ * @sde: sdma engine
+ * @status: sdma interrupt reason
+ */
+void hfi2_sdma_engine_error(struct sdma_engine *sde, u64 status)
+{
+	unsigned long flags;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) error status 0x%llx state %s\n",
+		   sde->this_idx, (unsigned long long)status,
+		   sdma_state_names[sde->state.current_state]);
+#endif
+	spin_lock_irqsave(&sde->tail_lock, flags);
+	write_seqlock(&sde->head_lock);
+	if (status & ALL_SDMA_ENG_HALT_ERRS)
+		__sdma_process_event(sde, sdma_event_e60_hw_halted);
+	/* only print if not (SDmaHaltErr or a correctable error) */
+	if (status &
+	    ~(SD(ENG_ERR_STATUS_SDMA_HALT_ERR_SMASK) | ALL_SDMA_ENG_COR_ERRS)) {
+		dd_dev_err(sde->dd, "SDMA (%u) engine error: 0x%llx state %s\n",
+			   sde->this_idx, (unsigned long long)status,
+			   sdma_state_names[sde->state.current_state]);
+		dump_sdma_state(sde);
+	}
+	write_sequnlock(&sde->head_lock);
+	spin_unlock_irqrestore(&sde->tail_lock, flags);
+}
+
+static void sdma_sendctrl(struct sdma_engine *sde, unsigned int op)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	u64 set_senddmactrl = 0;
+	u64 clr_senddmactrl = 0;
+	unsigned long flags;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) senddmactrl E=%d I=%d H=%d C=%d\n",
+		   sde->this_idx, (op & SDMA_SENDCTRL_OP_ENABLE) ? 1 : 0,
+		   (op & SDMA_SENDCTRL_OP_INTENABLE) ? 1 : 0,
+		   (op & SDMA_SENDCTRL_OP_HALT) ? 1 : 0,
+		   (op & SDMA_SENDCTRL_OP_CLEANUP) ? 1 : 0);
+#endif
+
+	if (op & SDMA_SENDCTRL_OP_ENABLE)
+		set_senddmactrl |= SD(CTRL_SDMA_ENABLE_SMASK);
+	else
+		clr_senddmactrl |= SD(CTRL_SDMA_ENABLE_SMASK);
+
+	if (op & SDMA_SENDCTRL_OP_INTENABLE)
+		set_senddmactrl |= SD(CTRL_SDMA_INT_ENABLE_SMASK);
+	else
+		clr_senddmactrl |= SD(CTRL_SDMA_INT_ENABLE_SMASK);
+
+	if (op & SDMA_SENDCTRL_OP_HALT)
+		set_senddmactrl |= SD(CTRL_SDMA_HALT_SMASK);
+	else
+		clr_senddmactrl |= SD(CTRL_SDMA_HALT_SMASK);
+
+	spin_lock_irqsave(&sde->senddmactrl_lock, flags);
+
+	sde->p_senddmactrl |= set_senddmactrl;
+	sde->p_senddmactrl &= ~clr_senddmactrl;
+	/* conditionally set SDmaSingleDescriptor */
+	if (sdma_single_descriptor)
+		sde->p_senddmactrl |= (1uLL << 5);
+	/* conditionally set SDmaThresholdEnable - JKR only */
+	if (dd->sdma_threshold) {
+		sde->p_senddmactrl |= (1uLL << 4);
+		write_sde_csr(sde, dd->params->send_dma_priority_thld_reg,
+			      dd->sdma_threshold);
+	}
+
+	if (op & SDMA_SENDCTRL_OP_CLEANUP)
+		write_sde_csr(sde, dd->params->send_dma_ctrl_reg,
+			      sde->p_senddmactrl | SD(CTRL_SDMA_CLEANUP_SMASK));
+	else
+		write_sde_csr(sde, dd->params->send_dma_ctrl_reg,
+			      sde->p_senddmactrl);
+
+	spin_unlock_irqrestore(&sde->senddmactrl_lock, flags);
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	sdma_dumpstate(sde);
+#endif
+}
+
+static void sdma_setlengen(struct sdma_engine *sde)
+{
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx,
+		   slashstrip(__FILE__), __LINE__, __func__);
+#endif
+
+	/*
+	 * Set SendDmaLenGen and clear-then-set the MSB of the generation
+	 * count to enable generation checking and load the internal
+	 * generation counter.
+	 */
+	write_sde_csr(sde, sde->dd->params->send_dma_len_gen_reg,
+		      (sde->descq_cnt / 64) << SD(LEN_GEN_LENGTH_SHIFT));
+	write_sde_csr(sde, sde->dd->params->send_dma_len_gen_reg,
+		      ((sde->descq_cnt / 64) << SD(LEN_GEN_LENGTH_SHIFT)) |
+			      (4ULL << SD(LEN_GEN_GENERATION_SHIFT)));
+}
+
+static inline void sdma_update_tail(struct sdma_engine *sde, u16 tail)
+{
+	/* Commit writes to memory and advance the tail on the chip */
+	smp_wmb(); /* see get_txhead() */
+	writeq(tail, sde->tail_csr);
+}
+
+/*
+ * This is called when changing to state s10_hw_start_up_halt_wait as
+ * a result of send buffer errors or send DMA descriptor errors.
+ */
+static void sdma_hw_start_up(struct sdma_engine *sde)
+{
+	u64 reg;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx,
+		   slashstrip(__FILE__), __LINE__, __func__);
+#endif
+
+	sdma_setlengen(sde);
+	sdma_update_tail(sde, 0); /* Set SendDmaTail */
+	*sde->head_dma = 0;
+
+	reg = SD(ENG_ERR_CLEAR_SDMA_HEADER_REQUEST_FIFO_UNC_ERR_MASK)
+	      << SD(ENG_ERR_CLEAR_SDMA_HEADER_REQUEST_FIFO_UNC_ERR_SHIFT);
+	write_sde_csr(sde, sde->dd->params->send_dma_eng_err_clear_reg, reg);
+}
+
+/*
+ * set_sdma_integrity
+ *
+ * Set the SEND_DMA_CHECK_ENABLE register for send DMA engine 'sde'.
+ */
+static void set_sdma_integrity(struct sdma_engine *sde)
+{
+	struct hfi2_devdata *dd = sde->dd;
+
+	write_sde_csr(sde, SD(CHECK_ENABLE), hfi2_pkt_base_sdma_integrity(dd));
+}
+
+static void init_sdma_regs(struct sdma_engine *sde, uint idle_cnt)
+{
+	u64 opval, opmask;
+	struct hfi2_devdata *dd = sde->dd;
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx,
+		   slashstrip(__FILE__), __LINE__, __func__);
+#endif
+
+	write_sde_csr(sde, dd->params->send_dma_base_addr_reg, sde->descq_phys);
+	sdma_setlengen(sde);
+	sdma_update_tail(sde, 0); /* Set SendDmaTail */
+	write_sde_csr(sde, dd->params->send_dma_reload_cnt_reg, idle_cnt);
+	write_sde_csr(sde, dd->params->send_dma_desc_cnt_reg, 0);
+	write_sde_csr(sde, dd->params->send_dma_head_addr_reg, sde->head_phys);
+	write_sde_csr(sde, dd->params->send_dma_eng_err_mask_reg, ~0ull);
+	if (dd->params->chip_type == CHIP_WFR) {
+		/* SEND_DMA_CHECK_* are WFR only */
+		set_sdma_integrity(sde);
+		opmask = OPCODE_CHECK_MASK_DISABLED;
+		opval = OPCODE_CHECK_VAL_DISABLED;
+		write_sde_csr(sde, SD(CHECK_OPCODE),
+			      (opmask << SEND_CTXT_CHECK_OPCODE_MASK_SHIFT) |
+				      (opval
+				       << SEND_CTXT_CHECK_OPCODE_VALUE_SHIFT));
+	}
+}
+
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+
+#define sdma_dumpstate_helper0(reg)                                     \
+	do {                                                            \
+		csr = hfi2_read_csr(sde->dd, reg);                      \
+		dd_dev_err(sde->dd, "%41s     0x%016llx\n", #reg, csr); \
+	} while (0)
+
+#define sdma_dumpstate_helper(reg)                                  \
+	do {                                                        \
+		csr = read_sde_csr(sde, reg);                       \
+		dd_dev_err(sde->dd, "%41s[%02u] 0x%016llx\n", #reg, \
+			   sde->this_idx, csr);                     \
+	} while (0)
+
+#define sdma_dumpstate_helper1(reg)                                 \
+	do {                                                        \
+		csr = read_sdecfg_csr(sde, reg);                    \
+		dd_dev_err(sde->dd, "%41s[%02u] 0x%016llx\n", #reg, \
+			   sde->this_idx, csr);                     \
+	} while (0)
+
+/* interrupt status */
+static void sdma_dumpstate_int(struct sdma_engine *sde, u32 is_base,
+			       const char *what)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	u32 is_num = is_base + sde->this_idx;
+	u32 reg_off = 8 * (is_num / BITS_PER_REGISTER);
+	u64 reg_mask = BIT_ULL(is_num % BITS_PER_REGISTER);
+	int status;
+	int mask;
+	int blocked;
+
+	status =
+		!!(hfi2_read_csr(dd, dd->params->cce_int_status_reg + reg_off) &
+		   reg_mask);
+	mask = !!(hfi2_read_csr(dd, dd->params->cce_int_mask_reg + reg_off) &
+		  reg_mask);
+	blocked = !!(
+		hfi2_read_csr(dd, dd->params->cce_int_blocked_reg + reg_off) &
+		reg_mask);
+
+	dd_dev_err(dd, "%41s[%02u] status:%d mask:%d blocked:%d\n", what,
+		   sde->this_idx, status, mask, blocked);
+}
+
+void sdma_dumpstate(struct sdma_engine *sde)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	u64 csr;
+
+	sdma_dumpstate_helper(dd->params->send_dma_ctrl_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_status_reg);
+	sdma_dumpstate_helper0(dd->params->send_dma_err_status_reg);
+	sdma_dumpstate_helper0(dd->params->send_dma_err_mask_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_eng_err_status_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_eng_err_mask_reg);
+
+	sdma_dumpstate_int(sde, dd->params->is_sdma_start, "SdmaInt");
+	sdma_dumpstate_int(sde, dd->params->is_sdma_progress_start,
+			   "SdmaProgressInt");
+	sdma_dumpstate_int(sde, dd->params->is_sdma_idle_start, "SdmaIdleInt");
+
+	sdma_dumpstate_helper(dd->params->send_dma_tail_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_head_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_priority_thld_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_idle_cnt_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_reload_cnt_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_desc_cnt_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_desc_fetched_cnt_reg);
+	sdma_dumpstate_helper1(dd->params->send_dma_cfg_memory_reg);
+	sdma_dumpstate_helper0(dd->params->send_dma_engines_reg);
+	sdma_dumpstate_helper0(dd->params->send_dma_mem_size_reg);
+	/* sdma_dumpstate_helper(SEND_EGRESS_SEND_DMA_STATUS);  */
+	sdma_dumpstate_helper(dd->params->send_dma_base_addr_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_len_gen_reg);
+	sdma_dumpstate_helper(dd->params->send_dma_head_addr_reg);
+	if (dd->params->chip_type == CHIP_WFR) {
+		/* SEND_DMA_CHECK_* are WFR only */
+		sdma_dumpstate_helper(SD(CHECK_ENABLE));
+		sdma_dumpstate_helper(SD(CHECK_VL));
+		sdma_dumpstate_helper(SD(CHECK_JOB_KEY));
+		sdma_dumpstate_helper(SD(CHECK_PARTITION_KEY));
+		sdma_dumpstate_helper(SD(CHECK_SLID));
+		sdma_dumpstate_helper(SD(CHECK_OPCODE));
+	}
+}
+#endif
+
+/*
+ * Translate the SDMA descriptor (qw) into human readable text.
+ *
+ * Output is one or two buffers.  Both buffers will always be initialized into
+ * valid strings.  The second buffer is empty if it is an empty string.  Both
+ * buffers are expected to be of length QW_BUF_SIZE.
+ */
+#define QW_BUF_SIZE 128
+static void sdma_qw_strings(struct hfi2_devdata *dd, u32 idx, u64 *qw,
+			    char *buf0, char *buf1)
+{
+	u64 addr;
+	u32 len;
+	u32 gen;
+	char flags[6];
+	bool first = sdma_qw_get(dd, first_desc, qw);
+
+	flags[0] = (qw[1] & SDMA_DESC1_INT_REQ_FLAG) ? 'I' : '-';
+	flags[1] = (qw[1] & SDMA_DESC1_HEAD_TO_HOST_FLAG) ? 'H' : '-';
+	flags[2] = first ? 'F' : '-';
+	flags[3] = sdma_qw_get(dd, last_desc, qw) ? 'L' : '-';
+	flags[4] = 0; /* terminate */
+	addr = sdma_qw_get(dd, phy_addr, qw);
+	gen = (qw[1] >> SDMA_DESC1_GENERATION_SHIFT) &
+	      SDMA_DESC1_GENERATION_MASK;
+	len = sdma_qw_get(dd, byte_count, qw);
+	scnprintf(
+		buf0, QW_BUF_SIZE,
+		"desc[%u]: flags:%s addr:0x%016llx gen:%u len:%u d0:0x%016llx d1:0x%016llx",
+		idx, flags, addr, gen, len, qw[0], qw[1]);
+	if (first) {
+		scnprintf(buf1, QW_BUF_SIZE, "aidx:%u amode:%u alen:%u",
+			  (u32)((qw[1] & SDMA_DESC1_HEADER_INDEX_SMASK) >>
+				SDMA_DESC1_HEADER_INDEX_SHIFT),
+			  (u32)((qw[1] & SDMA_DESC1_HEADER_MODE_SMASK) >>
+				SDMA_DESC1_HEADER_MODE_SHIFT),
+			  (u32)((qw[1] & SDMA_DESC1_HEADER_DWS_SMASK) >>
+				SDMA_DESC1_HEADER_DWS_SHIFT));
+	} else {
+		buf1[0] = 0;
+	}
+}
+
+/* interrupt deferred SDMA descriptor dump information */
+struct sdma_print_info {
+	struct work_struct sdma_dump_work;
+	struct sdma_engine *sde; /* only use for auxiliary info */
+	struct hw_sdma_desc *descs;
+	u32 tag;
+	u16 head;
+	u16 tail;
+};
+
+/* show descriptors using information from argument sdi, not sdi->sde */
+static void show_tagged_sdma_descriptors(const struct sdma_print_info *sdi)
+{
+	char buf0[QW_BUF_SIZE];
+	char buf1[QW_BUF_SIZE];
+	struct sdma_engine *sde = sdi->sde;
+	struct hfi2_devdata *dd = sde->dd;
+	struct hw_sdma_desc *descqp;
+	u64 desc[2];
+	u16 head = sdi->head;
+
+	/* print info for each entry in the descriptor queue */
+	while (head != sdi->tail) {
+		descqp = &sdi->descs[head];
+		desc[0] = le64_to_cpu(descqp->qw[0]);
+		desc[1] = le64_to_cpu(descqp->qw[1]);
+
+		sdma_qw_strings(dd, head, desc, buf0, buf1);
+		dd_dev_err(dd, "[desc %u] SDMA %s\n", sdi->tag, buf0);
+		if (buf1[0])
+			dd_dev_err(dd, "[desc %u]\t%s\n", sdi->tag, buf1);
+		head = (head + 1) & sde->sdma_mask;
+	}
+}
+
+static void sdma_dump_worker(struct work_struct *work)
+{
+	struct sdma_print_info *sdi =
+		container_of(work, struct sdma_print_info, sdma_dump_work);
+
+	show_tagged_sdma_descriptors(sdi);
+	kfree(sdi->descs);
+	kfree(sdi);
+}
+
+static void dump_sdma_state(struct sdma_engine *sde)
+{
+	char tag_info[64];
+	struct hfi2_devdata *dd = sde->dd;
+	struct sdma_print_info local_sdi;
+	struct sdma_print_info *sdi;
+	struct hw_sdma_desc *descs;
+	int tag;
+	u16 head, tail, used, avail;
+
+	head = sde->descq_head & sde->sdma_mask;
+	tail = sde->descq_tail & sde->sdma_mask;
+	used = sdma_descq_inprocess(sde);
+	avail = sdma_descq_freecnt(sde);
+
+	if (used) {
+		tag = atomic_fetch_inc(&dd->sdma_print_tag);
+		snprintf(tag_info, sizeof(tag_info),
+			 ", descriptor print prefix \"[desc %d]\"", tag);
+	} else {
+		tag = 0;
+		tag_info[0] = 0;
+	}
+
+	dd_dev_err(
+		dd,
+		"SDMA (%u) descq_head %u, descq_tail %u, used %u, avail %u, FLE %d%s\n",
+		sde->this_idx, head, tail, used, avail,
+		!list_empty(&sde->flushlist), tag_info);
+	if (used == 0)
+		return;
+
+	/* print descriptors - either immediately or delayed */
+	if (in_interrupt()) {
+		size_t size = sizeof(struct hw_sdma_desc) * sde->descq_cnt;
+
+		sdi = kmalloc_obj(sdi, GFP_ATOMIC);
+		descs = kmalloc(size, GFP_ATOMIC);
+		if (!sdi || !descs) {
+			kfree(sdi);
+			kfree(descs);
+			return;
+		}
+
+		INIT_WORK(&sdi->sdma_dump_work, sdma_dump_worker);
+		memcpy(descs, sde->descq, size);
+	} else {
+		sdi = &local_sdi;
+		descs = sde->descq;
+		memset(&sdi->sdma_dump_work, 0, sizeof(sdi->sdma_dump_work));
+	}
+
+	sdi->sde = sde;
+	sdi->descs = descs;
+	sdi->tag = tag;
+	sdi->head = head;
+	sdi->tail = tail;
+
+	if (in_interrupt())
+		queue_work(dd->hfi2_wq, &sdi->sdma_dump_work);
+	else
+		show_tagged_sdma_descriptors(sdi);
+}
+
+#define SDE_FMT \
+	"SDE %u CPU %d STE %s C 0x%llx S 0x%016llx E 0x%llx T(HW) 0x%llx T(SW) 0x%x H(HW) 0x%llx H(SW) 0x%x H(D) 0x%llx DM 0x%llx GL 0x%llx R 0x%llx LIS 0x%llx AHGI 0x%llx TXT %u TXH %u DT %u DH %u FLNE %d DQF %u SLC 0x%llx\n"
+/**
+ * hfi2_sdma_seqfile_dump_sde() - debugfs dump of sde
+ * @s: seq file
+ * @sde: send dma engine to dump
+ *
+ * This routine dumps the sde to the indicated seq file.
+ */
+void hfi2_sdma_seqfile_dump_sde(struct seq_file *s, struct sdma_engine *sde)
+{
+	char buf0[QW_BUF_SIZE];
+	char buf1[QW_BUF_SIZE];
+	struct hfi2_devdata *dd = sde->dd;
+	unsigned long long check_slid;
+	u16 head, tail;
+	struct hw_sdma_desc *descqp;
+	u64 desc[2];
+
+	head = sde->descq_head & sde->sdma_mask;
+	tail = READ_ONCE(sde->descq_tail) & sde->sdma_mask;
+	/* SEND_DMA_CHECK_SLID is only available on WFR */
+	check_slid = dd->params->chip_type == CHIP_WFR ?
+			     read_sde_csr(sde, SEND_DMA_CHECK_SLID) :
+			     0ULL;
+	seq_printf(
+		s, SDE_FMT, sde->this_idx, sde->cpu,
+		sdma_state_name(sde->state.current_state),
+		(unsigned long long)read_sde_csr(sde,
+						 dd->params->send_dma_ctrl_reg),
+		(unsigned long long)read_sde_csr(
+			sde, dd->params->send_dma_status_reg),
+		(unsigned long long)read_sde_csr(
+			sde, dd->params->send_dma_eng_err_status_reg),
+		(unsigned long long)read_sde_csr(sde,
+						 dd->params->send_dma_tail_reg),
+		tail,
+		(unsigned long long)read_sde_csr(sde,
+						 dd->params->send_dma_head_reg),
+		head, (unsigned long long)le64_to_cpu(*sde->head_dma),
+		(unsigned long long)read_sdecfg_csr(
+			sde, dd->params->send_dma_cfg_memory_reg),
+		(unsigned long long)read_sde_csr(
+			sde, dd->params->send_dma_len_gen_reg),
+		(unsigned long long)read_sde_csr(
+			sde, dd->params->send_dma_reload_cnt_reg),
+		(unsigned long long)sde->last_status,
+		(unsigned long long)sde->ahg_bits, sde->tx_tail, sde->tx_head,
+		sde->descq_tail, sde->descq_head, !list_empty(&sde->flushlist),
+		sde->descq_full_count, check_slid);
+
+	/* print info for each entry in the descriptor queue */
+	while (head != tail) {
+		descqp = &sde->descq[head];
+		desc[0] = le64_to_cpu(descqp->qw[0]);
+		desc[1] = le64_to_cpu(descqp->qw[1]);
+
+		sdma_qw_strings(dd, head, desc, buf0, buf1);
+		seq_printf(s, "\t%s\n", buf0);
+		if (buf1[0])
+			seq_printf(s, "\t\t%s\n", buf1);
+
+		head = (head + 1) & sde->sdma_mask;
+	}
+}
+
+/*
+ * Add the generation number into qw1 and return the updated value.
+ * The incoming value of the field is expected to be zero.
+ */
+static inline u64 add_gen(struct sdma_engine *sde, u64 qw1)
+{
+	u64 generation = (sde->descq_tail >> sde->sdma_shift) &
+			 SDMA_DESC1_GENERATION_MASK;
+
+	return qw1 | (generation << SDMA_DESC1_GENERATION_SHIFT);
+}
+
+/*
+ * This routine submits the indicated tx
+ *
+ * Space has already been guaranteed and
+ * tail side of ring is locked.
+ *
+ * The hardware tail update is done
+ * in the caller and that is facilitated
+ * by returning the new tail.
+ *
+ * There is special case logic for ahg
+ * to not add the generation number for
+ * up to 2 descriptors that follow the
+ * first descriptor.
+ *
+ */
+static inline u16 _submit_tx(struct sdma_engine *sde, struct sdma_txreq *tx,
+			     u8 pad)
+{
+	int i;
+	u16 tail;
+	struct sdma_desc *descp = tx->descp;
+	u8 skip = 0, mode = ahg_mode(tx);
+
+	tail = sde->descq_tail & sde->sdma_mask;
+	sde->descq[tail].qw[0] = cpu_to_le64(descp->qw[0]);
+	sde->descq[tail].qw[1] = cpu_to_le64(add_gen(sde, descp->qw[1]));
+	trace_hfi2_sdma_descriptor(sde, descp->qw, tail, &sde->descq[tail]);
+	tail = ++sde->descq_tail & sde->sdma_mask;
+	descp++;
+	if (mode > SDMA_AHG_APPLY_UPDATE1)
+		skip = mode >> 1;
+	for (i = 1; i < tx->num_desc; i++, descp++) {
+		u64 qw[2];
+
+		if (i == tx->num_desc - 1 && pad) {
+			u16 j;
+
+			qw[0] = sdma_pad.qw[0];
+			for (j = 0; j < pad; j++) {
+				qw[1] = add_gen(sde, sdma_pad.qw[1]);
+				sde->descq[tail].qw[0] = cpu_to_le64(qw[0]);
+				sde->descq[tail].qw[1] = cpu_to_le64(qw[1]);
+				trace_hfi2_sdma_descriptor(sde, qw, tail,
+							   &sde->descq[tail]);
+				tail = ++sde->descq_tail & sde->sdma_mask;
+			}
+		}
+
+		qw[0] = descp->qw[0];
+		if (skip) {
+			/* edits don't have generation */
+			qw[1] = descp->qw[1];
+			skip--;
+		} else {
+			/* replace generation with real one for non-edits */
+			qw[1] = add_gen(sde, descp->qw[1]);
+		}
+		sde->descq[tail].qw[0] = cpu_to_le64(qw[0]);
+		sde->descq[tail].qw[1] = cpu_to_le64(qw[1]);
+		trace_hfi2_sdma_descriptor(sde, qw, tail, &sde->descq[tail]);
+		tail = ++sde->descq_tail & sde->sdma_mask;
+	}
+	tx->next_descq_idx = tail;
+#ifdef CONFIG_HFI2_DEBUG_SDMA_ORDER
+	tx->sn = sde->tail_sn++;
+	trace_hfi2_sdma_in_sn(sde, tx->sn);
+	WARN_ON_ONCE(sde->tx_ring[sde->tx_tail & sde->sdma_mask]);
+#endif
+	sde->tx_ring[sde->tx_tail++ & sde->sdma_mask] = tx;
+	sde->desc_avail -= tx->num_desc + pad;
+	return tail;
+}
+
+static inline u16 submit_tx(struct sdma_engine *sde, struct sdma_txreq *tx)
+{
+	if (sde->dd->pad_sdma_desc)
+		trace_hfi2_sdma_pad(sde->this_idx, 1, sde->dd->pad_sdma_desc,
+				    tx->num_desc, tx->num_pad);
+	return _submit_tx(sde, tx, tx->num_pad);
+}
+
+/*
+ * Check for progress
+ */
+static int sdma_check_progress(struct sdma_engine *sde,
+			       struct iowait_work *wait, struct sdma_txreq *tx,
+			       bool pkts_sent)
+{
+	int ret;
+
+	sde->desc_avail = sdma_descq_freecnt(sde);
+	if (tx->num_desc + tx->num_pad <= sde->desc_avail)
+		return -EAGAIN;
+	/* pulse the head_lock */
+	if (wait && iowait_ioww_to_iow(wait)->sleep) {
+		unsigned int seq;
+
+		seq = raw_seqcount_begin(
+			(const seqcount_t *)&sde->head_lock.seqcount);
+		ret = wait->iow->sleep(sde, wait, tx, seq, pkts_sent);
+		if (ret == -EAGAIN)
+			sde->desc_avail = sdma_descq_freecnt(sde);
+	} else {
+		ret = -EBUSY;
+	}
+	return ret;
+}
+
+/**
+ * hfi2_sdma_send_txreq() - submit a tx req to ring
+ * @sde: sdma engine to use
+ * @wait: SE wait structure to use when full (may be NULL)
+ * @tx: sdma_txreq to submit
+ * @pkts_sent: has any packet been sent yet?
+ *
+ * The call submits the tx into the ring.  If a iowait structure is non-NULL
+ * the packet will be queued to the list in wait.
+ *
+ * Return:
+ * 0            - success (submittted to ring or silently dropped)
+ * -EINVAL      - sdma_txreq incomplete
+ * -EBUSY       - no space in ring (wait == NULL)
+ * -EIOCBQUEUED - tx queued to iowait
+ * -ECOMM       - bad sdma state, tx queued to flush list
+ */
+int hfi2_sdma_send_txreq(struct sdma_engine *sde, struct iowait_work *wait,
+			 struct sdma_txreq *tx, bool pkts_sent)
+{
+	int ret = 0;
+	u16 tail;
+	unsigned long flags;
+
+	/* user should have supplied entire packet */
+	if (unlikely(tx->tlen))
+		return -EINVAL;
+	tx->wait = iowait_ioww_to_iow(wait);
+	spin_lock_irqsave(&sde->tail_lock, flags);
+retry:
+	if (unlikely(!__sdma_running(sde)))
+		goto unlock_noconn;
+	if (unlikely(tx->num_desc + tx->num_pad > sde->desc_avail))
+		goto nodesc;
+	tail = submit_tx(sde, tx);
+	if (wait)
+		iowait_sdma_inc(iowait_ioww_to_iow(wait));
+	sdma_update_tail(sde, tail);
+unlock:
+	spin_unlock_irqrestore(&sde->tail_lock, flags);
+	return ret;
+unlock_noconn:
+	if (wait)
+		iowait_sdma_inc(iowait_ioww_to_iow(wait));
+	tx->next_descq_idx = 0;
+#ifdef CONFIG_HFI2_DEBUG_SDMA_ORDER
+	tx->sn = sde->tail_sn++;
+	trace_hfi2_sdma_in_sn(sde, tx->sn);
+#endif
+	spin_lock(&sde->flushlist_lock);
+	list_add_tail(&tx->list, &sde->flushlist);
+	spin_unlock(&sde->flushlist_lock);
+	iowait_inc_wait_count(wait, tx->num_desc);
+	queue_work_on(sde->cpu, system_highpri_wq, &sde->flush_worker);
+	ret = -ECOMM;
+	goto unlock;
+nodesc:
+	ret = sdma_check_progress(sde, wait, tx, pkts_sent);
+	if (ret == -EAGAIN) {
+		ret = 0;
+		goto retry;
+	}
+	sde->descq_full_count++;
+	goto unlock;
+}
+
+/**
+ * hfi2_sdma_send_txlist() - submit a list of tx req to ring
+ * @sde: sdma engine to use
+ * @wait: SE wait structure to use when full (may be NULL)
+ * @tx_list: list of sdma_txreqs to submit
+ * @count_out: pointer to a u16 which, after return will contain the total number of
+ *             sdma_txreqs removed from the tx_list. This will include sdma_txreqs
+ *             whose SDMA descriptors are submitted to the ring and the sdma_txreqs
+ *             which are added to SDMA engine flush list if the SDMA engine state is
+ *             not running.
+ *
+ * The call submits the list into the ring.
+ *
+ * If the iowait structure is non-NULL and not equal to the iowait list
+ * the unprocessed part of the list  will be appended to the list in wait.
+ *
+ * In all cases, the tx_list will be updated so the head of the tx_list is
+ * the list of descriptors that have yet to be transmitted.
+ *
+ * The intent of this call is to provide a more efficient
+ * way of submitting multiple packets to SDMA while holding the tail
+ * side locking.
+ *
+ * Return:
+ * 0 - Success,
+ * -EINVAL - sdma_txreq incomplete, -EBUSY - no space in ring (wait == NULL)
+ * -EIOCBQUEUED - tx queued to iowait, -ECOMM bad sdma state
+ */
+int hfi2_sdma_send_txlist(struct sdma_engine *sde, struct iowait_work *wait,
+			  struct list_head *tx_list, u16 *count_out)
+{
+	struct hfi2_devdata *dd = sde->dd;
+	struct sdma_txreq *tx, *tx_next;
+	int ret = 0;
+	unsigned long flags;
+	u16 tail = INVALID_TAIL;
+	u16 desc_avail;
+	u16 cur_descs;
+	u8 pkts;
+	u32 submit_count = 0, flush_count = 0, total_count;
+
+	spin_lock_irqsave(&sde->tail_lock, flags);
+retry:
+	cur_descs = 0;
+	pkts = 0;
+	desc_avail = sde->desc_avail;
+	if (dd->pad_sdma_desc)
+		desc_avail = round_down(desc_avail, dd->pad_sdma_desc);
+
+	list_for_each_entry_safe(tx, tx_next, tx_list, list) {
+		u8 pad = 0;
+
+		tx->wait = iowait_ioww_to_iow(wait);
+		if (unlikely(!__sdma_running(sde)))
+			goto unlock_noconn;
+		if (unlikely(tx->num_desc > desc_avail))
+			goto nodesc;
+		if (unlikely(tx->tlen)) {
+			ret = -EINVAL;
+			goto update_tail;
+		}
+		list_del_init(&tx->list);
+		cur_descs += tx->num_desc;
+		desc_avail -= tx->num_desc;
+		pkts++;
+
+		/*
+		 * Only add padding descriptors to last packet before tail
+		 * update so that tail update is a dd->pad_sdma_desc multiple.
+		 *
+		 * Look ahead to see if the next packet will cause a tail
+		 * update. If so, this packet is the last packet, so pad out
+		 * its descriptors as necessary. The next packet will start its
+		 * own update block.
+		 */
+		if (dd->pad_sdma_desc) {
+			bool last = false;
+
+			/* No more packets */
+			if (list_empty(tx_list))
+				last = true;
+			/* Next packet will goto nodesc, then update_tail */
+			else if (tx_next->num_desc > desc_avail)
+				last = true;
+			/* Next packet will goto update_tail */
+			else if (unlikely(tx_next->tlen))
+				last = true;
+			else if (((submit_count + 1) &
+				  SDMA_TAIL_UPDATE_THRESH) == 0)
+				last = true;
+
+			if (last) {
+				pad = (u8)(round_up(cur_descs,
+						    dd->pad_sdma_desc) -
+					   cur_descs);
+				trace_hfi2_sdma_pad(sde->this_idx, pkts,
+						    dd->pad_sdma_desc,
+						    cur_descs, pad);
+				cur_descs += pad;
+				desc_avail -= pad;
+			}
+		}
+
+		tail = _submit_tx(sde, tx, pad);
+		submit_count++;
+		if (tail != INVALID_TAIL &&
+		    (submit_count & SDMA_TAIL_UPDATE_THRESH) == 0) {
+			sdma_update_tail(sde, tail);
+			tail = INVALID_TAIL;
+		}
+	}
+update_tail:
+	total_count = submit_count + flush_count;
+	if (wait) {
+		iowait_sdma_add(iowait_ioww_to_iow(wait), total_count);
+		iowait_starve_clear(submit_count > 0, iowait_ioww_to_iow(wait));
+	}
+	if (tail != INVALID_TAIL)
+		sdma_update_tail(sde, tail);
+	spin_unlock_irqrestore(&sde->tail_lock, flags);
+	*count_out = total_count;
+	return ret;
+unlock_noconn:
+	spin_lock(&sde->flushlist_lock);
+	list_for_each_entry_safe(tx, tx_next, tx_list, list) {
+		tx->wait = iowait_ioww_to_iow(wait);
+		list_del_init(&tx->list);
+		tx->next_descq_idx = 0;
+#ifdef CONFIG_HFI2_DEBUG_SDMA_ORDER
+		tx->sn = sde->tail_sn++;
+		trace_hfi2_sdma_in_sn(sde, tx->sn);
+#endif
+		list_add_tail(&tx->list, &sde->flushlist);
+		flush_count++;
+		iowait_inc_wait_count(wait, tx->num_desc);
+	}
+	spin_unlock(&sde->flushlist_lock);
+	queue_work_on(sde->cpu, system_highpri_wq, &sde->flush_worker);
+	ret = -ECOMM;
+	goto update_tail;
+nodesc:
+	ret = sdma_check_progress(sde, wait, tx, submit_count > 0);
+	if (ret == -EAGAIN) {
+		ret = 0;
+		goto retry;
+	}
+	sde->descq_full_count++;
+	goto update_tail;
+}
+
+static void sdma_process_event(struct sdma_engine *sde, enum sdma_events event)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sde->tail_lock, flags);
+	write_seqlock(&sde->head_lock);
+
+	__sdma_process_event(sde, event);
+
+	if (sde->state.current_state == sdma_state_s99_running)
+		sdma_desc_avail(sde, sdma_descq_freecnt(sde));
+
+	write_sequnlock(&sde->head_lock);
+	spin_unlock_irqrestore(&sde->tail_lock, flags);
+}
+
+static void __sdma_process_event(struct sdma_engine *sde,
+				 enum sdma_events event)
+{
+	struct sdma_state *ss = &sde->state;
+	int need_progress = 0;
+
+	/* CONFIG SDMA temporary */
+#ifdef CONFIG_HFI2_SDMA_VERBOSITY
+	dd_dev_err(sde->dd, "CONFIG SDMA(%u) [%s] %s\n", sde->this_idx,
+		   sdma_state_names[ss->current_state],
+		   sdma_event_names[event]);
+#endif
+
+	switch (ss->current_state) {
+	case sdma_state_s00_hw_down:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			break;
+		case sdma_event_e30_go_running:
+			/*
+			 * If down, but running requested (usually result
+			 * of link up, then we need to start up.
+			 * This can happen when hw down is requested while
+			 * bringing the link up with traffic active on
+			 * 7220, e.g.
+			 */
+			ss->go_s99_running = 1;
+			fallthrough; /* and start dma engine */
+		case sdma_event_e10_go_hw_start:
+			/* This reference means the state machine is started */
+			sdma_get(&sde->state);
+			sdma_set_state(sde,
+				       sdma_state_s10_hw_start_up_halt_wait);
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e40_sw_cleaned:
+			sdma_sw_tear_down(sde);
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s10_hw_start_up_halt_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			sdma_sw_tear_down(sde);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			sdma_set_state(sde,
+				       sdma_state_s15_hw_start_up_clean_wait);
+			sdma_start_hw_clean_up(sde);
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			schedule_work(&sde->err_halt_worker);
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s15_hw_start_up_clean_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			sdma_sw_tear_down(sde);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			sdma_hw_start_up(sde);
+			sdma_set_state(sde, ss->go_s99_running ?
+						    sdma_state_s99_running :
+						    sdma_state_s20_idle);
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s20_idle:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			sdma_sw_tear_down(sde);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			sdma_set_state(sde, sdma_state_s99_running);
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			sdma_set_state(sde, sdma_state_s50_hw_halt_wait);
+			schedule_work(&sde->err_halt_worker);
+			break;
+		case sdma_event_e70_go_idle:
+			break;
+		case sdma_event_e85_link_down:
+		case sdma_event_e80_hw_freeze:
+			sdma_set_state(sde, sdma_state_s80_hw_freeze);
+			atomic_dec(&sde->dd->sdma_unfreeze_count);
+			wake_up_interruptible(&sde->dd->sdma_unfreeze_wq);
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s30_sw_clean_up_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			sdma_set_state(sde, sdma_state_s40_hw_clean_up_wait);
+			sdma_start_hw_clean_up(sde);
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s40_hw_clean_up_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			sdma_hw_start_up(sde);
+			sdma_set_state(sde, ss->go_s99_running ?
+						    sdma_state_s99_running :
+						    sdma_state_s20_idle);
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s50_hw_halt_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			sdma_set_state(sde, sdma_state_s30_sw_clean_up_wait);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			schedule_work(&sde->err_halt_worker);
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s60_idle_halt_wait:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			sdma_set_state(sde, sdma_state_s30_sw_clean_up_wait);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			schedule_work(&sde->err_halt_worker);
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s80_hw_freeze:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			sdma_set_state(sde, sdma_state_s82_freeze_sw_clean);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s82_freeze_sw_clean:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			ss->go_s99_running = 1;
+			break;
+		case sdma_event_e40_sw_cleaned:
+			/* notify caller this engine is done cleaning */
+			atomic_dec(&sde->dd->sdma_unfreeze_count);
+			wake_up_interruptible(&sde->dd->sdma_unfreeze_wq);
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			break;
+		case sdma_event_e70_go_idle:
+			ss->go_s99_running = 0;
+			break;
+		case sdma_event_e80_hw_freeze:
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			sdma_hw_start_up(sde);
+			sdma_set_state(sde, ss->go_s99_running ?
+						    sdma_state_s99_running :
+						    sdma_state_s20_idle);
+			break;
+		case sdma_event_e85_link_down:
+			break;
+		case sdma_event_e90_sw_halted:
+			break;
+		}
+		break;
+
+	case sdma_state_s99_running:
+		switch (event) {
+		case sdma_event_e00_go_hw_down:
+			sdma_set_state(sde, sdma_state_s00_hw_down);
+			queue_work(sde->dd->hfi2_wq,
+				   &sde->sdma_sw_clean_up_work);
+			break;
+		case sdma_event_e10_go_hw_start:
+			break;
+		case sdma_event_e15_hw_halt_done:
+			break;
+		case sdma_event_e25_hw_clean_up_done:
+			break;
+		case sdma_event_e30_go_running:
+			break;
+		case sdma_event_e40_sw_cleaned:
+			break;
+		case sdma_event_e50_hw_cleaned:
+			break;
+		case sdma_event_e60_hw_halted:
+			need_progress = 1;
+			sdma_err_progress_check_schedule(sde);
+			fallthrough;
+		case sdma_event_e90_sw_halted:
+			/*
+			 * SW initiated halt does not perform engines
+			 * progress check
+			 */
+			sdma_set_state(sde, sdma_state_s50_hw_halt_wait);
+			schedule_work(&sde->err_halt_worker);
+			break;
+		case sdma_event_e70_go_idle:
+			sdma_set_state(sde, sdma_state_s60_idle_halt_wait);
+			break;
+		case sdma_event_e85_link_down:
+			ss->go_s99_running = 0;
+			fallthrough;
+		case sdma_event_e80_hw_freeze:
+			sdma_set_state(sde, sdma_state_s80_hw_freeze);
+			atomic_dec(&sde->dd->sdma_unfreeze_count);
+			wake_up_interruptible(&sde->dd->sdma_unfreeze_wq);
+			break;
+		case sdma_event_e81_hw_frozen:
+			break;
+		case sdma_event_e82_hw_unfreeze:
+			break;
+		}
+		break;
+	}
+
+	ss->last_event = event;
+	if (need_progress)
+		sdma_make_progress(sde, 0);
+}
+
+/*
+ * _hfi2_extend_sdma_tx_descs() - helper to extend txreq
+ *
+ * Called when the initial nominal allocation of descriptors in the sdma_txreq
+ * is exhausted.
+ *
+ * The code will bump the allocation up to the max of MAX_DESC (64) descriptors.
+ * There doesn't seem much point in an interim step.  The last descriptor or
+ * two is reserved for coalesce buffer and possible pad in order to support
+ * cases where input packet has >MAX_DESC iovecs.
+ */
+int _hfi2_extend_sdma_tx_descs(struct hfi2_devdata *dd, struct sdma_txreq *tx)
+{
+	int i;
+	struct sdma_desc *descp;
+
+	descp = kmalloc_array(MAX_DESC, sizeof(struct sdma_desc), GFP_ATOMIC);
+	if (!descp) {
+		__hfi2_sdma_txclean(dd, tx);
+		return -ENOMEM;
+	}
+	tx->descp = descp;
+	tx->desc_limit = MAX_DESC;
+
+	/* copy ones already built */
+	for (i = 0; i < tx->num_desc; i++)
+		tx->descp[i] = tx->descs[i];
+	return 0;
+}
+
+/*
+ * hfi2_do_coalesce() - coalesce this tx buffer
+ *
+ * Called when the data in this buffer must be coalesced.
+ *
+ * If needed, create the coalesce buffer.  Copy the current data into the
+ * coalesce buffer.  If this is the last data, dmamap the buffer and add
+ * its descriptor.
+ *
+ * Return:
+ * <0 - error
+ *  0 - success
+ */
+int hfi2_do_coalesce(struct hfi2_devdata *dd, struct sdma_txreq *tx, int type,
+		     void *kvaddr, struct page *page, unsigned long offset,
+		     u16 len)
+{
+	int pad_len, rval;
+	dma_addr_t addr;
+
+	if (!tx->coalesce_buf) {
+		/* allocate coalesce buffer with space for padding */
+		tx->coalesce_buf = kmalloc(tx->tlen + sizeof(u32), GFP_ATOMIC);
+		if (!tx->coalesce_buf) {
+			rval = -ENOMEM;
+			goto fail;
+		}
+		tx->coalesce_idx = 0;
+	}
+
+	if (type == SDMA_MAP_NONE) {
+		rval = -EINVAL;
+		goto fail;
+	}
+
+	if (type == SDMA_MAP_PAGE) {
+		kvaddr = kmap_local_page(page);
+		kvaddr += offset;
+	} else if (WARN_ON(!kvaddr)) {
+		rval = -EINVAL;
+		goto fail;
+	}
+
+	memcpy(tx->coalesce_buf + tx->coalesce_idx, kvaddr, len);
+	tx->coalesce_idx += len;
+	if (type == SDMA_MAP_PAGE)
+		kunmap_local(kvaddr);
+
+	/* if there is more data, return */
+	if (tx->tlen != tx->coalesce_idx)
+		return 0;
+
+	/* Whole packet is received; add any padding */
+	pad_len = pad_length(tx);
+	if (pad_len) {
+		memset(tx->coalesce_buf + tx->coalesce_idx, 0, pad_len);
+		/* padding is taken care of for coalescing case */
+		tx->packet_len += pad_len;
+		tx->tlen += pad_len;
+	}
+
+	/* dma map the coalesce buffer */
+	addr = dma_map_single(&dd->pcidev->dev, tx->coalesce_buf, tx->tlen,
+			      DMA_TO_DEVICE);
+
+	if (unlikely(dma_mapping_error(&dd->pcidev->dev, addr))) {
+		rval = -ENOSPC;
+		goto fail;
+	}
+
+	return _sdma_txadd_daddr(dd, SDMA_MAP_SINGLE, tx, addr, tx->tlen);
+
+fail:
+	__hfi2_sdma_txclean(dd, tx);
+	return rval;
+}
+
+/* Update sdes when the lmc changes */
+void hfi2_sdma_update_lmc(struct hfi2_devdata *dd, u64 mask, u32 lid)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	struct sdma_engine *sde;
+	int i;
+	u64 sreg;
+
+	/* only WFR has SDMA CHECK registers - skip for all others */
+	if (dd->params->chip_type != CHIP_WFR)
+		return;
+
+	sreg = ((mask & SD(CHECK_SLID_MASK_MASK))
+		<< SD(CHECK_SLID_MASK_SHIFT)) |
+	       (((lid & mask) & SD(CHECK_SLID_VALUE_MASK))
+		<< SD(CHECK_SLID_VALUE_SHIFT));
+
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i) {
+		hfi2_cdbg(LINKVERB, "SendDmaEngine[%d].SLID_CHECK = 0x%x", i,
+			  (u32)sreg);
+		sde = &dd->per_sdma[i];
+		write_sde_csr(sde, SD(CHECK_SLID), sreg);
+	}
+}
+
+/*
+ * Add ahg to the sdma_txreq
+ *
+ * The logic will consume up to 3
+ * descriptors at the beginning of
+ * sdma_txreq.
+ */
+void _hfi2_sdma_txreq_ahgadd(struct sdma_txreq *tx, u8 num_ahg, u8 ahg_entry,
+			     u32 *ahg, u8 ahg_hlen)
+{
+	u32 i, shift = 0, desc = 0;
+	u8 mode;
+
+	WARN_ON_ONCE(num_ahg > 9 || (ahg_hlen & 3) || ahg_hlen == 4);
+	/* compute mode */
+	if (num_ahg == 1)
+		mode = SDMA_AHG_APPLY_UPDATE1;
+	else if (num_ahg <= 5)
+		mode = SDMA_AHG_APPLY_UPDATE2;
+	else
+		mode = SDMA_AHG_APPLY_UPDATE3;
+	tx->num_desc++;
+	/* initialize to consumed descriptors to zero */
+	switch (mode) {
+	case SDMA_AHG_APPLY_UPDATE3:
+		tx->num_desc++;
+		tx->descs[2].qw[0] = 0;
+		tx->descs[2].qw[1] = 0;
+		fallthrough;
+	case SDMA_AHG_APPLY_UPDATE2:
+		tx->num_desc++;
+		tx->descs[1].qw[0] = 0;
+		tx->descs[1].qw[1] = 0;
+		break;
+	}
+	ahg_hlen >>= 2;
+	tx->descs[0].qw[1] |= (((u64)ahg_entry & SDMA_DESC1_HEADER_INDEX_MASK)
+			       << SDMA_DESC1_HEADER_INDEX_SHIFT) |
+			      (((u64)ahg_hlen & SDMA_DESC1_HEADER_DWS_MASK)
+			       << SDMA_DESC1_HEADER_DWS_SHIFT) |
+			      (((u64)mode & SDMA_DESC1_HEADER_MODE_MASK)
+			       << SDMA_DESC1_HEADER_MODE_SHIFT) |
+			      (((u64)ahg[0] & SDMA_DESC1_HEADER_UPDATE1_MASK)
+			       << SDMA_DESC1_HEADER_UPDATE1_SHIFT);
+	for (i = 0; i < (num_ahg - 1); i++) {
+		if (!shift && !(i & 2))
+			desc++;
+		tx->descs[desc].qw[!!(i & 2)] |= (((u64)ahg[i + 1]) << shift);
+		shift = (shift + 32) & 63;
+	}
+}
+
+/**
+ * hfi2_sdma_ahg_alloc - allocate an AHG entry
+ * @sde: engine to allocate from
+ *
+ * Return:
+ * 0-31 when successful, -EOPNOTSUPP if AHG is not enabled,
+ * -ENOSPC if an entry is not available
+ */
+int hfi2_sdma_ahg_alloc(struct sdma_engine *sde)
+{
+	int nr;
+	int oldbit;
+
+	if (!sde) {
+		trace_hfi2_ahg_allocate(sde, -EINVAL);
+		return -EINVAL;
+	}
+	while (1) {
+		nr = ffz(READ_ONCE(sde->ahg_bits));
+		if (nr > 31) {
+			trace_hfi2_ahg_allocate(sde, -ENOSPC);
+			return -ENOSPC;
+		}
+		oldbit = test_and_set_bit(nr, &sde->ahg_bits);
+		if (!oldbit)
+			break;
+		cpu_relax();
+	}
+	trace_hfi2_ahg_allocate(sde, nr);
+	return nr;
+}
+
+/**
+ * hfi2_sdma_ahg_free - free an AHG entry
+ * @sde: engine to return AHG entry
+ * @ahg_index: index to free
+ *
+ * This routine frees the indicate AHG entry.
+ */
+void hfi2_sdma_ahg_free(struct sdma_engine *sde, int ahg_index)
+{
+	if (!sde)
+		return;
+	trace_hfi2_ahg_deallocate(sde, ahg_index);
+	if (ahg_index < 0 || ahg_index > 31)
+		return;
+	clear_bit(ahg_index, &sde->ahg_bits);
+}
+
+/*
+ * SPC freeze handling for SDMA engines.  Called when the driver knows
+ * the SPC is going into a freeze but before the freeze is fully
+ * settled.  Generally an error interrupt.
+ *
+ * This event will pull the engine out of running so no more entries can be
+ * added to the engine's queue.
+ */
+void hfi2_sdma_freeze_notify(struct hfi2_devdata *dd, int link_down)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	int i;
+	enum sdma_events event = link_down ? sdma_event_e85_link_down :
+					     sdma_event_e80_hw_freeze;
+
+	/* set up the wait but do not wait here */
+	atomic_set(&dd->sdma_unfreeze_count,
+		   dr->last_sdma_engine - dr->first_sdma_engine);
+
+	/* tell all engines to stop running and wait */
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i)
+		sdma_process_event(&dd->per_sdma[i], event);
+
+	/* hfi2_sdma_freeze() will wait for all engines to have stopped */
+}
+
+/*
+ * SPC freeze handling for SDMA engines.  Called when the driver knows
+ * the SPC is fully frozen.
+ */
+void hfi2_sdma_freeze(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	int i;
+	int ret;
+
+	/*
+	 * Make sure all engines have moved out of the running state before
+	 * continuing.
+	 */
+	ret = wait_event_interruptible(dd->sdma_unfreeze_wq,
+				       atomic_read(&dd->sdma_unfreeze_count) <=
+					       0);
+	/* interrupted or count is negative, then unloading - just exit */
+	if (ret || atomic_read(&dd->sdma_unfreeze_count) < 0)
+		return;
+
+	/* set up the count for the next wait */
+	atomic_set(&dd->sdma_unfreeze_count,
+		   dr->last_sdma_engine - dr->first_sdma_engine);
+
+	/* tell all engines that the SPC is frozen, they can start cleaning */
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i)
+		sdma_process_event(&dd->per_sdma[i], sdma_event_e81_hw_frozen);
+
+	/*
+	 * Wait for everyone to finish software clean before exiting.  The
+	 * software clean will read engine CSRs, so must be completed before
+	 * the next step, which will clear the engine CSRs.
+	 */
+	(void)wait_event_interruptible(dd->sdma_unfreeze_wq,
+				       atomic_read(&dd->sdma_unfreeze_count) <=
+					       0);
+	/* no need to check results - done no matter what */
+}
+
+/*
+ * SPC freeze handling for the SDMA engines.  Called after the SPC is unfrozen.
+ *
+ * The SPC freeze acts like a SDMA halt and a hardware clean combined.  All
+ * that is left is a software clean.  We could do it after the SPC is fully
+ * frozen, but then we'd have to add another state to wait for the unfreeze.
+ * Instead, just defer the software clean until the unfreeze step.
+ */
+void hfi2_sdma_unfreeze(struct hfi2_devdata *dd)
+{
+	struct hfi2_devrsrcs *dr = &dd->rsrcs;
+	int i;
+
+	/* tell all engines start freeze clean up */
+	for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; ++i)
+		sdma_process_event(&dd->per_sdma[i],
+				   sdma_event_e82_hw_unfreeze);
+}
+
+/**
+ * _hfi2_sdma_engine_progress_schedule() - schedule progress on engine
+ * @sde: sdma_engine to schedule progress
+ */
+void _hfi2_sdma_engine_progress_schedule(struct sdma_engine *sde)
+{
+	trace_hfi2_sdma_engine_progress(sde, sde->progress_mask);
+	/* assume we have selected a good cpu */
+	hfi2_write_csr(sde->dd,
+		       sde->dd->params->cce_int_force_reg +
+			       (8 * (sde->dd->params->is_sdma_start / 64)),
+		       sde->progress_mask);
+}
+
+/*
+ * Wait for the given bit to be set in the SendDmaStatus register.
+ *
+ * Return:
+ *   0          success
+ *   -ETIMEDOUT fail
+ */
+static int wait_for_engine_bit(struct hfi2_devdata *dd, u32 engine, u64 bit,
+			       u32 mstimeout)
+{
+	unsigned long timeout;
+	u64 status;
+
+	timeout = jiffies + msecs_to_jiffies(mstimeout);
+	while (1) {
+		usleep_range(80, 120);
+		status = read_sdma_csr(dd, engine,
+				       dd->params->send_dma_status_reg);
+		if (status & bit)
+			return 0;
+		if (time_after(jiffies, timeout))
+			return -ETIMEDOUT;
+	}
+}
+
+static void engine_disable(struct hfi2_devdata *dd, u32 engine)
+{
+	write_sdma_csr(dd, engine, dd->params->send_dma_ctrl_reg, 0);
+}
+
+/* expects engine is disabled */
+static void engine_halt(struct hfi2_devdata *dd, u32 engine)
+{
+	u64 status;
+	int ret;
+
+	/* halt only if needed */
+	status = read_sdma_csr(dd, engine, dd->params->send_dma_status_reg);
+	if (status & SD(STATUS_ENG_HALTED_SMASK))
+		return;
+
+	write_sdma_csr(dd, engine, dd->params->send_dma_ctrl_reg,
+		       SD(CTRL_SDMA_HALT_SMASK));
+	ret = wait_for_engine_bit(dd, engine, SD(STATUS_ENG_HALTED_SMASK), 100);
+	if (ret)
+		dd_dev_err(dd, "%s: engine %d did not halt\n", __func__,
+			   engine);
+}
+
+/* expects engine is disabled */
+static void engine_cleanup(struct hfi2_devdata *dd, u32 engine)
+{
+	u64 status;
+	int ret;
+
+	/* cleanup only if needed */
+	status = read_sdma_csr(dd, engine, dd->params->send_dma_status_reg);
+	if (status & SD(STATUS_ENG_CLEANED_UP_SMASK))
+		return;
+
+	write_sdma_csr(dd, engine, dd->params->send_dma_ctrl_reg,
+		       SD(CTRL_SDMA_CLEANUP_SMASK));
+	ret = wait_for_engine_bit(dd, engine, SD(STATUS_ENG_CLEANED_UP_SMASK),
+				  100);
+	if (ret)
+		dd_dev_err(dd, "%s: engine %d did not clean up\n", __func__,
+			   engine);
+}
+
+static void engine_enable(struct hfi2_devdata *dd, u32 engine)
+{
+	write_sdma_csr(dd, engine, dd->params->send_dma_ctrl_reg,
+		       SD(CTRL_SDMA_ENABLE_SMASK));
+}
+
+/*
+ * Write to each JKR SDMA memory
+ *
+ * Expect:
+ *   o Interrupts are masked.
+ *   o SDMA engines are not set up for main driver use.
+ *
+ * Return:
+ *   o 0      success
+ *   o -errno failure
+ */
+static int prime_sdma_memories(struct hfi2_devdata *dd)
+{
+	const u32 engine = 0;
+	/* data */
+	const u32 num_desc = 64; /* in multiples of 64 */
+	const u32 data_size = 8; /* in bytes */
+	const u32 desc_size = sizeof(struct hw_sdma_desc) * num_desc;
+	const u32 buf_size = desc_size + data_size;
+	/* memories */
+	const u32 num_memories = 16;
+	const u32 num_banks = 4;
+	const u32 bank_size = 784 * 8; /* bytes */
+	const u32 mem_size = num_banks * bank_size;
+	const u32 num_credits = bank_size / SDMA_BLOCK_SIZE;
+	/* variables */
+	unsigned long timeout;
+	dma_addr_t buf_phys;
+	dma_addr_t desc_phys;
+	dma_addr_t data_phys;
+	void *buf_addr;
+	struct hw_sdma_desc *desc_addr;
+	u64 status;
+	u64 value;
+	u64 qw0, qw1;
+	u32 mem;
+	u32 bank;
+	u32 start;
+
+	/* only for JKR */
+	if (dd->params->chip_type != CHIP_JKR)
+		return 0;
+	if (!enable_jkr_sdma_mem_init) {
+		dd_dev_info(dd, "SKIPPING Write JKR SDMA memories\n");
+		return 0;
+	}
+	dd_dev_info(dd, "Write JKR SDMA memories\n");
+
+	/*
+	 * Set up memory
+	 */
+	/* allocate dma memory */
+	buf_addr = dma_alloc_coherent(&dd->pcidev->dev, buf_size, &buf_phys,
+				      GFP_KERNEL);
+	if (!buf_addr)
+		return -EIO;
+	memset(buf_addr, 0, buf_size);
+
+	/* assign DMA buffer: desc, then data */
+	desc_addr = buf_addr;
+	desc_phys = buf_phys;
+	data_phys = buf_phys + desc_size;
+
+	/* create bad packet: leave as zero, size is 8 */
+
+	/*
+	 * Set up engine
+	 */
+	/* mask all errors */
+	write_sdma_csr(dd, engine, dd->params->send_dma_eng_err_mask_reg, 0);
+
+	/* disable, halt, and clean in case anything is lingering */
+	engine_disable(dd, engine);
+	engine_halt(dd, engine);
+	engine_cleanup(dd, engine);
+
+	/* descriptor address */
+	write_sdma_csr(dd, engine, dd->params->send_dma_base_addr_reg,
+		       desc_phys);
+	/* no idle countdown */
+	write_sdma_csr(dd, engine, dd->params->send_dma_reload_cnt_reg, 0);
+	/* no progress countdown */
+	write_sdma_csr(dd, engine, dd->params->send_dma_desc_cnt_reg, 0);
+	/* no head dma address */
+	write_sdma_csr(dd, engine, dd->params->send_dma_head_addr_reg, 0);
+
+	/*
+	 * Loop over memories
+	 *
+	 * Expect engine is cleaned up at top of loop.
+	 */
+	for (mem = 0; mem < num_memories; mem++) {
+		for (bank = 0; bank < num_banks; bank++) {
+			/* set fetch destination */
+			start = ((mem * mem_size) + (bank * bank_size)) /
+				SDMA_BLOCK_SIZE;
+			value = ((u64)num_credits
+				 << SD(MEMORY_SDMA_MEMORY_CNT_SHIFT)) |
+				((u64)start
+				 << SD(MEMORY_SDMA_MEMORY_INDEX_SHIFT));
+			write_sdmacfg_csr(dd, engine,
+					  dd->params->send_dma_cfg_memory_reg,
+					  value);
+			/* set descriptor length, disable generation counter */
+			write_sdma_csr(
+				dd, engine, dd->params->send_dma_len_gen_reg,
+				(num_desc / 64) << SD(LEN_GEN_LENGTH_SHIFT));
+
+			/* zero descriptors */
+			memset(desc_addr, 0, desc_size);
+
+			/* enable engine */
+			engine_enable(dd, engine);
+
+			/*
+			 * Do send
+			 */
+			/*
+			 * step: create descriptor
+			 *   no ahg, no generation, no interrupt request,
+			 *   no host writes
+			 */
+			qw0 = data_phys;
+			qw1 = JKR_SDMA_DESC1_FIRST_DESC_FLAG |
+			      JKR_SDMA_DESC1_LAST_DESC_FLAG |
+			      data_size << JKR_SDMA_DESC1_BYTE_COUNT_SHIFT;
+
+			/*
+			 * step: write descriptor
+			 *   expect index 0 from engine cleanup
+			 */
+			desc_addr->qw[0] = cpu_to_le64(qw0);
+			desc_addr->qw[1] = cpu_to_le64(qw1);
+
+			/*
+			 * step: start engine by updating tail to index 1
+			 */
+			smp_wmb(); /* commit previous writes to memory */
+			write_sdma_csr(dd, engine,
+				       dd->params->send_dma_tail_reg, 1);
+
+			/*
+			 * step: wait for error halt
+			 */
+			timeout = jiffies + msecs_to_jiffies(500);
+			while (1) {
+				usleep_range(80, 120);
+				status = read_sdma_csr(
+					dd, engine,
+					dd->params->send_dma_status_reg);
+				if (status & SD(STATUS_ENG_HALTED_SMASK))
+					break;
+
+				if (time_after(jiffies, timeout)) {
+					dd_dev_warn(
+						dd,
+						"%s: [%2d,%d] timeout waiting for halt\n",
+						__func__, mem, bank);
+					break;
+				}
+			}
+
+			/* disable, halt, and clean up */
+			engine_disable(dd, engine);
+			engine_halt(dd, engine);
+			engine_cleanup(dd, engine);
+		}
+	}
+
+	/*
+	 * Clean up
+	 */
+	write_sdma_csr(dd, engine, dd->params->send_dma_base_addr_reg, 0);
+	write_sdmacfg_csr(dd, engine, dd->params->send_dma_cfg_memory_reg, 0);
+	write_sdma_csr(dd, engine, dd->params->send_dma_eng_err_clear_reg,
+		       ~0ull);
+
+	/* free dma memory */
+	dma_free_coherent(&dd->pcidev->dev, buf_size, buf_addr, buf_phys);
+	return 0;
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.