blob: 871da1de4e4efa5a809ab95609327c5291a0064d [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 Jefferydb47cd72022-01-13 14:14:41 +103032static void process_debug(int sink)
33{
Andrew Jeffery30b64962022-01-19 16:58:28 +103034 /* 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 +103035 static const char action = 'c';
36 ssize_t rc;
37
38 sync();
39
40 if ((rc = write(sink, &action, sizeof(action))) == sizeof(action))
41 return;
42
43 if (rc == -1) {
44 warn("Failed to execute debug command");
45 } else {
46 warnx("Failed to execute debug command: %zd", rc);
47 }
48}
49
Andrew Jeffery30b64962022-01-19 16:58:28 +103050static void process_reboot(int sink)
Andrew Jeffery210ad632022-01-13 14:16:53 +103051{
Andrew Jeffery30b64962022-01-19 16:58:28 +103052 /* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
53 static const char action = 'b';
Andrew Jeffery210ad632022-01-13 14:16:53 +103054 ssize_t rc;
55
56 sync();
57
Andrew Jeffery30b64962022-01-19 16:58:28 +103058 if ((rc = write(sink, &action, sizeof(action))) == sizeof(action))
59 return;
60
61 if (rc == -1) {
62 warn("Failed to reboot BMC");
63 } else {
64 warnx("Failed to reboot BMC: %zd", rc);
Andrew Jeffery210ad632022-01-13 14:16:53 +103065 }
66}
67
Andrew Jeffery11cd2542021-05-03 11:03:30 +093068static int process(int source, int sink)
69{
70 ssize_t ingress;
71 char command;
72
73 while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
Andrew Jeffery11cd2542021-05-03 11:03:30 +093074 switch (command) {
Andrew Jefferydb47cd72022-01-13 14:14:41 +103075 case 'D':
76 process_debug(sink);
Andrew Jeffery11cd2542021-05-03 11:03:30 +093077 break;
78 case 'R':
Andrew Jeffery210ad632022-01-13 14:16:53 +103079 process_reboot(sink);
Andrew Jeffery11cd2542021-05-03 11:03:30 +093080 break;
81 default:
82 warnx("Unexpected command: 0x%02x (%c)", command, command);
83 }
84 }
85
86 if (ingress == -1)
87 warn("Failed to read from source");
88
89 return ingress;
90}
91
92int main(int argc, char * const argv[])
93{
94 char devnode[PATH_MAX];
95 char *devid;
96 int source;
97 int sink;
98
Andrew Jefferyd65368b2022-01-19 15:59:20 +103099 /* Option processing. Currently nothing implemented, but allows us to use optind */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930100 while (1) {
101 static struct option long_options[] = {
102 {0, 0, 0, 0},
103 };
104 int c;
105
106 c = getopt_long(argc, argv, "", long_options, NULL);
107 if (c == -1)
108 break;
109 }
110
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030111 /*
112 * The default behaviour sets the source as stdin and the sink as stdout. This allows
113 * trivial testing on the command-line with just a keyboard and without crashing the system.
114 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930115 source = 0;
116 sink = 1;
117
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030118 /* Handle the source argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930119 if (optind < argc) {
120 char devpath[PATH_MAX];
121
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030122 /*
123 * To make our lives easy with udev we take the basename of the source argument and
124 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
125 * rule to pass the device of interest to the systemd unit.
126 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930127 strncpy(devpath, argv[optind], sizeof(devpath));
128 devpath[PATH_MAX - 1] = '\0';
129 devid = basename(devpath);
130
131 strncpy(devnode, "/dev/", sizeof(devnode));
132 strncat(devnode, devid, sizeof(devnode));
133 devnode[PATH_MAX - 1] = '\0';
134
135 if ((source = open(devnode, O_RDONLY)) == -1)
136 err(EXIT_FAILURE, "Failed to open %s", devnode);
137
138 optind++;
139 }
140
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030141 /* Handle the sink argument, if any */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930142 if (optind < argc) {
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030143 /*
144 * Just open the sink path directly. If we ever need different behaviour then we
145 * patch this bit when we know what we need.
146 */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930147 if ((sink = open(argv[optind], O_WRONLY)) == -1)
148 err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
149
150 optind++;
151 }
152
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030153 /* Check we're done with the command-line */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930154 if (optind < argc)
155 err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
156
Andrew Jefferyd65368b2022-01-19 15:59:20 +1030157 /* Trigger the actions on the sink when we receive an event from the source */
Andrew Jeffery11cd2542021-05-03 11:03:30 +0930158 if (process(source, sink) < 0)
159 errx(EXIT_FAILURE, "Failure while processing command stream");
160
161 return 0;
162}