--- /dev/null	Tue Aug 20 09:24:08 2019
+++ new/usr/src/cmd/mdb/i86pc/modules/unix/xcall.c	Tue Aug 20 09:24:08 2019
@@ -0,0 +1,237 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright 2019 Joyent, Inc.
+ */
+
+#include <mdb/mdb_modapi.h>
+#include <mdb/mdb_ctf.h>
+#include <sys/cpuvar.h>
+#include <sys/x_call.h>
+
+typedef struct {
+		uint32_t xc_work_cnt;
+		struct xc_msg *xc_curmsg;
+		struct xc_msg *xc_msgbox;
+		xc_data_t xc_data;
+} mdb_xcall_machcpu_t;
+
+typedef struct {
+	processorid_t cpu_id;
+	mdb_xcall_machcpu_t cpu_m;
+} mdb_xcall_cpu_t;
+
+typedef struct {
+	uint_t xd_flags;
+	processorid_t xd_cpu_id;
+	size_t xd_msg_index;
+	struct xc_msg xd_msgs[NCPU];
+} xcall_data_t;
+
+void
+xcall_help(void)
+{
+	mdb_printf(
+	    "Print all active cross-calls where the given CPU is the master.\n"
+	    "The PEND column is ->xc_work_cnt, the pending message count -\n"
+	    "this includes both master and slave messages.  For each\n"
+	    "cross call, the message type and the slave CPU ID are shown.\n");
+}
+
+static int
+cpu_id_to_addr(processorid_t cpun, uintptr_t *addrp)
+{
+	uintptr_t addr;
+	GElf_Sym sym;
+
+	if (mdb_lookup_by_name("cpu", &sym) == -1) {
+		mdb_warn("failed to find symbol for 'cpu'");
+		return (-1);
+	}
+
+	if (cpun * sizeof (uintptr_t) > sym.st_size)
+		return (-1);
+
+	addr = (uintptr_t)sym.st_value + cpun * sizeof (uintptr_t);
+
+	if (mdb_vread(&addr, sizeof (addr), addr) == -1) {
+		mdb_warn("failed to read cpu[%lu]", cpun);
+		return (-1);
+	}
+
+	if (addr != (uintptr_t)NULL) {
+		*addrp = addr;
+		return (0);
+	}
+
+	return (-1);
+}
+
+static int
+xcall_copy_msg(struct xc_msg *msg, xcall_data_t *data, boolean_t current)
+{
+	if (data->xd_msg_index >= NCPU) {
+		mdb_warn("ran out of msg space: %lu >= %lu\n",
+		    data->xd_msg_index, NCPU);
+		return (-1);
+	}
+
+	bcopy(msg, &data->xd_msgs[data->xd_msg_index], sizeof (*msg));
+
+	/*
+	 * As we don't use .xc_next, store 'current' there.
+	 */
+	data->xd_msgs[data->xd_msg_index].xc_next = (void *)(uintptr_t)current;
+	data->xd_msg_index++;
+	return (0);
+}
+
+static int
+xcall_get_msgs(uintptr_t addr, const void *wdata, void *priv)
+{
+	_NOTE(ARGUNUSED(wdata));
+	xcall_data_t *data = priv;
+	mdb_xcall_cpu_t xcpu = { 0, };
+	struct xc_msg msg;
+	uintptr_t msgaddr;
+
+	if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t",
+	    addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1)
+		return (WALK_ERR);
+
+	if (xcpu.cpu_m.xc_curmsg != NULL) {
+		msgaddr = (uintptr_t)xcpu.cpu_m.xc_curmsg;
+
+		if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg))
+			return (WALK_ERR);
+
+		if (msg.xc_master == data->xd_cpu_id) {
+			if (data->xd_flags & DCMD_PIPE_OUT)
+				mdb_printf("%p\n", msgaddr);
+			else if (xcall_copy_msg(&msg, data, B_TRUE) != 0)
+				return (WALK_ERR);
+		}
+	}
+
+	for (msgaddr = (uintptr_t)xcpu.cpu_m.xc_msgbox;
+	    msgaddr != (uintptr_t)NULL; msgaddr = (uintptr_t)msg.xc_next) {
+		if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg))
+			return (WALK_ERR);
+
+		if (msg.xc_master != data->xd_cpu_id)
+			continue;
+
+		if (data->xd_flags & DCMD_PIPE_OUT)
+			mdb_printf("%p\n", msgaddr);
+		else if (xcall_copy_msg(&msg, data, B_FALSE) != 0)
+			return (WALK_ERR);
+	}
+
+	return (WALK_NEXT);
+}
+
+static int
+print_xcall_msg(struct xc_msg *msg)
+{
+	boolean_t current = (boolean_t)msg->xc_next;
+	char indent[] = "        ";
+	const char *cmd;
+
+	switch (msg->xc_command) {
+		case XC_MSG_ASYNC: cmd = "ASYNC"; break;
+		case XC_MSG_CALL: cmd = "CALL"; break;
+		case XC_MSG_SYNC: cmd = "SYNC"; break;
+		case XC_MSG_FREE:cmd = "FREE"; break;
+		case XC_MSG_WAITING: cmd = "WAITING"; break;
+		case XC_MSG_RELEASED: cmd = "RELEASED"; break;
+		case XC_MSG_DONE: cmd = "DONE"; break;
+		default: cmd = "?"; break;
+	}
+
+	mdb_printf("%s %s%-*s %-6u\n", indent, current ? "*" : "",
+	    9 - current, cmd, msg->xc_slave);
+	return (0);
+}
+
+/*
+ * Show all xcall messages where the master is the given CPU.
+ *
+ * As non-free messages can be on the slave's ->xc_msgbox or ->xc_curmsg, we
+ * need to walk across all of them to find each message where ->xc_master
+ * is our CPU ID.
+ */
+int
+xcall_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+	mdb_xcall_cpu_t xcpu = { 0, };
+	xcall_data_t data = { 0, };
+
+	if (mdb_getopts(argc, argv, NULL) != argc)
+		return (DCMD_USAGE);
+
+	/*
+	 * Yep, this will re-collect all the messages each time.  Shrug.
+	 */
+	if (!(flags & DCMD_ADDRSPEC)) {
+		if (mdb_pwalk_dcmd("cpu", "xcall", argc, argv, 0) == -1) {
+			mdb_warn("can't walk CPUs");
+			return (DCMD_ERR);
+		}
+
+		return (DCMD_OK);
+	}
+
+	if (addr < NCPU && cpu_id_to_addr((processorid_t)addr, &addr) != 0) {
+		mdb_warn("invalid CPU ID %lu\n", addr);
+		return (DCMD_ERR);
+	}
+
+	if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t",
+	    addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1) {
+		mdb_warn("couldn't read cpu 0x%lx", addr);
+		return (DCMD_ERR);
+	}
+
+	data.xd_cpu_id = xcpu.cpu_id;
+	data.xd_flags = flags;
+
+	if (mdb_pwalk("cpu", xcall_get_msgs, &data, (uintptr_t)NULL) == -1) {
+		mdb_warn("can't walk CPUs");
+		return (DCMD_ERR);
+	}
+
+	if (flags & DCMD_PIPE_OUT)
+		return (DCMD_OK);
+
+	if (DCMD_HDRSPEC(flags))
+		mdb_printf("%<u>%3s %4s %s%</u>\n", "CPU", "PEND", "HANDLER");
+
+	if (data.xd_msg_index == 0) {
+		mdb_printf("%3d %4d -\n",
+		    xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt);
+		return (DCMD_OK);
+	}
+
+	mdb_printf("%3d %4d %a(%a, %a, %a)\n",
+	    xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt,
+	    xcpu.cpu_m.xc_data.xc_func, xcpu.cpu_m.xc_data.xc_a1,
+	    xcpu.cpu_m.xc_data.xc_a2, xcpu.cpu_m.xc_data.xc_a3);
+
+	if (!(flags & DCMD_PIPE_OUT))
+		mdb_printf("         %<u>%-9s %-6s%</u>\n", "COMMAND", "SLAVE");
+
+	for (size_t i = 0; i < data.xd_msg_index; i++) {
+		if (print_xcall_msg(&data.xd_msgs[i]) != 0)
+			return (DCMD_ERR);
+	}
+
+	return (DCMD_OK);
+}
