111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0 211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp. 311cd254bSAndrew Jeffery 4d65368beSAndrew Jeffery /* 5e998ba77SAndrew Jeffery * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When a 6d65368beSAndrew Jeffery * signal is received it triggers a crash to collect debug data and reboots the system in the hope 7d65368beSAndrew Jeffery * that it will recover. 8d65368beSAndrew Jeffery * 9d65368beSAndrew Jeffery * Usage: debug-trigger [SOURCE] [SINK] 10d65368beSAndrew Jeffery * 11e998ba77SAndrew Jeffery * Options: 12e998ba77SAndrew Jeffery * --sink-actions=ACTION 13e998ba77SAndrew Jeffery * Set the class of sink action(s) to be used. Defaults to 'sysrq' 14e998ba77SAndrew Jeffery * 15d65368beSAndrew Jeffery * Examples: 16d65368beSAndrew Jeffery * debug-trigger 17e998ba77SAndrew Jeffery * Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink 18e998ba77SAndrew Jeffery * actions. Useful for testing. 19e998ba77SAndrew Jeffery * 20e998ba77SAndrew Jeffery * debug-trigger --sink-actions=sysrq 21e998ba77SAndrew Jeffery * Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the 22e998ba77SAndrew Jeffery * sink. 23d65368beSAndrew Jeffery * 24d65368beSAndrew Jeffery * debug-trigger /dev/serio_raw0 /proc/sysrq-trigger 25e998ba77SAndrew Jeffery * Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default 26e998ba77SAndrew Jeffery * 'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to 27e998ba77SAndrew Jeffery * /proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will 28e998ba77SAndrew Jeffery * be written to /proc/sysrq-trigger, causing an immediate reboot of the system. 29d65368beSAndrew Jeffery */ 30d65368beSAndrew Jeffery 3111cd254bSAndrew Jeffery #include <err.h> 32*b1ea254eSAndrew Jeffery #include <errno.h> 3311cd254bSAndrew Jeffery #include <fcntl.h> 3411cd254bSAndrew Jeffery #include <getopt.h> 3511cd254bSAndrew Jeffery #include <libgen.h> 3611cd254bSAndrew Jeffery #include <limits.h> 3711cd254bSAndrew Jeffery #include <linux/reboot.h> 3811cd254bSAndrew Jeffery #include <stdlib.h> 3911cd254bSAndrew Jeffery #include <string.h> 4011cd254bSAndrew Jeffery #include <sys/reboot.h> 4111cd254bSAndrew Jeffery #include <sys/stat.h> 4211cd254bSAndrew Jeffery #include <sys/types.h> 4311cd254bSAndrew Jeffery #include <unistd.h> 4411cd254bSAndrew Jeffery 45*b1ea254eSAndrew Jeffery struct debug_source_ops { 46*b1ea254eSAndrew Jeffery int (*poll)(void *ctx, char *op); 47*b1ea254eSAndrew Jeffery }; 48*b1ea254eSAndrew Jeffery 49*b1ea254eSAndrew Jeffery struct debug_source { 50*b1ea254eSAndrew Jeffery const struct debug_source_ops *ops; 51*b1ea254eSAndrew Jeffery void *ctx; 52*b1ea254eSAndrew Jeffery }; 53*b1ea254eSAndrew Jeffery 54*b1ea254eSAndrew Jeffery struct debug_source_basic { 55*b1ea254eSAndrew Jeffery int source; 56*b1ea254eSAndrew Jeffery }; 57*b1ea254eSAndrew Jeffery 581dc6adc9SAndrew Jeffery struct debug_sink_ops { 591dc6adc9SAndrew Jeffery void (*debug)(void *ctx); 601dc6adc9SAndrew Jeffery void (*reboot)(void *ctx); 611dc6adc9SAndrew Jeffery }; 621dc6adc9SAndrew Jeffery 631dc6adc9SAndrew Jeffery struct debug_sink { 641dc6adc9SAndrew Jeffery const struct debug_sink_ops *ops; 651dc6adc9SAndrew Jeffery void *ctx; 661dc6adc9SAndrew Jeffery }; 671dc6adc9SAndrew Jeffery 681dc6adc9SAndrew Jeffery struct debug_sink_sysrq { 691dc6adc9SAndrew Jeffery int sink; 701dc6adc9SAndrew Jeffery }; 711dc6adc9SAndrew Jeffery 721dc6adc9SAndrew Jeffery static void sysrq_sink_debug(void *ctx) 73db47cd7fSAndrew Jeffery { 741dc6adc9SAndrew Jeffery struct debug_sink_sysrq *sysrq = ctx; 7530b6496aSAndrew Jeffery /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */ 76db47cd7fSAndrew Jeffery static const char action = 'c'; 77db47cd7fSAndrew Jeffery ssize_t rc; 78db47cd7fSAndrew Jeffery 79db47cd7fSAndrew Jeffery sync(); 80db47cd7fSAndrew Jeffery 811dc6adc9SAndrew Jeffery if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action)) 82db47cd7fSAndrew Jeffery return; 83db47cd7fSAndrew Jeffery 84db47cd7fSAndrew Jeffery if (rc == -1) { 85db47cd7fSAndrew Jeffery warn("Failed to execute debug command"); 86db47cd7fSAndrew Jeffery } else { 87db47cd7fSAndrew Jeffery warnx("Failed to execute debug command: %zd", rc); 88db47cd7fSAndrew Jeffery } 89db47cd7fSAndrew Jeffery } 90db47cd7fSAndrew Jeffery 911dc6adc9SAndrew Jeffery static void sysrq_sink_reboot(void *ctx) 9211cd254bSAndrew Jeffery { 931dc6adc9SAndrew Jeffery struct debug_sink_sysrq *sysrq = ctx; 9430b6496aSAndrew Jeffery /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */ 9530b6496aSAndrew Jeffery static const char action = 'b'; 9611cd254bSAndrew Jeffery ssize_t rc; 9711cd254bSAndrew Jeffery 9811cd254bSAndrew Jeffery sync(); 9911cd254bSAndrew Jeffery 1001dc6adc9SAndrew Jeffery if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action)) 10130b6496aSAndrew Jeffery return; 10230b6496aSAndrew Jeffery 10330b6496aSAndrew Jeffery if (rc == -1) { 10411cd254bSAndrew Jeffery warn("Failed to reboot BMC"); 10530b6496aSAndrew Jeffery } else { 10611cd254bSAndrew Jeffery warnx("Failed to reboot BMC: %zd", rc); 10711cd254bSAndrew Jeffery } 108210ad636SAndrew Jeffery } 109210ad636SAndrew Jeffery 1101dc6adc9SAndrew Jeffery const struct debug_sink_ops sysrq_sink_ops = { 1111dc6adc9SAndrew Jeffery .debug = sysrq_sink_debug, 1121dc6adc9SAndrew Jeffery .reboot = sysrq_sink_reboot, 1131dc6adc9SAndrew Jeffery }; 1141dc6adc9SAndrew Jeffery 115*b1ea254eSAndrew Jeffery static int basic_source_poll(void *ctx, char *op) 116210ad636SAndrew Jeffery { 117*b1ea254eSAndrew Jeffery struct debug_source_basic *basic = ctx; 118210ad636SAndrew Jeffery ssize_t ingress; 119210ad636SAndrew Jeffery 120*b1ea254eSAndrew Jeffery if ((ingress = read(basic->source, op, 1)) != 1) { 121*b1ea254eSAndrew Jeffery if (ingress < 0) { 122*b1ea254eSAndrew Jeffery warn("Failed to read from basic source"); 123*b1ea254eSAndrew Jeffery return -errno; 124*b1ea254eSAndrew Jeffery } 125*b1ea254eSAndrew Jeffery 126*b1ea254eSAndrew Jeffery /* Unreachable */ 127*b1ea254eSAndrew Jeffery errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress); 128*b1ea254eSAndrew Jeffery } 129*b1ea254eSAndrew Jeffery 130*b1ea254eSAndrew Jeffery return 0; 131*b1ea254eSAndrew Jeffery } 132*b1ea254eSAndrew Jeffery 133*b1ea254eSAndrew Jeffery const struct debug_source_ops basic_source_ops = { 134*b1ea254eSAndrew Jeffery .poll = basic_source_poll, 135*b1ea254eSAndrew Jeffery }; 136*b1ea254eSAndrew Jeffery 137*b1ea254eSAndrew Jeffery static int process(struct debug_source *source, struct debug_sink *sink) 138*b1ea254eSAndrew Jeffery { 139*b1ea254eSAndrew Jeffery char command; 140*b1ea254eSAndrew Jeffery int rc; 141*b1ea254eSAndrew Jeffery 142*b1ea254eSAndrew Jeffery while (!(rc = source->ops->poll(source->ctx, &command))) { 143210ad636SAndrew Jeffery switch (command) { 144210ad636SAndrew Jeffery case 'D': 1451dc6adc9SAndrew Jeffery sink->ops->debug(sink->ctx); 146210ad636SAndrew Jeffery break; 147210ad636SAndrew Jeffery case 'R': 1481dc6adc9SAndrew Jeffery sink->ops->reboot(sink->ctx); 14911cd254bSAndrew Jeffery break; 15011cd254bSAndrew Jeffery default: 15111cd254bSAndrew Jeffery warnx("Unexpected command: 0x%02x (%c)", command, command); 15211cd254bSAndrew Jeffery } 15311cd254bSAndrew Jeffery } 15411cd254bSAndrew Jeffery 155*b1ea254eSAndrew Jeffery if (rc < 0) 156*b1ea254eSAndrew Jeffery warnx("Failed to poll source: %s", strerror(-rc)); 15711cd254bSAndrew Jeffery 158*b1ea254eSAndrew Jeffery return rc; 15911cd254bSAndrew Jeffery } 16011cd254bSAndrew Jeffery 16111cd254bSAndrew Jeffery int main(int argc, char * const argv[]) 16211cd254bSAndrew Jeffery { 163e998ba77SAndrew Jeffery const char *sink_actions = NULL; 164*b1ea254eSAndrew Jeffery struct debug_source_basic basic; 1651dc6adc9SAndrew Jeffery struct debug_sink_sysrq sysrq; 166*b1ea254eSAndrew Jeffery struct debug_source source; 1671dc6adc9SAndrew Jeffery struct debug_sink sink; 16811cd254bSAndrew Jeffery char devnode[PATH_MAX]; 16911cd254bSAndrew Jeffery char *devid; 1701dc6adc9SAndrew Jeffery int sourcefd; 1711dc6adc9SAndrew Jeffery int sinkfd; 17211cd254bSAndrew Jeffery 173e998ba77SAndrew Jeffery /* Option processing */ 17411cd254bSAndrew Jeffery while (1) { 17511cd254bSAndrew Jeffery static struct option long_options[] = { 176e998ba77SAndrew Jeffery {"sink-actions", required_argument, 0, 's'}, 17711cd254bSAndrew Jeffery {0, 0, 0, 0}, 17811cd254bSAndrew Jeffery }; 17911cd254bSAndrew Jeffery int c; 18011cd254bSAndrew Jeffery 18111cd254bSAndrew Jeffery c = getopt_long(argc, argv, "", long_options, NULL); 18211cd254bSAndrew Jeffery if (c == -1) 18311cd254bSAndrew Jeffery break; 184e998ba77SAndrew Jeffery 185e998ba77SAndrew Jeffery switch (c) { 186e998ba77SAndrew Jeffery case 's': 187e998ba77SAndrew Jeffery sink_actions = optarg; 188e998ba77SAndrew Jeffery break; 189e998ba77SAndrew Jeffery default: 190e998ba77SAndrew Jeffery break; 191e998ba77SAndrew Jeffery } 19211cd254bSAndrew Jeffery } 19311cd254bSAndrew Jeffery 194d65368beSAndrew Jeffery /* 195e998ba77SAndrew Jeffery * The default behaviour sets the source file descriptor as stdin and the sink file 196e998ba77SAndrew Jeffery * descriptor as stdout. This allows trivial testing on the command-line with just a 197e998ba77SAndrew Jeffery * keyboard and without crashing the system. 198d65368beSAndrew Jeffery */ 1991dc6adc9SAndrew Jeffery sourcefd = 0; 2001dc6adc9SAndrew Jeffery sinkfd = 1; 20111cd254bSAndrew Jeffery 202e998ba77SAndrew Jeffery /* Handle the source path argument, if any */ 20311cd254bSAndrew Jeffery if (optind < argc) { 20411cd254bSAndrew Jeffery char devpath[PATH_MAX]; 20511cd254bSAndrew Jeffery 206d65368beSAndrew Jeffery /* 207d65368beSAndrew Jeffery * To make our lives easy with udev we take the basename of the source argument and 208d65368beSAndrew Jeffery * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev 209d65368beSAndrew Jeffery * rule to pass the device of interest to the systemd unit. 210d65368beSAndrew Jeffery */ 21111cd254bSAndrew Jeffery strncpy(devpath, argv[optind], sizeof(devpath)); 21211cd254bSAndrew Jeffery devpath[PATH_MAX - 1] = '\0'; 21311cd254bSAndrew Jeffery devid = basename(devpath); 21411cd254bSAndrew Jeffery 21511cd254bSAndrew Jeffery strncpy(devnode, "/dev/", sizeof(devnode)); 21611cd254bSAndrew Jeffery strncat(devnode, devid, sizeof(devnode)); 21711cd254bSAndrew Jeffery devnode[PATH_MAX - 1] = '\0'; 21811cd254bSAndrew Jeffery 2191dc6adc9SAndrew Jeffery if ((sourcefd = open(devnode, O_RDONLY)) == -1) 22011cd254bSAndrew Jeffery err(EXIT_FAILURE, "Failed to open %s", devnode); 22111cd254bSAndrew Jeffery 22211cd254bSAndrew Jeffery optind++; 22311cd254bSAndrew Jeffery } 22411cd254bSAndrew Jeffery 225e998ba77SAndrew Jeffery /* 226e998ba77SAndrew Jeffery * Handle the sink path argument, if any. If sink_actions hasn't been set via the 227e998ba77SAndrew Jeffery * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has 228e998ba77SAndrew Jeffery * been passed, do as we're told and use the 'sysrq' sink actions. 229e998ba77SAndrew Jeffery */ 230e998ba77SAndrew Jeffery if (!sink_actions || !strcmp("sysrq", sink_actions)) { 23111cd254bSAndrew Jeffery if (optind < argc) { 232d65368beSAndrew Jeffery /* 233e998ba77SAndrew Jeffery * Just open the sink path directly. If we ever need different behaviour 234e998ba77SAndrew Jeffery * then we patch this bit when we know what we need. 235d65368beSAndrew Jeffery */ 2361dc6adc9SAndrew Jeffery if ((sinkfd = open(argv[optind], O_WRONLY)) == -1) 23711cd254bSAndrew Jeffery err(EXIT_FAILURE, "Failed to open %s", argv[optind]); 23811cd254bSAndrew Jeffery 23911cd254bSAndrew Jeffery optind++; 24011cd254bSAndrew Jeffery } 24111cd254bSAndrew Jeffery 2421dc6adc9SAndrew Jeffery sysrq.sink = sinkfd; 2431dc6adc9SAndrew Jeffery sink.ops = &sysrq_sink_ops; 2441dc6adc9SAndrew Jeffery sink.ctx = &sysrq; 245e998ba77SAndrew Jeffery } 246e998ba77SAndrew Jeffery 247e998ba77SAndrew Jeffery /* Check we're done with the command-line */ 248e998ba77SAndrew Jeffery if (optind < argc) 249e998ba77SAndrew Jeffery err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind); 2501dc6adc9SAndrew Jeffery 251*b1ea254eSAndrew Jeffery basic.source = sourcefd; 252*b1ea254eSAndrew Jeffery source.ops = &basic_source_ops; 253*b1ea254eSAndrew Jeffery source.ctx = &basic; 254*b1ea254eSAndrew Jeffery 255d65368beSAndrew Jeffery /* Trigger the actions on the sink when we receive an event from the source */ 256*b1ea254eSAndrew Jeffery if (process(&source, &sink) < 0) 25711cd254bSAndrew Jeffery errx(EXIT_FAILURE, "Failure while processing command stream"); 25811cd254bSAndrew Jeffery 25911cd254bSAndrew Jeffery return 0; 26011cd254bSAndrew Jeffery } 261