blob: c3408c0fa92057629d9c7078cb389a4923ac6328 [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/*
5 * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When the
6 * 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 *
11 * Examples:
12 * debug-trigger
13 * Set the source as stdin and the sink as stdout. Useful for testing.
14 *
15 * debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
16 * Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink.
17 */
18
Andrew Jeffery11cd2542021-05-03 11:03:30 +093019#include <err.h>
20#include <fcntl.h>
21#include <getopt.h>
22#include <libgen.h>
23#include <limits.h>
24#include <linux/reboot.h>
25#include <stdlib.h>
26#include <string.h>
27#include <sys/reboot.h>
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <unistd.h>
31
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103032struct debug_sink_ops {
33 void (*debug)(void *ctx);
34 void (*reboot)(void *ctx);
35};
36
37struct debug_sink {
38 const struct debug_sink_ops *ops;
39 void *ctx;
40};
41
42struct debug_sink_sysrq {
43 int sink;
44};
45
46static void sysrq_sink_debug(void *ctx)
Andrew Jefferydb47cd72022-01-13 14:14:41 +103047{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103048 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103049 /* 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 +103050 static const char action = 'c';
51 ssize_t rc;
52
53 sync();
54
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103055 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jefferydb47cd72022-01-13 14:14:41 +103056 return;
57
58 if (rc == -1) {
59 warn("Failed to execute debug command");
60 } else {
61 warnx("Failed to execute debug command: %zd", rc);
62 }
63}
64
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103065static void sysrq_sink_reboot(void *ctx)
Andrew Jeffery210ad632022-01-13 14:16:53 +103066{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103067 struct debug_sink_sysrq *sysrq = ctx;
Andrew Jeffery30b64962022-01-19 16:58:28 +103068 /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
69 static const char action = 'b';
Andrew Jeffery210ad632022-01-13 14:16:53 +103070 ssize_t rc;
71
72 sync();
73
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103074 if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
Andrew Jeffery30b64962022-01-19 16:58:28 +103075 return;
76
77 if (rc == -1) {
78 warn("Failed to reboot BMC");
79 } else {
80 warnx("Failed to reboot BMC: %zd", rc);
Andrew Jeffery210ad632022-01-13 14:16:53 +103081 }
82}
83
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103084const struct debug_sink_ops sysrq_sink_ops = {
85 .debug = sysrq_sink_debug,
86 .reboot = sysrq_sink_reboot,
87};
88
89static int process(int source, struct debug_sink *sink)
Andrew Jeffery11cd2542021-05-03 11:03:30 +093090{
91 ssize_t ingress;
92 char command;
93
94 while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
Andrew Jeffery11cd2542021-05-03 11:03:30 +093095 switch (command) {
Andrew Jefferydb47cd72022-01-13 14:14:41 +103096 case 'D':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +103097 sink->ops->debug(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +093098 break;
99 case 'R':
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030100 sink->ops->reboot(sink->ctx);
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930101 break;
102 default:
103 warnx("Unexpected command: 0x%02x (%c)", command, command);
104 }
105 }
106
107 if (ingress == -1)
108 warn("Failed to read from source");
109
110 return ingress;
111}
112
113int main(int argc, char * const argv[])
114{
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030115 struct debug_sink_sysrq sysrq;
116 struct debug_sink sink;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930117 char devnode[PATH_MAX];
118 char *devid;
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030119 int sourcefd;
120 int sinkfd;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930121
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030122 /* Option processing. Currently nothing implemented, but allows us to use optind */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930123 while (1) {
124 static struct option long_options[] = {
125 {0, 0, 0, 0},
126 };
127 int c;
128
129 c = getopt_long(argc, argv, "", long_options, NULL);
130 if (c == -1)
131 break;
132 }
133
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030134 /*
135 * The default behaviour sets the source as stdin and the sink as stdout. This allows
136 * trivial testing on the command-line with just a keyboard and without crashing the system.
137 */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030138 sourcefd = 0;
139 sinkfd = 1;
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930140
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030141 /* Handle the source argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930142 if (optind < argc) {
143 char devpath[PATH_MAX];
144
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030145 /*
146 * To make our lives easy with udev we take the basename of the source argument and
147 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
148 * rule to pass the device of interest to the systemd unit.
149 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930150 strncpy(devpath, argv[optind], sizeof(devpath));
151 devpath[PATH_MAX - 1] = '\0';
152 devid = basename(devpath);
153
154 strncpy(devnode, "/dev/", sizeof(devnode));
155 strncat(devnode, devid, sizeof(devnode));
156 devnode[PATH_MAX - 1] = '\0';
157
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030158 if ((sourcefd = open(devnode, O_RDONLY)) == -1)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930159 err(EXIT_FAILURE, "Failed to open %s", devnode);
160
161 optind++;
162 }
163
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030164 /* Handle the sink argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930165 if (optind < argc) {
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030166 /*
167 * Just open the sink path directly. If we ever need different behaviour then we
168 * patch this bit when we know what we need.
169 */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030170 if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930171 err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
172
173 optind++;
174 }
175
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030176 /* Check we're done with the command-line */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930177 if (optind < argc)
178 err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
179
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030180 sysrq.sink = sinkfd;
181 sink.ops = &sysrq_sink_ops;
182 sink.ctx = &sysrq;
183
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030184 /* Trigger the actions on the sink when we receive an event from the source */
Andrew Jeffery1dc6adc2022-01-13 14:34:37 +1030185 if (process(sourcefd, &sink) < 0)
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930186 errx(EXIT_FAILURE, "Failure while processing command stream");
187
188 return 0;
189}