--- old/usr/src/cmd/mdb/intel/modules/i40e/i40e.c	Mon Aug  5 17:15:13 2019
+++ new/usr/src/cmd/mdb/intel/modules/i40e/i40e.c	Mon Aug  5 17:15:13 2019
@@ -10,9 +10,10 @@
  */
 
 /*
- * Copyright 2016 Joyent, Inc.
+ * Copyright 2018 Joyent, Inc.
  */
 
+#include <mdb/mdb_ctf.h>
 #include <sys/mdb_modapi.h>
 #include "i40e_sw.h"
 
@@ -97,9 +98,181 @@
 	return (DCMD_OK);
 }
 
+typedef struct mdb_i40e_trqpair {
+	uint32_t		itrq_tx_ring_size;
+	uint32_t		itrq_desc_free;
+	uint32_t 		*itrq_desc_wbhead;
+	uint32_t		itrq_desc_head;
+	uint32_t		itrq_desc_tail;
+	i40e_tx_desc_t		*itrq_desc_ring;
+	i40e_tx_control_block_t	**itrq_tcb_work_list;
+} mdb_i40e_trqpair_t;
+
+static void
+i40e_tx_ring_help()
+{
+	mdb_printf(
+	    "\t -a dump all ring entries\n"
+	    "\t or\n"
+	    "\t combine -b [start index] with -e [end index] to specify a \n"
+	    "\t range of ring entries to print\n");
+}
+
+static int
+i40e_tx_ring_dcmd(uintptr_t addr, uint_t flags, int argc,
+    const mdb_arg_t *argv)
+{
+	mdb_i40e_trqpair_t trq;
+	i40e_tx_desc_t *descring;
+	i40e_tx_control_block_t **wklist;
+	uint32_t wbhead;
+	size_t ringsz, wklistsz;
+	boolean_t opt_a = B_FALSE;
+	char *opt_b = NULL, *opt_e = NULL;
+	uint64_t begin = UINT64_MAX, end = UINT64_MAX;
+
+	if (!(flags & DCMD_ADDRSPEC)) {
+		mdb_warn("::i40e_tx_ring does not operate globally\n");
+		return (DCMD_USAGE);
+	}
+
+	if (mdb_getopts(argc, argv,
+	    'a', MDB_OPT_SETBITS, B_TRUE, &opt_a,
+	    'b', MDB_OPT_STR, &opt_b,
+	    'e', MDB_OPT_STR, &opt_e, NULL) != argc)
+		return (DCMD_USAGE);
+
+	/*
+	 * Verify that a legal combination of -a/-b/-e were used.
+	 */
+	if (opt_a && (opt_b != NULL || opt_e != NULL)) {
+		mdb_warn("-a and -b/-e are mutually exclusive\n");
+		return (DCMD_USAGE);
+	}
+	if (argc > 0 && ! opt_a && (opt_b == NULL || opt_e == NULL)) {
+		mdb_warn("-b/-e must both be specified\n");
+		return (DCMD_USAGE);
+	}
+
+	if (mdb_ctf_vread(&trq, "i40e_trqpair_t", "mdb_i40e_trqpair_t", addr,
+	    0) == -1) {
+		mdb_warn("failed to read i40e_trqpair_t at %p", addr);
+		return (DCMD_ERR);
+	}
+
+	if (opt_b != NULL)
+		begin = mdb_strtoull(opt_b);
+	if (opt_e != NULL)
+		end = mdb_strtoull(opt_e);
+	if (opt_a) {
+		begin = 0;
+		end = trq.itrq_tx_ring_size - 1;
+	}
+
+	/*
+	 * Verify that the requested range of ring entries makes sense.
+	 */
+	if (argc > 0 && (end < begin || begin >= trq.itrq_tx_ring_size ||
+	    end >= trq.itrq_tx_ring_size)) {
+		mdb_warn("invalid range specified\n");
+		return (DCMD_USAGE);
+	}
+
+	if (mdb_vread(&wbhead, sizeof (uint32_t),
+	    (uintptr_t)trq.itrq_desc_wbhead) != sizeof (uint32_t)) {
+		mdb_warn("failed to read trq.itrq_desc_wbhead");
+		return (DCMD_ERR);
+	}
+	mdb_printf("%-20s%d\n", "Ring Size:", trq.itrq_tx_ring_size);
+	mdb_printf("%-20s%d\n", "Free Descriptors:", trq.itrq_desc_free);
+	mdb_printf("%-20s%d\n", "Writeback Head:", wbhead);
+	mdb_printf("%-20s%d\n", "Head:", trq.itrq_desc_head);
+	mdb_printf("%-20s%d\n", "Tail:", trq.itrq_desc_tail);
+
+	/*
+	 * No arguments were specified, so we're done.
+	 */
+	if (argc == 0)
+		return (DCMD_OK);
+
+	/*
+	 * Allocate memory and read in the entire TX descriptor ring and
+	 * TCB work list.
+	 */
+	ringsz = sizeof (i40e_tx_desc_t) * trq.itrq_tx_ring_size;
+	descring = mdb_alloc(ringsz, UM_SLEEP);
+	if (mdb_vread(descring, ringsz, (uintptr_t)trq.itrq_desc_ring) !=
+	    ringsz) {
+		mdb_warn("Failed to read in TX decriptor ring\n");
+		mdb_free(descring, ringsz);
+		return (DCMD_ERR);
+	}
+	wklistsz = sizeof (i40e_tx_control_block_t *) * trq.itrq_tx_ring_size;
+	wklist = mdb_alloc(wklistsz, UM_SLEEP);
+	if (mdb_vread(wklist, wklistsz, (uintptr_t)trq.itrq_tcb_work_list) !=
+	    wklistsz) {
+		mdb_warn("Failed to read in TX TCB work list\n");
+		mdb_free(descring, ringsz);
+		mdb_free(wklist, wklistsz);
+		return (DCMD_ERR);
+	}
+
+	mdb_printf("\n%-10s %-10s %-16s %-16s %-10s\n", "Index", "Desc Type",
+	    "Desc Ptr", "TCB Ptr", "Other");
+	for (uint64_t i = begin; i <= end; i++) {
+		const char *dtype;
+		char dother[17];
+		i40e_tx_desc_t *dptr;
+		i40e_tx_control_block_t *tcbptr;
+		uint64_t ctob;
+
+		dptr = &descring[i];
+		tcbptr = wklist[i];
+		ctob = LE_64(dptr->cmd_type_offset_bsz);
+		if (ctob == 0) {
+			dtype = "FREE";
+		} else {
+			switch (ctob & I40E_TXD_QW1_DTYPE_MASK) {
+			case (I40E_TX_DESC_DTYPE_CONTEXT):
+				dtype = "CONTEXT";
+				break;
+			case (I40E_TX_DESC_DTYPE_DATA):
+				dtype = "DATA";
+				break;
+			case (I40E_TX_DESC_DTYPE_FILTER_PROG):
+				dtype = "FILTER";
+				break;
+			default:
+				dtype = "UNKNOWN";
+			}
+		}
+		dother[0] = '\0';
+		if (i == wbhead)
+			(void) strcat(dother, "WBHEAD");
+
+		if (i == trq.itrq_desc_head)
+			(void) strcat(dother,
+			    strlen(dother) > 0 ? " HEAD" : "HEAD");
+
+		if (i == trq.itrq_desc_tail)
+			(void) strcat(dother,
+			    strlen(dother) > 0 ? " TAIL" : "TAIL");
+
+		mdb_printf("%-10d %-10s %-16p %-16p %-10s\n", i, dtype, dptr,
+		    tcbptr, dother);
+	}
+
+	mdb_free(descring, ringsz);
+	mdb_free(wklist, wklistsz);
+	return (DCMD_OK);
+}
+
 static const mdb_dcmd_t i40e_dcmds[] = {
 	{ "i40e_switch_rsrcs", NULL, "print switch resources",
 	    i40e_switch_rsrcs_dcmd, NULL },
+	{ "i40e_tx_ring", "[-a] -b [start index] -e [end index]\n",
+	    "dump TX descriptor ring state", i40e_tx_ring_dcmd,
+	    i40e_tx_ring_help },
 	{ NULL }
 };
 
