blob: 6a8193310e20bb165e2c468beee7dfb4a1faeccc [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>
32#include <fcntl.h>
33#include <getopt.h>
34#include <libgen.h>
35#include <limits.h>
36#include <linux/reboot.h>
37#include <stdlib.h>
38#include <string.h>
39#include <sys/reboot.h>
40#include <sys/stat.h>
41#include <sys/types.h>
42#include <unistd.h>
43
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103044struct debug_sink_ops {
45 void (*debug)(void *ctx);
46 void (*reboot)(void *ctx);
47};
48
49struct debug_sink {
50 const struct debug_sink_ops *ops;
51 void *ctx;
52};
53
54struct debug_sink_sysrq {
55 int sink;
56};
57
58static void sysrq_sink_debug(void *ctx)
Andrew Jefferydb47cd72022-01-13 14:14:41 +103059{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103060 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103061 /* 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 +103062 static const char action = 'c';
63 ssize_t rc;
64
65 sync();
66
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103067 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jefferydb47cd72022-01-13 14:14:41 +103068 return;
69
70 if (rc == -1) {
71 warn("Failed to execute debug command");
72 } else {
73 warnx("Failed to execute debug command: %zd", rc);
74 }
75}
76
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103077static void sysrq_sink_reboot(void *ctx)
Andrew Jeffery210ad632022-01-13 14:16:53 +103078{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103079 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103080 /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
81 static const char action = 'b';
Andrew Jeffery210ad632022-01-13 14:16:53 +103082 ssize_t rc;
83
84 sync();
85
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103086 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jeffery30b64962022-01-19 16:58:28 +103087 return;
88
89 if (rc == -1) {
90 warn("Failed to reboot BMC");
91 } else {
92 warnx("Failed to reboot BMC: %zd", rc);
Andrew Jeffery210ad632022-01-13 14:16:53 +103093 }
94}
95
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103096const struct debug_sink_ops sysrq_sink_ops = {
97 .debug = sysrq_sink_debug,
98 .reboot = sysrq_sink_reboot,
99};
100
101static int process(int source, struct debug_sink *sink)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930102{
103 ssize_t ingress;
104 char command;
105
106 while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930107 switch (command) {
Andrew Jefferydb47cd72022-01-13 14:14:41 +1030108 case 'D':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030109 sink->ops->debug(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930110 break;
111 case 'R':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030112 sink->ops->reboot(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930113 break;
114 default:
115 warnx("Unexpected command: 0x%02x (%c)", command, command);
116 }
117 }
118
119 if (ingress == -1)
120 warn("Failed to read from source");
121
122 return ingress;
123}
124
125int main(int argc, char * const argv[])
126{
Andrew Jefferye998ba72022-01-13 14:44:04 +1030127 const char *sink_actions = NULL;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030128 struct debug_sink_sysrq sysrq;
129 struct debug_sink sink;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930130 char devnode[PATH_MAX];
131 char *devid;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030132 int sourcefd;
133 int sinkfd;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930134
Andrew Jefferye998ba72022-01-13 14:44:04 +1030135 /* Option processing */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930136 while (1) {
137 static struct option long_options[] = {
Andrew Jefferye998ba72022-01-13 14:44:04 +1030138 {"sink-actions", required_argument, 0, 's'},
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930139 {0, 0, 0, 0},
140 };
141 int c;
142
143 c = getopt_long(argc, argv, "", long_options, NULL);
144 if (c == -1)
145 break;
Andrew Jefferye998ba72022-01-13 14:44:04 +1030146
147 switch (c) {
148 case 's':
149 sink_actions = optarg;
150 break;
151 default:
152 break;
153 }
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930154 }
155
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030156 /*
Andrew Jefferye998ba72022-01-13 14:44:04 +1030157 * The default behaviour sets the source file descriptor as stdin and the sink file
158 * descriptor as stdout. This allows trivial testing on the command-line with just a
159 * keyboard and without crashing the system.
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030160 */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030161 sourcefd = 0;
162 sinkfd = 1;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930163
Andrew Jefferye998ba72022-01-13 14:44:04 +1030164 /* Handle the source path argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930165 if (optind < argc) {
166 char devpath[PATH_MAX];
167
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030168 /*
169 * To make our lives easy with udev we take the basename of the source argument and
170 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
171 * rule to pass the device of interest to the systemd unit.
172 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930173 strncpy(devpath, argv[optind], sizeof(devpath));
174 devpath[PATH_MAX - 1] = '\0';
175 devid = basename(devpath);
176
177 strncpy(devnode, "/dev/", sizeof(devnode));
178 strncat(devnode, devid, sizeof(devnode));
179 devnode[PATH_MAX - 1] = '\0';
180
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030181 if ((sourcefd = open(devnode, O_RDONLY)) == -1)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930182 err(EXIT_FAILURE, "Failed to open %s", devnode);
183
184 optind++;
185 }
186
Andrew Jefferye998ba72022-01-13 14:44:04 +1030187 /*
188 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
189 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
190 * been passed, do as we're told and use the 'sysrq' sink actions.
191 */
192 if (!sink_actions || !strcmp("sysrq", sink_actions)) {
193 if (optind < argc) {
194 /*
195 * Just open the sink path directly. If we ever need different behaviour
196 * then we patch this bit when we know what we need.
197 */
198 if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
199 err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930200
Andrew Jefferye998ba72022-01-13 14:44:04 +1030201 optind++;
202 }
203
204 sysrq.sink = sinkfd;
205 sink.ops = &sysrq_sink_ops;
206 sink.ctx = &sysrq;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930207 }
208
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030209 /* Check we're done with the command-line */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930210 if (optind < argc)
211 err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
212
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030213 /* Trigger the actions on the sink when we receive an event from the source */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030214 if (process(sourcefd, &sink) < 0)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930215 errx(EXIT_FAILURE, "Failure while processing command stream");
216
217 return 0;
218}