xref: /openbmc/debug-trigger/main.c (revision e998ba7708af8598addea91e3598e6fa3a0113fa)
111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0
211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp.
311cd254bSAndrew Jeffery 
4d65368beSAndrew Jeffery /*
5*e998ba77SAndrew Jeffery  * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When a
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  *
11*e998ba77SAndrew Jeffery  * Options:
12*e998ba77SAndrew Jeffery  *  --sink-actions=ACTION
13*e998ba77SAndrew Jeffery  *	Set the class of sink action(s) to be used. Defaults to 'sysrq'
14*e998ba77SAndrew Jeffery  *
15d65368beSAndrew Jeffery  * Examples:
16d65368beSAndrew Jeffery  *  debug-trigger
17*e998ba77SAndrew Jeffery  *	Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink
18*e998ba77SAndrew Jeffery  *	actions. Useful for testing.
19*e998ba77SAndrew Jeffery  *
20*e998ba77SAndrew Jeffery  *  debug-trigger --sink-actions=sysrq
21*e998ba77SAndrew Jeffery  *	Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the
22*e998ba77SAndrew Jeffery  *	sink.
23d65368beSAndrew Jeffery  *
24d65368beSAndrew Jeffery  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
25*e998ba77SAndrew Jeffery  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default
26*e998ba77SAndrew Jeffery  *	'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to
27*e998ba77SAndrew Jeffery  *	/proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will
28*e998ba77SAndrew Jeffery  *	be written to /proc/sysrq-trigger, causing an immediate reboot of the system.
29d65368beSAndrew Jeffery  */
30d65368beSAndrew Jeffery 
3111cd254bSAndrew Jeffery #include <err.h>
3211cd254bSAndrew Jeffery #include <fcntl.h>
3311cd254bSAndrew Jeffery #include <getopt.h>
3411cd254bSAndrew Jeffery #include <libgen.h>
3511cd254bSAndrew Jeffery #include <limits.h>
3611cd254bSAndrew Jeffery #include <linux/reboot.h>
3711cd254bSAndrew Jeffery #include <stdlib.h>
3811cd254bSAndrew Jeffery #include <string.h>
3911cd254bSAndrew Jeffery #include <sys/reboot.h>
4011cd254bSAndrew Jeffery #include <sys/stat.h>
4111cd254bSAndrew Jeffery #include <sys/types.h>
4211cd254bSAndrew Jeffery #include <unistd.h>
4311cd254bSAndrew Jeffery 
441dc6adc9SAndrew Jeffery struct debug_sink_ops {
451dc6adc9SAndrew Jeffery 	void (*debug)(void *ctx);
461dc6adc9SAndrew Jeffery 	void (*reboot)(void *ctx);
471dc6adc9SAndrew Jeffery };
481dc6adc9SAndrew Jeffery 
491dc6adc9SAndrew Jeffery struct debug_sink {
501dc6adc9SAndrew Jeffery 	const struct debug_sink_ops *ops;
511dc6adc9SAndrew Jeffery 	void *ctx;
521dc6adc9SAndrew Jeffery };
531dc6adc9SAndrew Jeffery 
541dc6adc9SAndrew Jeffery struct debug_sink_sysrq {
551dc6adc9SAndrew Jeffery 	int sink;
561dc6adc9SAndrew Jeffery };
571dc6adc9SAndrew Jeffery 
581dc6adc9SAndrew Jeffery static void sysrq_sink_debug(void *ctx)
59db47cd7fSAndrew Jeffery {
601dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
6130b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
62db47cd7fSAndrew Jeffery 	static const char action = 'c';
63db47cd7fSAndrew Jeffery 	ssize_t rc;
64db47cd7fSAndrew Jeffery 
65db47cd7fSAndrew Jeffery 	sync();
66db47cd7fSAndrew Jeffery 
671dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
68db47cd7fSAndrew Jeffery 		return;
69db47cd7fSAndrew Jeffery 
70db47cd7fSAndrew Jeffery 	if (rc == -1) {
71db47cd7fSAndrew Jeffery 		warn("Failed to execute debug command");
72db47cd7fSAndrew Jeffery 	} else {
73db47cd7fSAndrew Jeffery 		warnx("Failed to execute debug command: %zd", rc);
74db47cd7fSAndrew Jeffery 	}
75db47cd7fSAndrew Jeffery }
76db47cd7fSAndrew Jeffery 
771dc6adc9SAndrew Jeffery static void sysrq_sink_reboot(void *ctx)
7811cd254bSAndrew Jeffery {
791dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
8030b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
8130b6496aSAndrew Jeffery 	static const char action = 'b';
8211cd254bSAndrew Jeffery 	ssize_t rc;
8311cd254bSAndrew Jeffery 
8411cd254bSAndrew Jeffery 	sync();
8511cd254bSAndrew Jeffery 
861dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
8730b6496aSAndrew Jeffery 		return;
8830b6496aSAndrew Jeffery 
8930b6496aSAndrew Jeffery 	if (rc == -1) {
9011cd254bSAndrew Jeffery 		warn("Failed to reboot BMC");
9130b6496aSAndrew Jeffery 	} else {
9211cd254bSAndrew Jeffery 		warnx("Failed to reboot BMC: %zd", rc);
9311cd254bSAndrew Jeffery 	}
94210ad636SAndrew Jeffery }
95210ad636SAndrew Jeffery 
961dc6adc9SAndrew Jeffery const struct debug_sink_ops sysrq_sink_ops = {
971dc6adc9SAndrew Jeffery 	.debug = sysrq_sink_debug,
981dc6adc9SAndrew Jeffery 	.reboot = sysrq_sink_reboot,
991dc6adc9SAndrew Jeffery };
1001dc6adc9SAndrew Jeffery 
1011dc6adc9SAndrew Jeffery static int process(int source, struct debug_sink *sink)
102210ad636SAndrew Jeffery {
103210ad636SAndrew Jeffery 	ssize_t ingress;
104210ad636SAndrew Jeffery 	char command;
105210ad636SAndrew Jeffery 
106210ad636SAndrew Jeffery 	while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
107210ad636SAndrew Jeffery 		switch (command) {
108210ad636SAndrew Jeffery 		case 'D':
1091dc6adc9SAndrew Jeffery 			sink->ops->debug(sink->ctx);
110210ad636SAndrew Jeffery 			break;
111210ad636SAndrew Jeffery 		case 'R':
1121dc6adc9SAndrew Jeffery 			sink->ops->reboot(sink->ctx);
11311cd254bSAndrew Jeffery 			break;
11411cd254bSAndrew Jeffery 		default:
11511cd254bSAndrew Jeffery 			warnx("Unexpected command: 0x%02x (%c)", command, command);
11611cd254bSAndrew Jeffery 		}
11711cd254bSAndrew Jeffery 	}
11811cd254bSAndrew Jeffery 
11911cd254bSAndrew Jeffery 	if (ingress == -1)
12011cd254bSAndrew Jeffery 		warn("Failed to read from source");
12111cd254bSAndrew Jeffery 
12211cd254bSAndrew Jeffery 	return ingress;
12311cd254bSAndrew Jeffery }
12411cd254bSAndrew Jeffery 
12511cd254bSAndrew Jeffery int main(int argc, char * const argv[])
12611cd254bSAndrew Jeffery {
127*e998ba77SAndrew Jeffery 	const char *sink_actions = NULL;
1281dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq sysrq;
1291dc6adc9SAndrew Jeffery 	struct debug_sink sink;
13011cd254bSAndrew Jeffery 	char devnode[PATH_MAX];
13111cd254bSAndrew Jeffery 	char *devid;
1321dc6adc9SAndrew Jeffery 	int sourcefd;
1331dc6adc9SAndrew Jeffery 	int sinkfd;
13411cd254bSAndrew Jeffery 
135*e998ba77SAndrew Jeffery 	/* Option processing */
13611cd254bSAndrew Jeffery 	while (1) {
13711cd254bSAndrew Jeffery 		static struct option long_options[] = {
138*e998ba77SAndrew Jeffery 			{"sink-actions", required_argument, 0, 's'},
13911cd254bSAndrew Jeffery 			{0, 0, 0, 0},
14011cd254bSAndrew Jeffery 		};
14111cd254bSAndrew Jeffery 		int c;
14211cd254bSAndrew Jeffery 
14311cd254bSAndrew Jeffery 		c = getopt_long(argc, argv, "", long_options, NULL);
14411cd254bSAndrew Jeffery 		if (c == -1)
14511cd254bSAndrew Jeffery 			break;
146*e998ba77SAndrew Jeffery 
147*e998ba77SAndrew Jeffery 		switch (c) {
148*e998ba77SAndrew Jeffery 		case 's':
149*e998ba77SAndrew Jeffery 			sink_actions = optarg;
150*e998ba77SAndrew Jeffery 			break;
151*e998ba77SAndrew Jeffery 		default:
152*e998ba77SAndrew Jeffery 			break;
153*e998ba77SAndrew Jeffery 		}
15411cd254bSAndrew Jeffery 	}
15511cd254bSAndrew Jeffery 
156d65368beSAndrew Jeffery 	/*
157*e998ba77SAndrew Jeffery 	 * The default behaviour sets the source file descriptor as stdin and the sink file
158*e998ba77SAndrew Jeffery 	 * descriptor as stdout. This allows trivial testing on the command-line with just a
159*e998ba77SAndrew Jeffery 	 * keyboard and without crashing the system.
160d65368beSAndrew Jeffery 	 */
1611dc6adc9SAndrew Jeffery 	sourcefd = 0;
1621dc6adc9SAndrew Jeffery 	sinkfd = 1;
16311cd254bSAndrew Jeffery 
164*e998ba77SAndrew Jeffery 	/* Handle the source path argument, if any */
16511cd254bSAndrew Jeffery 	if (optind < argc) {
16611cd254bSAndrew Jeffery 		char devpath[PATH_MAX];
16711cd254bSAndrew Jeffery 
168d65368beSAndrew Jeffery 		/*
169d65368beSAndrew Jeffery 		 * To make our lives easy with udev we take the basename of the source argument and
170d65368beSAndrew Jeffery 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
171d65368beSAndrew Jeffery 		 * rule to pass the device of interest to the systemd unit.
172d65368beSAndrew Jeffery 		 */
17311cd254bSAndrew Jeffery 		strncpy(devpath, argv[optind], sizeof(devpath));
17411cd254bSAndrew Jeffery 		devpath[PATH_MAX - 1] = '\0';
17511cd254bSAndrew Jeffery 		devid = basename(devpath);
17611cd254bSAndrew Jeffery 
17711cd254bSAndrew Jeffery 		strncpy(devnode, "/dev/", sizeof(devnode));
17811cd254bSAndrew Jeffery 		strncat(devnode, devid, sizeof(devnode));
17911cd254bSAndrew Jeffery 		devnode[PATH_MAX - 1] = '\0';
18011cd254bSAndrew Jeffery 
1811dc6adc9SAndrew Jeffery 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
18211cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", devnode);
18311cd254bSAndrew Jeffery 
18411cd254bSAndrew Jeffery 		optind++;
18511cd254bSAndrew Jeffery 	}
18611cd254bSAndrew Jeffery 
187*e998ba77SAndrew Jeffery 	/*
188*e998ba77SAndrew Jeffery 	 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
189*e998ba77SAndrew Jeffery 	 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
190*e998ba77SAndrew Jeffery 	 * been passed, do as we're told and use the 'sysrq' sink actions.
191*e998ba77SAndrew Jeffery 	 */
192*e998ba77SAndrew Jeffery 	if (!sink_actions || !strcmp("sysrq", sink_actions)) {
19311cd254bSAndrew Jeffery 		if (optind < argc) {
194d65368beSAndrew Jeffery 			/*
195*e998ba77SAndrew Jeffery 			 * Just open the sink path directly. If we ever need different behaviour
196*e998ba77SAndrew Jeffery 			 * then we patch this bit when we know what we need.
197d65368beSAndrew Jeffery 			 */
1981dc6adc9SAndrew Jeffery 			if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
19911cd254bSAndrew Jeffery 				err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
20011cd254bSAndrew Jeffery 
20111cd254bSAndrew Jeffery 			optind++;
20211cd254bSAndrew Jeffery 		}
20311cd254bSAndrew Jeffery 
2041dc6adc9SAndrew Jeffery 		sysrq.sink = sinkfd;
2051dc6adc9SAndrew Jeffery 		sink.ops = &sysrq_sink_ops;
2061dc6adc9SAndrew Jeffery 		sink.ctx = &sysrq;
207*e998ba77SAndrew Jeffery 	}
208*e998ba77SAndrew Jeffery 
209*e998ba77SAndrew Jeffery 	/* Check we're done with the command-line */
210*e998ba77SAndrew Jeffery 	if (optind < argc)
211*e998ba77SAndrew Jeffery 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
2121dc6adc9SAndrew Jeffery 
213d65368beSAndrew Jeffery 	/* Trigger the actions on the sink when we receive an event from the source */
2141dc6adc9SAndrew Jeffery 	if (process(sourcefd, &sink) < 0)
21511cd254bSAndrew Jeffery 		errx(EXIT_FAILURE, "Failure while processing command stream");
21611cd254bSAndrew Jeffery 
21711cd254bSAndrew Jeffery 	return 0;
21811cd254bSAndrew Jeffery }
219