xref: /openbmc/debug-trigger/main.c (revision d65368be2872800621eb2528d6ff9494ab2362c3)
111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0
211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp.
311cd254bSAndrew Jeffery 
4*d65368beSAndrew Jeffery /*
5*d65368beSAndrew Jeffery  * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When the
6*d65368beSAndrew Jeffery  * signal is received it triggers a crash to collect debug data and reboots the system in the hope
7*d65368beSAndrew Jeffery  * that it will recover.
8*d65368beSAndrew Jeffery  *
9*d65368beSAndrew Jeffery  * Usage: debug-trigger [SOURCE] [SINK]
10*d65368beSAndrew Jeffery  *
11*d65368beSAndrew Jeffery  * Examples:
12*d65368beSAndrew Jeffery  *  debug-trigger
13*d65368beSAndrew Jeffery  *	Set the source as stdin and the sink as stdout. Useful for testing.
14*d65368beSAndrew Jeffery  *
15*d65368beSAndrew Jeffery  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
16*d65368beSAndrew Jeffery  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink.
17*d65368beSAndrew Jeffery  */
18*d65368beSAndrew 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 
32db47cd7fSAndrew Jeffery static void process_debug(int sink)
33db47cd7fSAndrew Jeffery {
3430b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
35db47cd7fSAndrew Jeffery 	static const char action = 'c';
36db47cd7fSAndrew Jeffery 	ssize_t rc;
37db47cd7fSAndrew Jeffery 
38db47cd7fSAndrew Jeffery 	sync();
39db47cd7fSAndrew Jeffery 
40db47cd7fSAndrew Jeffery 	if ((rc = write(sink, &action, sizeof(action))) == sizeof(action))
41db47cd7fSAndrew Jeffery 		return;
42db47cd7fSAndrew Jeffery 
43db47cd7fSAndrew Jeffery 	if (rc == -1) {
44db47cd7fSAndrew Jeffery 		warn("Failed to execute debug command");
45db47cd7fSAndrew Jeffery 	} else {
46db47cd7fSAndrew Jeffery 		warnx("Failed to execute debug command: %zd", rc);
47db47cd7fSAndrew Jeffery 	}
48db47cd7fSAndrew Jeffery }
49db47cd7fSAndrew Jeffery 
5030b6496aSAndrew Jeffery static void process_reboot(int sink)
5111cd254bSAndrew Jeffery {
5230b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
5330b6496aSAndrew Jeffery 	static const char action = 'b';
5411cd254bSAndrew Jeffery 	ssize_t rc;
5511cd254bSAndrew Jeffery 
5611cd254bSAndrew Jeffery 	sync();
5711cd254bSAndrew Jeffery 
5830b6496aSAndrew Jeffery 	if ((rc = write(sink, &action, sizeof(action))) == sizeof(action))
5930b6496aSAndrew Jeffery 		return;
6030b6496aSAndrew Jeffery 
6130b6496aSAndrew Jeffery 	if (rc == -1) {
6211cd254bSAndrew Jeffery 		warn("Failed to reboot BMC");
6330b6496aSAndrew Jeffery 	} else {
6411cd254bSAndrew Jeffery 		warnx("Failed to reboot BMC: %zd", rc);
6511cd254bSAndrew Jeffery 	}
66210ad636SAndrew Jeffery }
67210ad636SAndrew Jeffery 
68210ad636SAndrew Jeffery static int process(int source, int sink)
69210ad636SAndrew Jeffery {
70210ad636SAndrew Jeffery 	ssize_t ingress;
71210ad636SAndrew Jeffery 	char command;
72210ad636SAndrew Jeffery 
73210ad636SAndrew Jeffery 	while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
74210ad636SAndrew Jeffery 		switch (command) {
75210ad636SAndrew Jeffery 		case 'D':
76210ad636SAndrew Jeffery 			process_debug(sink);
77210ad636SAndrew Jeffery 			break;
78210ad636SAndrew Jeffery 		case 'R':
79210ad636SAndrew Jeffery 			process_reboot(sink);
8011cd254bSAndrew Jeffery 			break;
8111cd254bSAndrew Jeffery 		default:
8211cd254bSAndrew Jeffery 			warnx("Unexpected command: 0x%02x (%c)", command, command);
8311cd254bSAndrew Jeffery 		}
8411cd254bSAndrew Jeffery 	}
8511cd254bSAndrew Jeffery 
8611cd254bSAndrew Jeffery 	if (ingress == -1)
8711cd254bSAndrew Jeffery 		warn("Failed to read from source");
8811cd254bSAndrew Jeffery 
8911cd254bSAndrew Jeffery 	return ingress;
9011cd254bSAndrew Jeffery }
9111cd254bSAndrew Jeffery 
9211cd254bSAndrew Jeffery int main(int argc, char * const argv[])
9311cd254bSAndrew Jeffery {
9411cd254bSAndrew Jeffery 	char devnode[PATH_MAX];
9511cd254bSAndrew Jeffery 	char *devid;
9611cd254bSAndrew Jeffery 	int source;
9711cd254bSAndrew Jeffery 	int sink;
9811cd254bSAndrew Jeffery 
99*d65368beSAndrew Jeffery 	/* Option processing. Currently nothing implemented, but allows us to use optind */
10011cd254bSAndrew Jeffery 	while (1) {
10111cd254bSAndrew Jeffery 		static struct option long_options[] = {
10211cd254bSAndrew Jeffery 			{0, 0, 0, 0},
10311cd254bSAndrew Jeffery 		};
10411cd254bSAndrew Jeffery 		int c;
10511cd254bSAndrew Jeffery 
10611cd254bSAndrew Jeffery 		c = getopt_long(argc, argv, "", long_options, NULL);
10711cd254bSAndrew Jeffery 		if (c == -1)
10811cd254bSAndrew Jeffery 			break;
10911cd254bSAndrew Jeffery 	}
11011cd254bSAndrew Jeffery 
111*d65368beSAndrew Jeffery 	/*
112*d65368beSAndrew Jeffery 	 * The default behaviour sets the source as stdin and the sink as stdout. This allows
113*d65368beSAndrew Jeffery 	 * trivial testing on the command-line with just a keyboard and without crashing the system.
114*d65368beSAndrew Jeffery 	 */
11511cd254bSAndrew Jeffery 	source = 0;
11611cd254bSAndrew Jeffery 	sink = 1;
11711cd254bSAndrew Jeffery 
118*d65368beSAndrew Jeffery 	/* Handle the source argument, if any */
11911cd254bSAndrew Jeffery 	if (optind < argc) {
12011cd254bSAndrew Jeffery 		char devpath[PATH_MAX];
12111cd254bSAndrew Jeffery 
122*d65368beSAndrew Jeffery 		/*
123*d65368beSAndrew Jeffery 		 * To make our lives easy with udev we take the basename of the source argument and
124*d65368beSAndrew Jeffery 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
125*d65368beSAndrew Jeffery 		 * rule to pass the device of interest to the systemd unit.
126*d65368beSAndrew Jeffery 		 */
12711cd254bSAndrew Jeffery 		strncpy(devpath, argv[optind], sizeof(devpath));
12811cd254bSAndrew Jeffery 		devpath[PATH_MAX - 1] = '\0';
12911cd254bSAndrew Jeffery 		devid = basename(devpath);
13011cd254bSAndrew Jeffery 
13111cd254bSAndrew Jeffery 		strncpy(devnode, "/dev/", sizeof(devnode));
13211cd254bSAndrew Jeffery 		strncat(devnode, devid, sizeof(devnode));
13311cd254bSAndrew Jeffery 		devnode[PATH_MAX - 1] = '\0';
13411cd254bSAndrew Jeffery 
13511cd254bSAndrew Jeffery 		if ((source = open(devnode, O_RDONLY)) == -1)
13611cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", devnode);
13711cd254bSAndrew Jeffery 
13811cd254bSAndrew Jeffery 		optind++;
13911cd254bSAndrew Jeffery 	}
14011cd254bSAndrew Jeffery 
141*d65368beSAndrew Jeffery 	/* Handle the sink argument, if any */
14211cd254bSAndrew Jeffery 	if (optind < argc) {
143*d65368beSAndrew Jeffery 		/*
144*d65368beSAndrew Jeffery 		 * Just open the sink path directly. If we ever need different behaviour then we
145*d65368beSAndrew Jeffery 		 * patch this bit when we know what we need.
146*d65368beSAndrew Jeffery 		 */
14711cd254bSAndrew Jeffery 		if ((sink = open(argv[optind], O_WRONLY)) == -1)
14811cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
14911cd254bSAndrew Jeffery 
15011cd254bSAndrew Jeffery 		optind++;
15111cd254bSAndrew Jeffery 	}
15211cd254bSAndrew Jeffery 
153*d65368beSAndrew Jeffery 	/* Check we're done with the command-line */
15411cd254bSAndrew Jeffery 	if (optind < argc)
15511cd254bSAndrew Jeffery 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
15611cd254bSAndrew Jeffery 
157*d65368beSAndrew Jeffery 	/* Trigger the actions on the sink when we receive an event from the source */
15811cd254bSAndrew Jeffery 	if (process(source, sink) < 0)
15911cd254bSAndrew Jeffery 		errx(EXIT_FAILURE, "Failure while processing command stream");
16011cd254bSAndrew Jeffery 
16111cd254bSAndrew Jeffery 	return 0;
16211cd254bSAndrew Jeffery }
163