blob: c06403524cf7ce0bfb71687760f1b1dc9e2b69ca [file] [log] [blame]
Andrew Jeffery11cd2542021-05-03 11:03:30 +09301// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2021 IBM Corp.
3
Andrew Jefferyd65368b2022-01-19 15:59:20 +10304/*
Andrew Jefferye998ba72022-01-13 14:44:04 +10305 * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When a
Andrew Jefferyd65368b2022-01-19 15:59:20 +10306 * signal is received it triggers a crash to collect debug data and reboots the system in the hope
7 * that it will recover.
8 *
9 * Usage: debug-trigger [SOURCE] [SINK]
10 *
Andrew Jefferye998ba72022-01-13 14:44:04 +103011 * Options:
12 * --sink-actions=ACTION
13 * Set the class of sink action(s) to be used. Defaults to 'sysrq'
14 *
Andrew Jefferyd65368b2022-01-19 15:59:20 +103015 * Examples:
16 * debug-trigger
Andrew Jefferye998ba72022-01-13 14:44:04 +103017 * Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink
18 * actions. Useful for testing.
19 *
20 * debug-trigger --sink-actions=sysrq
21 * Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the
22 * sink.
Andrew Jefferyd65368b2022-01-19 15:59:20 +103023 *
24 * debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
Andrew Jefferye998ba72022-01-13 14:44:04 +103025 * Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default
26 * 'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to
27 * /proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will
28 * be written to /proc/sysrq-trigger, causing an immediate reboot of the system.
Andrew Jefferyd65368b2022-01-19 15:59:20 +103029 */
30
Andrew Jeffery11cd2542021-05-03 11:03:30 +093031#include <err.h>
Andrew Jefferyb1ea2542022-01-14 15:03:22 +103032#include <errno.h>
Andrew Jeffery11cd2542021-05-03 11:03:30 +093033#include <fcntl.h>
34#include <getopt.h>
35#include <libgen.h>
36#include <limits.h>
37#include <linux/reboot.h>
38#include <stdlib.h>
39#include <string.h>
40#include <sys/reboot.h>
41#include <sys/stat.h>
42#include <sys/types.h>
43#include <unistd.h>
44
Andrew Jefferyb1ea2542022-01-14 15:03:22 +103045struct debug_source_ops {
46 int (*poll)(void *ctx, char *op);
47};
48
49struct debug_source {
50 const struct debug_source_ops *ops;
51 void *ctx;
52};
53
54struct debug_source_basic {
55 int source;
56};
57
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103058struct debug_sink_ops {
59 void (*debug)(void *ctx);
60 void (*reboot)(void *ctx);
61};
62
63struct debug_sink {
64 const struct debug_sink_ops *ops;
65 void *ctx;
66};
67
68struct debug_sink_sysrq {
69 int sink;
70};
71
72static void sysrq_sink_debug(void *ctx)
Andrew Jefferydb47cd72022-01-13 14:14:41 +103073{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103074 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103075 /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
Andrew Jefferydb47cd72022-01-13 14:14:41 +103076 static const char action = 'c';
77 ssize_t rc;
78
79 sync();
80
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103081 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jefferydb47cd72022-01-13 14:14:41 +103082 return;
83
84 if (rc == -1) {
85 warn("Failed to execute debug command");
86 } else {
87 warnx("Failed to execute debug command: %zd", rc);
88 }
89}
90
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103091static void sysrq_sink_reboot(void *ctx)
Andrew Jeffery210ad632022-01-13 14:16:53 +103092{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103093 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103094 /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
95 static const char action = 'b';
Andrew Jeffery210ad632022-01-13 14:16:53 +103096 ssize_t rc;
97
98 sync();
99
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030100 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jeffery30b64962022-01-19 16:58:28 +1030101 return;
102
103 if (rc == -1) {
104 warn("Failed to reboot BMC");
105 } else {
106 warnx("Failed to reboot BMC: %zd", rc);
Andrew Jeffery210ad632022-01-13 14:16:53 +1030107 }
108}
109
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030110const struct debug_sink_ops sysrq_sink_ops = {
111 .debug = sysrq_sink_debug,
112 .reboot = sysrq_sink_reboot,
113};
114
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030115static int basic_source_poll(void *ctx, char *op)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930116{
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030117 struct debug_source_basic *basic = ctx;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930118 ssize_t ingress;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930119
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030120 if ((ingress = read(basic->source, op, 1)) != 1) {
121 if (ingress < 0) {
122 warn("Failed to read from basic source");
123 return -errno;
124 }
125
126 /* Unreachable */
127 errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress);
128 }
129
130 return 0;
131}
132
133const struct debug_source_ops basic_source_ops = {
134 .poll = basic_source_poll,
135};
136
137static int process(struct debug_source *source, struct debug_sink *sink)
138{
139 char command;
140 int rc;
141
142 while (!(rc = source->ops->poll(source->ctx, &command))) {
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930143 switch (command) {
Andrew Jefferydb47cd72022-01-13 14:14:41 +1030144 case 'D':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030145 sink->ops->debug(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930146 break;
147 case 'R':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030148 sink->ops->reboot(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930149 break;
150 default:
151 warnx("Unexpected command: 0x%02x (%c)", command, command);
152 }
153 }
154
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030155 if (rc < 0)
156 warnx("Failed to poll source: %s", strerror(-rc));
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930157
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030158 return rc;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930159}
160
161int main(int argc, char * const argv[])
162{
Andrew Jefferye998ba72022-01-13 14:44:04 +1030163 const char *sink_actions = NULL;
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030164 struct debug_source_basic basic;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030165 struct debug_sink_sysrq sysrq;
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030166 struct debug_source source;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030167 struct debug_sink sink;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930168 char devnode[PATH_MAX];
169 char *devid;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030170 int sourcefd;
171 int sinkfd;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930172
Andrew Jefferye998ba72022-01-13 14:44:04 +1030173 /* Option processing */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930174 while (1) {
175 static struct option long_options[] = {
Andrew Jefferye998ba72022-01-13 14:44:04 +1030176 {"sink-actions", required_argument, 0, 's'},
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930177 {0, 0, 0, 0},
178 };
179 int c;
180
181 c = getopt_long(argc, argv, "", long_options, NULL);
182 if (c == -1)
183 break;
Andrew Jefferye998ba72022-01-13 14:44:04 +1030184
185 switch (c) {
186 case 's':
187 sink_actions = optarg;
188 break;
189 default:
190 break;
191 }
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930192 }
193
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030194 /*
Andrew Jefferye998ba72022-01-13 14:44:04 +1030195 * The default behaviour sets the source file descriptor as stdin and the sink file
196 * descriptor as stdout. This allows trivial testing on the command-line with just a
197 * keyboard and without crashing the system.
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030198 */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030199 sourcefd = 0;
200 sinkfd = 1;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930201
Andrew Jefferye998ba72022-01-13 14:44:04 +1030202 /* Handle the source path argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930203 if (optind < argc) {
204 char devpath[PATH_MAX];
205
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030206 /*
207 * To make our lives easy with udev we take the basename of the source argument and
208 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
209 * rule to pass the device of interest to the systemd unit.
210 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930211 strncpy(devpath, argv[optind], sizeof(devpath));
212 devpath[PATH_MAX - 1] = '\0';
213 devid = basename(devpath);
214
215 strncpy(devnode, "/dev/", sizeof(devnode));
216 strncat(devnode, devid, sizeof(devnode));
217 devnode[PATH_MAX - 1] = '\0';
218
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030219 if ((sourcefd = open(devnode, O_RDONLY)) == -1)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930220 err(EXIT_FAILURE, "Failed to open %s", devnode);
221
222 optind++;
223 }
224
Andrew Jefferye998ba72022-01-13 14:44:04 +1030225 /*
226 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
227 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
228 * been passed, do as we're told and use the 'sysrq' sink actions.
229 */
230 if (!sink_actions || !strcmp("sysrq", sink_actions)) {
231 if (optind < argc) {
232 /*
233 * Just open the sink path directly. If we ever need different behaviour
234 * then we patch this bit when we know what we need.
235 */
236 if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
237 err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930238
Andrew Jefferye998ba72022-01-13 14:44:04 +1030239 optind++;
240 }
241
242 sysrq.sink = sinkfd;
243 sink.ops = &sysrq_sink_ops;
244 sink.ctx = &sysrq;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930245 }
246
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030247 /* Check we're done with the command-line */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930248 if (optind < argc)
249 err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
250
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030251 basic.source = sourcefd;
252 source.ops = &basic_source_ops;
253 source.ctx = &basic;
254
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030255 /* Trigger the actions on the sink when we receive an event from the source */
Andrew Jefferyb1ea2542022-01-14 15:03:22 +1030256 if (process(&source, &sink) < 0)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930257 errx(EXIT_FAILURE, "Failure while processing command stream");
258
259 return 0;
260}