xref: /openbmc/debug-trigger/main.c (revision 1dc6adc90d294b0acd85c5203af266c8e22f9527)
111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0
211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp.
311cd254bSAndrew Jeffery 
4d65368beSAndrew Jeffery /*
5d65368beSAndrew Jeffery  * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When the
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  *
11d65368beSAndrew Jeffery  * Examples:
12d65368beSAndrew Jeffery  *  debug-trigger
13d65368beSAndrew Jeffery  *	Set the source as stdin and the sink as stdout. Useful for testing.
14d65368beSAndrew Jeffery  *
15d65368beSAndrew Jeffery  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
16d65368beSAndrew Jeffery  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink.
17d65368beSAndrew Jeffery  */
18d65368beSAndrew Jeffery 
1911cd254bSAndrew Jeffery #include <err.h>
2011cd254bSAndrew Jeffery #include <fcntl.h>
2111cd254bSAndrew Jeffery #include <getopt.h>
2211cd254bSAndrew Jeffery #include <libgen.h>
2311cd254bSAndrew Jeffery #include <limits.h>
2411cd254bSAndrew Jeffery #include <linux/reboot.h>
2511cd254bSAndrew Jeffery #include <stdlib.h>
2611cd254bSAndrew Jeffery #include <string.h>
2711cd254bSAndrew Jeffery #include <sys/reboot.h>
2811cd254bSAndrew Jeffery #include <sys/stat.h>
2911cd254bSAndrew Jeffery #include <sys/types.h>
3011cd254bSAndrew Jeffery #include <unistd.h>
3111cd254bSAndrew Jeffery 
32*1dc6adc9SAndrew Jeffery struct debug_sink_ops {
33*1dc6adc9SAndrew Jeffery 	void (*debug)(void *ctx);
34*1dc6adc9SAndrew Jeffery 	void (*reboot)(void *ctx);
35*1dc6adc9SAndrew Jeffery };
36*1dc6adc9SAndrew Jeffery 
37*1dc6adc9SAndrew Jeffery struct debug_sink {
38*1dc6adc9SAndrew Jeffery 	const struct debug_sink_ops *ops;
39*1dc6adc9SAndrew Jeffery 	void *ctx;
40*1dc6adc9SAndrew Jeffery };
41*1dc6adc9SAndrew Jeffery 
42*1dc6adc9SAndrew Jeffery struct debug_sink_sysrq {
43*1dc6adc9SAndrew Jeffery 	int sink;
44*1dc6adc9SAndrew Jeffery };
45*1dc6adc9SAndrew Jeffery 
46*1dc6adc9SAndrew Jeffery static void sysrq_sink_debug(void *ctx)
47db47cd7fSAndrew Jeffery {
48*1dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
4930b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
50db47cd7fSAndrew Jeffery 	static const char action = 'c';
51db47cd7fSAndrew Jeffery 	ssize_t rc;
52db47cd7fSAndrew Jeffery 
53db47cd7fSAndrew Jeffery 	sync();
54db47cd7fSAndrew Jeffery 
55*1dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
56db47cd7fSAndrew Jeffery 		return;
57db47cd7fSAndrew Jeffery 
58db47cd7fSAndrew Jeffery 	if (rc == -1) {
59db47cd7fSAndrew Jeffery 		warn("Failed to execute debug command");
60db47cd7fSAndrew Jeffery 	} else {
61db47cd7fSAndrew Jeffery 		warnx("Failed to execute debug command: %zd", rc);
62db47cd7fSAndrew Jeffery 	}
63db47cd7fSAndrew Jeffery }
64db47cd7fSAndrew Jeffery 
65*1dc6adc9SAndrew Jeffery static void sysrq_sink_reboot(void *ctx)
6611cd254bSAndrew Jeffery {
67*1dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
6830b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
6930b6496aSAndrew Jeffery 	static const char action = 'b';
7011cd254bSAndrew Jeffery 	ssize_t rc;
7111cd254bSAndrew Jeffery 
7211cd254bSAndrew Jeffery 	sync();
7311cd254bSAndrew Jeffery 
74*1dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
7530b6496aSAndrew Jeffery 		return;
7630b6496aSAndrew Jeffery 
7730b6496aSAndrew Jeffery 	if (rc == -1) {
7811cd254bSAndrew Jeffery 		warn("Failed to reboot BMC");
7930b6496aSAndrew Jeffery 	} else {
8011cd254bSAndrew Jeffery 		warnx("Failed to reboot BMC: %zd", rc);
8111cd254bSAndrew Jeffery 	}
82210ad636SAndrew Jeffery }
83210ad636SAndrew Jeffery 
84*1dc6adc9SAndrew Jeffery const struct debug_sink_ops sysrq_sink_ops = {
85*1dc6adc9SAndrew Jeffery 	.debug = sysrq_sink_debug,
86*1dc6adc9SAndrew Jeffery 	.reboot = sysrq_sink_reboot,
87*1dc6adc9SAndrew Jeffery };
88*1dc6adc9SAndrew Jeffery 
89*1dc6adc9SAndrew Jeffery static int process(int source, struct debug_sink *sink)
90210ad636SAndrew Jeffery {
91210ad636SAndrew Jeffery 	ssize_t ingress;
92210ad636SAndrew Jeffery 	char command;
93210ad636SAndrew Jeffery 
94210ad636SAndrew Jeffery 	while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
95210ad636SAndrew Jeffery 		switch (command) {
96210ad636SAndrew Jeffery 		case 'D':
97*1dc6adc9SAndrew Jeffery 			sink->ops->debug(sink->ctx);
98210ad636SAndrew Jeffery 			break;
99210ad636SAndrew Jeffery 		case 'R':
100*1dc6adc9SAndrew Jeffery 			sink->ops->reboot(sink->ctx);
10111cd254bSAndrew Jeffery 			break;
10211cd254bSAndrew Jeffery 		default:
10311cd254bSAndrew Jeffery 			warnx("Unexpected command: 0x%02x (%c)", command, command);
10411cd254bSAndrew Jeffery 		}
10511cd254bSAndrew Jeffery 	}
10611cd254bSAndrew Jeffery 
10711cd254bSAndrew Jeffery 	if (ingress == -1)
10811cd254bSAndrew Jeffery 		warn("Failed to read from source");
10911cd254bSAndrew Jeffery 
11011cd254bSAndrew Jeffery 	return ingress;
11111cd254bSAndrew Jeffery }
11211cd254bSAndrew Jeffery 
11311cd254bSAndrew Jeffery int main(int argc, char * const argv[])
11411cd254bSAndrew Jeffery {
115*1dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq sysrq;
116*1dc6adc9SAndrew Jeffery 	struct debug_sink sink;
11711cd254bSAndrew Jeffery 	char devnode[PATH_MAX];
11811cd254bSAndrew Jeffery 	char *devid;
119*1dc6adc9SAndrew Jeffery 	int sourcefd;
120*1dc6adc9SAndrew Jeffery 	int sinkfd;
12111cd254bSAndrew Jeffery 
122d65368beSAndrew Jeffery 	/* Option processing. Currently nothing implemented, but allows us to use optind */
12311cd254bSAndrew Jeffery 	while (1) {
12411cd254bSAndrew Jeffery 		static struct option long_options[] = {
12511cd254bSAndrew Jeffery 			{0, 0, 0, 0},
12611cd254bSAndrew Jeffery 		};
12711cd254bSAndrew Jeffery 		int c;
12811cd254bSAndrew Jeffery 
12911cd254bSAndrew Jeffery 		c = getopt_long(argc, argv, "", long_options, NULL);
13011cd254bSAndrew Jeffery 		if (c == -1)
13111cd254bSAndrew Jeffery 			break;
13211cd254bSAndrew Jeffery 	}
13311cd254bSAndrew Jeffery 
134d65368beSAndrew Jeffery 	/*
135d65368beSAndrew Jeffery 	 * The default behaviour sets the source as stdin and the sink as stdout. This allows
136d65368beSAndrew Jeffery 	 * trivial testing on the command-line with just a keyboard and without crashing the system.
137d65368beSAndrew Jeffery 	 */
138*1dc6adc9SAndrew Jeffery 	sourcefd = 0;
139*1dc6adc9SAndrew Jeffery 	sinkfd = 1;
14011cd254bSAndrew Jeffery 
141d65368beSAndrew Jeffery 	/* Handle the source argument, if any */
14211cd254bSAndrew Jeffery 	if (optind < argc) {
14311cd254bSAndrew Jeffery 		char devpath[PATH_MAX];
14411cd254bSAndrew Jeffery 
145d65368beSAndrew Jeffery 		/*
146d65368beSAndrew Jeffery 		 * To make our lives easy with udev we take the basename of the source argument and
147d65368beSAndrew Jeffery 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
148d65368beSAndrew Jeffery 		 * rule to pass the device of interest to the systemd unit.
149d65368beSAndrew Jeffery 		 */
15011cd254bSAndrew Jeffery 		strncpy(devpath, argv[optind], sizeof(devpath));
15111cd254bSAndrew Jeffery 		devpath[PATH_MAX - 1] = '\0';
15211cd254bSAndrew Jeffery 		devid = basename(devpath);
15311cd254bSAndrew Jeffery 
15411cd254bSAndrew Jeffery 		strncpy(devnode, "/dev/", sizeof(devnode));
15511cd254bSAndrew Jeffery 		strncat(devnode, devid, sizeof(devnode));
15611cd254bSAndrew Jeffery 		devnode[PATH_MAX - 1] = '\0';
15711cd254bSAndrew Jeffery 
158*1dc6adc9SAndrew Jeffery 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
15911cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", devnode);
16011cd254bSAndrew Jeffery 
16111cd254bSAndrew Jeffery 		optind++;
16211cd254bSAndrew Jeffery 	}
16311cd254bSAndrew Jeffery 
164d65368beSAndrew Jeffery 	/* Handle the sink argument, if any */
16511cd254bSAndrew Jeffery 	if (optind < argc) {
166d65368beSAndrew Jeffery 		/*
167d65368beSAndrew Jeffery 		 * Just open the sink path directly. If we ever need different behaviour then we
168d65368beSAndrew Jeffery 		 * patch this bit when we know what we need.
169d65368beSAndrew Jeffery 		 */
170*1dc6adc9SAndrew Jeffery 		if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
17111cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
17211cd254bSAndrew Jeffery 
17311cd254bSAndrew Jeffery 		optind++;
17411cd254bSAndrew Jeffery 	}
17511cd254bSAndrew Jeffery 
176d65368beSAndrew Jeffery 	/* Check we're done with the command-line */
17711cd254bSAndrew Jeffery 	if (optind < argc)
17811cd254bSAndrew Jeffery 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
17911cd254bSAndrew Jeffery 
180*1dc6adc9SAndrew Jeffery 	sysrq.sink = sinkfd;
181*1dc6adc9SAndrew Jeffery 	sink.ops = &sysrq_sink_ops;
182*1dc6adc9SAndrew Jeffery 	sink.ctx = &sysrq;
183*1dc6adc9SAndrew Jeffery 
184d65368beSAndrew Jeffery 	/* Trigger the actions on the sink when we receive an event from the source */
185*1dc6adc9SAndrew Jeffery 	if (process(sourcefd, &sink) < 0)
18611cd254bSAndrew Jeffery 		errx(EXIT_FAILURE, "Failure while processing command stream");
18711cd254bSAndrew Jeffery 
18811cd254bSAndrew Jeffery 	return 0;
18911cd254bSAndrew Jeffery }
190