1*a8ea0c9dSJohn Levon /*
2*a8ea0c9dSJohn Levon  * This file and its contents are supplied under the terms of the
3*a8ea0c9dSJohn Levon  * Common Development and Distribution License ("CDDL"), version 1.0.
4*a8ea0c9dSJohn Levon  * You may only use this file in accordance with the terms of version
5*a8ea0c9dSJohn Levon  * 1.0 of the CDDL.
6*a8ea0c9dSJohn Levon  *
7*a8ea0c9dSJohn Levon  * A full copy of the text of the CDDL should have accompanied this
8*a8ea0c9dSJohn Levon  * source.  A copy of the CDDL is also available via the Internet at
9*a8ea0c9dSJohn Levon  * http://www.illumos.org/license/CDDL.
10*a8ea0c9dSJohn Levon  */
11*a8ea0c9dSJohn Levon /*
12*a8ea0c9dSJohn Levon  * Copyright 2019 Joyent, Inc.
13*a8ea0c9dSJohn Levon  */
14*a8ea0c9dSJohn Levon 
15*a8ea0c9dSJohn Levon #include <mdb/mdb_modapi.h>
16*a8ea0c9dSJohn Levon #include <mdb/mdb_ctf.h>
17*a8ea0c9dSJohn Levon #include <sys/cpuvar.h>
18*a8ea0c9dSJohn Levon #include <sys/x_call.h>
19*a8ea0c9dSJohn Levon 
20*a8ea0c9dSJohn Levon typedef struct {
21*a8ea0c9dSJohn Levon 		uint32_t xc_work_cnt;
22*a8ea0c9dSJohn Levon 		struct xc_msg *xc_curmsg;
23*a8ea0c9dSJohn Levon 		struct xc_msg *xc_msgbox;
24*a8ea0c9dSJohn Levon 		xc_data_t xc_data;
25*a8ea0c9dSJohn Levon } mdb_xcall_machcpu_t;
26*a8ea0c9dSJohn Levon 
27*a8ea0c9dSJohn Levon typedef struct {
28*a8ea0c9dSJohn Levon 	processorid_t cpu_id;
29*a8ea0c9dSJohn Levon 	mdb_xcall_machcpu_t cpu_m;
30*a8ea0c9dSJohn Levon } mdb_xcall_cpu_t;
31*a8ea0c9dSJohn Levon 
32*a8ea0c9dSJohn Levon typedef struct {
33*a8ea0c9dSJohn Levon 	uint_t xd_flags;
34*a8ea0c9dSJohn Levon 	processorid_t xd_cpu_id;
35*a8ea0c9dSJohn Levon 	size_t xd_msg_index;
36*a8ea0c9dSJohn Levon 	struct xc_msg xd_msgs[NCPU];
37*a8ea0c9dSJohn Levon } xcall_data_t;
38*a8ea0c9dSJohn Levon 
39*a8ea0c9dSJohn Levon void
xcall_help(void)40*a8ea0c9dSJohn Levon xcall_help(void)
41*a8ea0c9dSJohn Levon {
42*a8ea0c9dSJohn Levon 	mdb_printf(
43*a8ea0c9dSJohn Levon 	    "Print all active cross-calls where the given CPU is the master.\n"
44*a8ea0c9dSJohn Levon 	    "The PEND column is ->xc_work_cnt, the pending message count -\n"
45*a8ea0c9dSJohn Levon 	    "this includes both master and slave messages.  For each\n"
46*a8ea0c9dSJohn Levon 	    "cross call, the message type and the slave CPU ID are shown.\n");
47*a8ea0c9dSJohn Levon }
48*a8ea0c9dSJohn Levon 
49*a8ea0c9dSJohn Levon static int
cpu_id_to_addr(processorid_t cpun,uintptr_t * addrp)50*a8ea0c9dSJohn Levon cpu_id_to_addr(processorid_t cpun, uintptr_t *addrp)
51*a8ea0c9dSJohn Levon {
52*a8ea0c9dSJohn Levon 	uintptr_t addr;
53*a8ea0c9dSJohn Levon 	GElf_Sym sym;
54*a8ea0c9dSJohn Levon 
55*a8ea0c9dSJohn Levon 	if (mdb_lookup_by_name("cpu", &sym) == -1) {
56*a8ea0c9dSJohn Levon 		mdb_warn("failed to find symbol for 'cpu'");
57*a8ea0c9dSJohn Levon 		return (-1);
58*a8ea0c9dSJohn Levon 	}
59*a8ea0c9dSJohn Levon 
60*a8ea0c9dSJohn Levon 	if (cpun * sizeof (uintptr_t) > sym.st_size)
61*a8ea0c9dSJohn Levon 		return (-1);
62*a8ea0c9dSJohn Levon 
63*a8ea0c9dSJohn Levon 	addr = (uintptr_t)sym.st_value + cpun * sizeof (uintptr_t);
64*a8ea0c9dSJohn Levon 
65*a8ea0c9dSJohn Levon 	if (mdb_vread(&addr, sizeof (addr), addr) == -1) {
66*a8ea0c9dSJohn Levon 		mdb_warn("failed to read cpu[%lu]", cpun);
67*a8ea0c9dSJohn Levon 		return (-1);
68*a8ea0c9dSJohn Levon 	}
69*a8ea0c9dSJohn Levon 
70*a8ea0c9dSJohn Levon 	if (addr != (uintptr_t)NULL) {
71*a8ea0c9dSJohn Levon 		*addrp = addr;
72*a8ea0c9dSJohn Levon 		return (0);
73*a8ea0c9dSJohn Levon 	}
74*a8ea0c9dSJohn Levon 
75*a8ea0c9dSJohn Levon 	return (-1);
76*a8ea0c9dSJohn Levon }
77*a8ea0c9dSJohn Levon 
78*a8ea0c9dSJohn Levon static int
xcall_copy_msg(struct xc_msg * msg,xcall_data_t * data,boolean_t current)79*a8ea0c9dSJohn Levon xcall_copy_msg(struct xc_msg *msg, xcall_data_t *data, boolean_t current)
80*a8ea0c9dSJohn Levon {
81*a8ea0c9dSJohn Levon 	if (data->xd_msg_index >= NCPU) {
82*a8ea0c9dSJohn Levon 		mdb_warn("ran out of msg space: %lu >= %lu\n",
83*a8ea0c9dSJohn Levon 		    data->xd_msg_index, NCPU);
84*a8ea0c9dSJohn Levon 		return (-1);
85*a8ea0c9dSJohn Levon 	}
86*a8ea0c9dSJohn Levon 
87*a8ea0c9dSJohn Levon 	bcopy(msg, &data->xd_msgs[data->xd_msg_index], sizeof (*msg));
88*a8ea0c9dSJohn Levon 
89*a8ea0c9dSJohn Levon 	/*
90*a8ea0c9dSJohn Levon 	 * As we don't use .xc_next, store 'current' there.
91*a8ea0c9dSJohn Levon 	 */
92*a8ea0c9dSJohn Levon 	data->xd_msgs[data->xd_msg_index].xc_next = (void *)(uintptr_t)current;
93*a8ea0c9dSJohn Levon 	data->xd_msg_index++;
94*a8ea0c9dSJohn Levon 	return (0);
95*a8ea0c9dSJohn Levon }
96*a8ea0c9dSJohn Levon 
97*a8ea0c9dSJohn Levon static int
xcall_get_msgs(uintptr_t addr,const void * wdata,void * priv)98*a8ea0c9dSJohn Levon xcall_get_msgs(uintptr_t addr, const void *wdata, void *priv)
99*a8ea0c9dSJohn Levon {
100*a8ea0c9dSJohn Levon 	_NOTE(ARGUNUSED(wdata));
101*a8ea0c9dSJohn Levon 	xcall_data_t *data = priv;
102*a8ea0c9dSJohn Levon 	mdb_xcall_cpu_t xcpu = { 0, };
103*a8ea0c9dSJohn Levon 	struct xc_msg msg;
104*a8ea0c9dSJohn Levon 	uintptr_t msgaddr;
105*a8ea0c9dSJohn Levon 
106*a8ea0c9dSJohn Levon 	if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t",
107*a8ea0c9dSJohn Levon 	    addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1)
108*a8ea0c9dSJohn Levon 		return (WALK_ERR);
109*a8ea0c9dSJohn Levon 
110*a8ea0c9dSJohn Levon 	if (xcpu.cpu_m.xc_curmsg != NULL) {
111*a8ea0c9dSJohn Levon 		msgaddr = (uintptr_t)xcpu.cpu_m.xc_curmsg;
112*a8ea0c9dSJohn Levon 
113*a8ea0c9dSJohn Levon 		if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg))
114*a8ea0c9dSJohn Levon 			return (WALK_ERR);
115*a8ea0c9dSJohn Levon 
116*a8ea0c9dSJohn Levon 		if (msg.xc_master == data->xd_cpu_id) {
117*a8ea0c9dSJohn Levon 			if (data->xd_flags & DCMD_PIPE_OUT)
118*a8ea0c9dSJohn Levon 				mdb_printf("%p\n", msgaddr);
119*a8ea0c9dSJohn Levon 			else if (xcall_copy_msg(&msg, data, B_TRUE) != 0)
120*a8ea0c9dSJohn Levon 				return (WALK_ERR);
121*a8ea0c9dSJohn Levon 		}
122*a8ea0c9dSJohn Levon 	}
123*a8ea0c9dSJohn Levon 
124*a8ea0c9dSJohn Levon 	for (msgaddr = (uintptr_t)xcpu.cpu_m.xc_msgbox;
125*a8ea0c9dSJohn Levon 	    msgaddr != (uintptr_t)NULL; msgaddr = (uintptr_t)msg.xc_next) {
126*a8ea0c9dSJohn Levon 		if (mdb_vread(&msg, sizeof (msg), msgaddr) != sizeof (msg))
127*a8ea0c9dSJohn Levon 			return (WALK_ERR);
128*a8ea0c9dSJohn Levon 
129*a8ea0c9dSJohn Levon 		if (msg.xc_master != data->xd_cpu_id)
130*a8ea0c9dSJohn Levon 			continue;
131*a8ea0c9dSJohn Levon 
132*a8ea0c9dSJohn Levon 		if (data->xd_flags & DCMD_PIPE_OUT)
133*a8ea0c9dSJohn Levon 			mdb_printf("%p\n", msgaddr);
134*a8ea0c9dSJohn Levon 		else if (xcall_copy_msg(&msg, data, B_FALSE) != 0)
135*a8ea0c9dSJohn Levon 			return (WALK_ERR);
136*a8ea0c9dSJohn Levon 	}
137*a8ea0c9dSJohn Levon 
138*a8ea0c9dSJohn Levon 	return (WALK_NEXT);
139*a8ea0c9dSJohn Levon }
140*a8ea0c9dSJohn Levon 
141*a8ea0c9dSJohn Levon static int
print_xcall_msg(struct xc_msg * msg)142*a8ea0c9dSJohn Levon print_xcall_msg(struct xc_msg *msg)
143*a8ea0c9dSJohn Levon {
144*a8ea0c9dSJohn Levon 	boolean_t current = (boolean_t)msg->xc_next;
145*a8ea0c9dSJohn Levon 	char indent[] = "        ";
146*a8ea0c9dSJohn Levon 	const char *cmd;
147*a8ea0c9dSJohn Levon 
148*a8ea0c9dSJohn Levon 	switch (msg->xc_command) {
149*a8ea0c9dSJohn Levon 		case XC_MSG_ASYNC: cmd = "ASYNC"; break;
150*a8ea0c9dSJohn Levon 		case XC_MSG_CALL: cmd = "CALL"; break;
151*a8ea0c9dSJohn Levon 		case XC_MSG_SYNC: cmd = "SYNC"; break;
152*a8ea0c9dSJohn Levon 		case XC_MSG_FREE:cmd = "FREE"; break;
153*a8ea0c9dSJohn Levon 		case XC_MSG_WAITING: cmd = "WAITING"; break;
154*a8ea0c9dSJohn Levon 		case XC_MSG_RELEASED: cmd = "RELEASED"; break;
155*a8ea0c9dSJohn Levon 		case XC_MSG_DONE: cmd = "DONE"; break;
156*a8ea0c9dSJohn Levon 		default: cmd = "?"; break;
157*a8ea0c9dSJohn Levon 	}
158*a8ea0c9dSJohn Levon 
159*a8ea0c9dSJohn Levon 	mdb_printf("%s %s%-*s %-6u\n", indent, current ? "*" : "",
160*a8ea0c9dSJohn Levon 	    9 - current, cmd, msg->xc_slave);
161*a8ea0c9dSJohn Levon 	return (0);
162*a8ea0c9dSJohn Levon }
163*a8ea0c9dSJohn Levon 
164*a8ea0c9dSJohn Levon /*
165*a8ea0c9dSJohn Levon  * Show all xcall messages where the master is the given CPU.
166*a8ea0c9dSJohn Levon  *
167*a8ea0c9dSJohn Levon  * As non-free messages can be on the slave's ->xc_msgbox or ->xc_curmsg, we
168*a8ea0c9dSJohn Levon  * need to walk across all of them to find each message where ->xc_master
169*a8ea0c9dSJohn Levon  * is our CPU ID.
170*a8ea0c9dSJohn Levon  */
171*a8ea0c9dSJohn Levon int
xcall_dcmd(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)172*a8ea0c9dSJohn Levon xcall_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
173*a8ea0c9dSJohn Levon {
174*a8ea0c9dSJohn Levon 	mdb_xcall_cpu_t xcpu = { 0, };
175*a8ea0c9dSJohn Levon 	xcall_data_t data = { 0, };
176*a8ea0c9dSJohn Levon 
177*a8ea0c9dSJohn Levon 	if (mdb_getopts(argc, argv, NULL) != argc)
178*a8ea0c9dSJohn Levon 		return (DCMD_USAGE);
179*a8ea0c9dSJohn Levon 
180*a8ea0c9dSJohn Levon 	/*
181*a8ea0c9dSJohn Levon 	 * Yep, this will re-collect all the messages each time.  Shrug.
182*a8ea0c9dSJohn Levon 	 */
183*a8ea0c9dSJohn Levon 	if (!(flags & DCMD_ADDRSPEC)) {
184*a8ea0c9dSJohn Levon 		if (mdb_pwalk_dcmd("cpu", "xcall", argc, argv, 0) == -1) {
185*a8ea0c9dSJohn Levon 			mdb_warn("can't walk CPUs");
186*a8ea0c9dSJohn Levon 			return (DCMD_ERR);
187*a8ea0c9dSJohn Levon 		}
188*a8ea0c9dSJohn Levon 
189*a8ea0c9dSJohn Levon 		return (DCMD_OK);
190*a8ea0c9dSJohn Levon 	}
191*a8ea0c9dSJohn Levon 
192*a8ea0c9dSJohn Levon 	if (addr < NCPU && cpu_id_to_addr((processorid_t)addr, &addr) != 0) {
193*a8ea0c9dSJohn Levon 		mdb_warn("invalid CPU ID %lu\n", addr);
194*a8ea0c9dSJohn Levon 		return (DCMD_ERR);
195*a8ea0c9dSJohn Levon 	}
196*a8ea0c9dSJohn Levon 
197*a8ea0c9dSJohn Levon 	if (mdb_ctf_vread(&xcpu, "unix`cpu_t", "mdb_xcall_cpu_t",
198*a8ea0c9dSJohn Levon 	    addr, MDB_CTF_VREAD_IGNORE_ABSENT) == -1) {
199*a8ea0c9dSJohn Levon 		mdb_warn("couldn't read cpu 0x%lx", addr);
200*a8ea0c9dSJohn Levon 		return (DCMD_ERR);
201*a8ea0c9dSJohn Levon 	}
202*a8ea0c9dSJohn Levon 
203*a8ea0c9dSJohn Levon 	data.xd_cpu_id = xcpu.cpu_id;
204*a8ea0c9dSJohn Levon 	data.xd_flags = flags;
205*a8ea0c9dSJohn Levon 
206*a8ea0c9dSJohn Levon 	if (mdb_pwalk("cpu", xcall_get_msgs, &data, (uintptr_t)NULL) == -1) {
207*a8ea0c9dSJohn Levon 		mdb_warn("can't walk CPUs");
208*a8ea0c9dSJohn Levon 		return (DCMD_ERR);
209*a8ea0c9dSJohn Levon 	}
210*a8ea0c9dSJohn Levon 
211*a8ea0c9dSJohn Levon 	if (flags & DCMD_PIPE_OUT)
212*a8ea0c9dSJohn Levon 		return (DCMD_OK);
213*a8ea0c9dSJohn Levon 
214*a8ea0c9dSJohn Levon 	if (DCMD_HDRSPEC(flags))
215*a8ea0c9dSJohn Levon 		mdb_printf("%<u>%3s %4s %s%</u>\n", "CPU", "PEND", "HANDLER");
216*a8ea0c9dSJohn Levon 
217*a8ea0c9dSJohn Levon 	if (data.xd_msg_index == 0) {
218*a8ea0c9dSJohn Levon 		mdb_printf("%3d %4d -\n",
219*a8ea0c9dSJohn Levon 		    xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt);
220*a8ea0c9dSJohn Levon 		return (DCMD_OK);
221*a8ea0c9dSJohn Levon 	}
222*a8ea0c9dSJohn Levon 
223*a8ea0c9dSJohn Levon 	mdb_printf("%3d %4d %a(%a, %a, %a)\n",
224*a8ea0c9dSJohn Levon 	    xcpu.cpu_id, xcpu.cpu_m.xc_work_cnt,
225*a8ea0c9dSJohn Levon 	    xcpu.cpu_m.xc_data.xc_func, xcpu.cpu_m.xc_data.xc_a1,
226*a8ea0c9dSJohn Levon 	    xcpu.cpu_m.xc_data.xc_a2, xcpu.cpu_m.xc_data.xc_a3);
227*a8ea0c9dSJohn Levon 
228*a8ea0c9dSJohn Levon 	if (!(flags & DCMD_PIPE_OUT))
229*a8ea0c9dSJohn Levon 		mdb_printf("         %<u>%-9s %-6s%</u>\n", "COMMAND", "SLAVE");
230*a8ea0c9dSJohn Levon 
231*a8ea0c9dSJohn Levon 	for (size_t i = 0; i < data.xd_msg_index; i++) {
232*a8ea0c9dSJohn Levon 		if (print_xcall_msg(&data.xd_msgs[i]) != 0)
233*a8ea0c9dSJohn Levon 			return (DCMD_ERR);
234*a8ea0c9dSJohn Levon 	}
235*a8ea0c9dSJohn Levon 
236*a8ea0c9dSJohn Levon 	return (DCMD_OK);
237*a8ea0c9dSJohn Levon }
238