xref: /openbmc/debug-trigger/main.c (revision b1ea254e6c3b1578127f97356612dffa85073698)
111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0
211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp.
311cd254bSAndrew Jeffery 
4d65368beSAndrew Jeffery /*
5e998ba77SAndrew 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  *
11e998ba77SAndrew Jeffery  * Options:
12e998ba77SAndrew Jeffery  *  --sink-actions=ACTION
13e998ba77SAndrew Jeffery  *	Set the class of sink action(s) to be used. Defaults to 'sysrq'
14e998ba77SAndrew Jeffery  *
15d65368beSAndrew Jeffery  * Examples:
16d65368beSAndrew Jeffery  *  debug-trigger
17e998ba77SAndrew Jeffery  *	Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink
18e998ba77SAndrew Jeffery  *	actions. Useful for testing.
19e998ba77SAndrew Jeffery  *
20e998ba77SAndrew Jeffery  *  debug-trigger --sink-actions=sysrq
21e998ba77SAndrew Jeffery  *	Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the
22e998ba77SAndrew Jeffery  *	sink.
23d65368beSAndrew Jeffery  *
24d65368beSAndrew Jeffery  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
25e998ba77SAndrew Jeffery  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default
26e998ba77SAndrew Jeffery  *	'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to
27e998ba77SAndrew Jeffery  *	/proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will
28e998ba77SAndrew Jeffery  *	be written to /proc/sysrq-trigger, causing an immediate reboot of the system.
29d65368beSAndrew Jeffery  */
30d65368beSAndrew Jeffery 
3111cd254bSAndrew Jeffery #include <err.h>
32*b1ea254eSAndrew Jeffery #include <errno.h>
3311cd254bSAndrew Jeffery #include <fcntl.h>
3411cd254bSAndrew Jeffery #include <getopt.h>
3511cd254bSAndrew Jeffery #include <libgen.h>
3611cd254bSAndrew Jeffery #include <limits.h>
3711cd254bSAndrew Jeffery #include <linux/reboot.h>
3811cd254bSAndrew Jeffery #include <stdlib.h>
3911cd254bSAndrew Jeffery #include <string.h>
4011cd254bSAndrew Jeffery #include <sys/reboot.h>
4111cd254bSAndrew Jeffery #include <sys/stat.h>
4211cd254bSAndrew Jeffery #include <sys/types.h>
4311cd254bSAndrew Jeffery #include <unistd.h>
4411cd254bSAndrew Jeffery 
45*b1ea254eSAndrew Jeffery struct debug_source_ops {
46*b1ea254eSAndrew Jeffery 	int (*poll)(void *ctx, char *op);
47*b1ea254eSAndrew Jeffery };
48*b1ea254eSAndrew Jeffery 
49*b1ea254eSAndrew Jeffery struct debug_source {
50*b1ea254eSAndrew Jeffery 	const struct debug_source_ops *ops;
51*b1ea254eSAndrew Jeffery 	void *ctx;
52*b1ea254eSAndrew Jeffery };
53*b1ea254eSAndrew Jeffery 
54*b1ea254eSAndrew Jeffery struct debug_source_basic {
55*b1ea254eSAndrew Jeffery 	int source;
56*b1ea254eSAndrew Jeffery };
57*b1ea254eSAndrew Jeffery 
581dc6adc9SAndrew Jeffery struct debug_sink_ops {
591dc6adc9SAndrew Jeffery 	void (*debug)(void *ctx);
601dc6adc9SAndrew Jeffery 	void (*reboot)(void *ctx);
611dc6adc9SAndrew Jeffery };
621dc6adc9SAndrew Jeffery 
631dc6adc9SAndrew Jeffery struct debug_sink {
641dc6adc9SAndrew Jeffery 	const struct debug_sink_ops *ops;
651dc6adc9SAndrew Jeffery 	void *ctx;
661dc6adc9SAndrew Jeffery };
671dc6adc9SAndrew Jeffery 
681dc6adc9SAndrew Jeffery struct debug_sink_sysrq {
691dc6adc9SAndrew Jeffery 	int sink;
701dc6adc9SAndrew Jeffery };
711dc6adc9SAndrew Jeffery 
721dc6adc9SAndrew Jeffery static void sysrq_sink_debug(void *ctx)
73db47cd7fSAndrew Jeffery {
741dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
7530b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
76db47cd7fSAndrew Jeffery 	static const char action = 'c';
77db47cd7fSAndrew Jeffery 	ssize_t rc;
78db47cd7fSAndrew Jeffery 
79db47cd7fSAndrew Jeffery 	sync();
80db47cd7fSAndrew Jeffery 
811dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
82db47cd7fSAndrew Jeffery 		return;
83db47cd7fSAndrew Jeffery 
84db47cd7fSAndrew Jeffery 	if (rc == -1) {
85db47cd7fSAndrew Jeffery 		warn("Failed to execute debug command");
86db47cd7fSAndrew Jeffery 	} else {
87db47cd7fSAndrew Jeffery 		warnx("Failed to execute debug command: %zd", rc);
88db47cd7fSAndrew Jeffery 	}
89db47cd7fSAndrew Jeffery }
90db47cd7fSAndrew Jeffery 
911dc6adc9SAndrew Jeffery static void sysrq_sink_reboot(void *ctx)
9211cd254bSAndrew Jeffery {
931dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
9430b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
9530b6496aSAndrew Jeffery 	static const char action = 'b';
9611cd254bSAndrew Jeffery 	ssize_t rc;
9711cd254bSAndrew Jeffery 
9811cd254bSAndrew Jeffery 	sync();
9911cd254bSAndrew Jeffery 
1001dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
10130b6496aSAndrew Jeffery 		return;
10230b6496aSAndrew Jeffery 
10330b6496aSAndrew Jeffery 	if (rc == -1) {
10411cd254bSAndrew Jeffery 		warn("Failed to reboot BMC");
10530b6496aSAndrew Jeffery 	} else {
10611cd254bSAndrew Jeffery 		warnx("Failed to reboot BMC: %zd", rc);
10711cd254bSAndrew Jeffery 	}
108210ad636SAndrew Jeffery }
109210ad636SAndrew Jeffery 
1101dc6adc9SAndrew Jeffery const struct debug_sink_ops sysrq_sink_ops = {
1111dc6adc9SAndrew Jeffery 	.debug = sysrq_sink_debug,
1121dc6adc9SAndrew Jeffery 	.reboot = sysrq_sink_reboot,
1131dc6adc9SAndrew Jeffery };
1141dc6adc9SAndrew Jeffery 
115*b1ea254eSAndrew Jeffery static int basic_source_poll(void *ctx, char *op)
116210ad636SAndrew Jeffery {
117*b1ea254eSAndrew Jeffery 	struct debug_source_basic *basic = ctx;
118210ad636SAndrew Jeffery 	ssize_t ingress;
119210ad636SAndrew Jeffery 
120*b1ea254eSAndrew Jeffery 	if ((ingress = read(basic->source, op, 1)) != 1) {
121*b1ea254eSAndrew Jeffery 		if (ingress < 0) {
122*b1ea254eSAndrew Jeffery 			warn("Failed to read from basic source");
123*b1ea254eSAndrew Jeffery 			return -errno;
124*b1ea254eSAndrew Jeffery 		}
125*b1ea254eSAndrew Jeffery 
126*b1ea254eSAndrew Jeffery 		/* Unreachable */
127*b1ea254eSAndrew Jeffery 		errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress);
128*b1ea254eSAndrew Jeffery 	}
129*b1ea254eSAndrew Jeffery 
130*b1ea254eSAndrew Jeffery 	return 0;
131*b1ea254eSAndrew Jeffery }
132*b1ea254eSAndrew Jeffery 
133*b1ea254eSAndrew Jeffery const struct debug_source_ops basic_source_ops = {
134*b1ea254eSAndrew Jeffery 	.poll = basic_source_poll,
135*b1ea254eSAndrew Jeffery };
136*b1ea254eSAndrew Jeffery 
137*b1ea254eSAndrew Jeffery static int process(struct debug_source *source, struct debug_sink *sink)
138*b1ea254eSAndrew Jeffery {
139*b1ea254eSAndrew Jeffery 	char command;
140*b1ea254eSAndrew Jeffery 	int rc;
141*b1ea254eSAndrew Jeffery 
142*b1ea254eSAndrew Jeffery 	while (!(rc = source->ops->poll(source->ctx, &command))) {
143210ad636SAndrew Jeffery 		switch (command) {
144210ad636SAndrew Jeffery 		case 'D':
1451dc6adc9SAndrew Jeffery 			sink->ops->debug(sink->ctx);
146210ad636SAndrew Jeffery 			break;
147210ad636SAndrew Jeffery 		case 'R':
1481dc6adc9SAndrew Jeffery 			sink->ops->reboot(sink->ctx);
14911cd254bSAndrew Jeffery 			break;
15011cd254bSAndrew Jeffery 		default:
15111cd254bSAndrew Jeffery 			warnx("Unexpected command: 0x%02x (%c)", command, command);
15211cd254bSAndrew Jeffery 		}
15311cd254bSAndrew Jeffery 	}
15411cd254bSAndrew Jeffery 
155*b1ea254eSAndrew Jeffery 	if (rc < 0)
156*b1ea254eSAndrew Jeffery 		warnx("Failed to poll source: %s", strerror(-rc));
15711cd254bSAndrew Jeffery 
158*b1ea254eSAndrew Jeffery 	return rc;
15911cd254bSAndrew Jeffery }
16011cd254bSAndrew Jeffery 
16111cd254bSAndrew Jeffery int main(int argc, char * const argv[])
16211cd254bSAndrew Jeffery {
163e998ba77SAndrew Jeffery 	const char *sink_actions = NULL;
164*b1ea254eSAndrew Jeffery 	struct debug_source_basic basic;
1651dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq sysrq;
166*b1ea254eSAndrew Jeffery 	struct debug_source source;
1671dc6adc9SAndrew Jeffery 	struct debug_sink sink;
16811cd254bSAndrew Jeffery 	char devnode[PATH_MAX];
16911cd254bSAndrew Jeffery 	char *devid;
1701dc6adc9SAndrew Jeffery 	int sourcefd;
1711dc6adc9SAndrew Jeffery 	int sinkfd;
17211cd254bSAndrew Jeffery 
173e998ba77SAndrew Jeffery 	/* Option processing */
17411cd254bSAndrew Jeffery 	while (1) {
17511cd254bSAndrew Jeffery 		static struct option long_options[] = {
176e998ba77SAndrew Jeffery 			{"sink-actions", required_argument, 0, 's'},
17711cd254bSAndrew Jeffery 			{0, 0, 0, 0},
17811cd254bSAndrew Jeffery 		};
17911cd254bSAndrew Jeffery 		int c;
18011cd254bSAndrew Jeffery 
18111cd254bSAndrew Jeffery 		c = getopt_long(argc, argv, "", long_options, NULL);
18211cd254bSAndrew Jeffery 		if (c == -1)
18311cd254bSAndrew Jeffery 			break;
184e998ba77SAndrew Jeffery 
185e998ba77SAndrew Jeffery 		switch (c) {
186e998ba77SAndrew Jeffery 		case 's':
187e998ba77SAndrew Jeffery 			sink_actions = optarg;
188e998ba77SAndrew Jeffery 			break;
189e998ba77SAndrew Jeffery 		default:
190e998ba77SAndrew Jeffery 			break;
191e998ba77SAndrew Jeffery 		}
19211cd254bSAndrew Jeffery 	}
19311cd254bSAndrew Jeffery 
194d65368beSAndrew Jeffery 	/*
195e998ba77SAndrew Jeffery 	 * The default behaviour sets the source file descriptor as stdin and the sink file
196e998ba77SAndrew Jeffery 	 * descriptor as stdout. This allows trivial testing on the command-line with just a
197e998ba77SAndrew Jeffery 	 * keyboard and without crashing the system.
198d65368beSAndrew Jeffery 	 */
1991dc6adc9SAndrew Jeffery 	sourcefd = 0;
2001dc6adc9SAndrew Jeffery 	sinkfd = 1;
20111cd254bSAndrew Jeffery 
202e998ba77SAndrew Jeffery 	/* Handle the source path argument, if any */
20311cd254bSAndrew Jeffery 	if (optind < argc) {
20411cd254bSAndrew Jeffery 		char devpath[PATH_MAX];
20511cd254bSAndrew Jeffery 
206d65368beSAndrew Jeffery 		/*
207d65368beSAndrew Jeffery 		 * To make our lives easy with udev we take the basename of the source argument and
208d65368beSAndrew Jeffery 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
209d65368beSAndrew Jeffery 		 * rule to pass the device of interest to the systemd unit.
210d65368beSAndrew Jeffery 		 */
21111cd254bSAndrew Jeffery 		strncpy(devpath, argv[optind], sizeof(devpath));
21211cd254bSAndrew Jeffery 		devpath[PATH_MAX - 1] = '\0';
21311cd254bSAndrew Jeffery 		devid = basename(devpath);
21411cd254bSAndrew Jeffery 
21511cd254bSAndrew Jeffery 		strncpy(devnode, "/dev/", sizeof(devnode));
21611cd254bSAndrew Jeffery 		strncat(devnode, devid, sizeof(devnode));
21711cd254bSAndrew Jeffery 		devnode[PATH_MAX - 1] = '\0';
21811cd254bSAndrew Jeffery 
2191dc6adc9SAndrew Jeffery 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
22011cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", devnode);
22111cd254bSAndrew Jeffery 
22211cd254bSAndrew Jeffery 		optind++;
22311cd254bSAndrew Jeffery 	}
22411cd254bSAndrew Jeffery 
225e998ba77SAndrew Jeffery 	/*
226e998ba77SAndrew Jeffery 	 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
227e998ba77SAndrew Jeffery 	 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
228e998ba77SAndrew Jeffery 	 * been passed, do as we're told and use the 'sysrq' sink actions.
229e998ba77SAndrew Jeffery 	 */
230e998ba77SAndrew Jeffery 	if (!sink_actions || !strcmp("sysrq", sink_actions)) {
23111cd254bSAndrew Jeffery 		if (optind < argc) {
232d65368beSAndrew Jeffery 			/*
233e998ba77SAndrew Jeffery 			 * Just open the sink path directly. If we ever need different behaviour
234e998ba77SAndrew Jeffery 			 * then we patch this bit when we know what we need.
235d65368beSAndrew Jeffery 			 */
2361dc6adc9SAndrew Jeffery 			if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
23711cd254bSAndrew Jeffery 				err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
23811cd254bSAndrew Jeffery 
23911cd254bSAndrew Jeffery 			optind++;
24011cd254bSAndrew Jeffery 		}
24111cd254bSAndrew Jeffery 
2421dc6adc9SAndrew Jeffery 		sysrq.sink = sinkfd;
2431dc6adc9SAndrew Jeffery 		sink.ops = &sysrq_sink_ops;
2441dc6adc9SAndrew Jeffery 		sink.ctx = &sysrq;
245e998ba77SAndrew Jeffery 	}
246e998ba77SAndrew Jeffery 
247e998ba77SAndrew Jeffery 	/* Check we're done with the command-line */
248e998ba77SAndrew Jeffery 	if (optind < argc)
249e998ba77SAndrew Jeffery 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
2501dc6adc9SAndrew Jeffery 
251*b1ea254eSAndrew Jeffery 	basic.source = sourcefd;
252*b1ea254eSAndrew Jeffery 	source.ops = &basic_source_ops;
253*b1ea254eSAndrew Jeffery 	source.ctx = &basic;
254*b1ea254eSAndrew Jeffery 
255d65368beSAndrew Jeffery 	/* Trigger the actions on the sink when we receive an event from the source */
256*b1ea254eSAndrew Jeffery 	if (process(&source, &sink) < 0)
25711cd254bSAndrew Jeffery 		errx(EXIT_FAILURE, "Failure while processing command stream");
25811cd254bSAndrew Jeffery 
25911cd254bSAndrew Jeffery 	return 0;
26011cd254bSAndrew Jeffery }
261