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