xref: /openbmc/obmc-console/console-server.c (revision 957818b41f7fc6b7d1a090098fbd34722041aba3)
1d831f960SJeremy Kerr /**
2d831f960SJeremy Kerr  * Console server process for OpenBMC
3d831f960SJeremy Kerr  *
4d831f960SJeremy Kerr  * Copyright © 2016 IBM Corporation <jk@ozlabs.org>
5d831f960SJeremy Kerr  */
6d831f960SJeremy Kerr 
717217845SJeremy Kerr #define _GNU_SOURCE
817217845SJeremy Kerr 
9d831f960SJeremy Kerr #include <stdint.h>
10d831f960SJeremy Kerr #include <stdbool.h>
11d831f960SJeremy Kerr #include <stdlib.h>
12d831f960SJeremy Kerr #include <stdio.h>
13d831f960SJeremy Kerr #include <fcntl.h>
14d831f960SJeremy Kerr #include <unistd.h>
15d831f960SJeremy Kerr #include <err.h>
16d831f960SJeremy Kerr #include <termios.h>
17d831f960SJeremy Kerr #include <string.h>
18d831f960SJeremy Kerr #include <getopt.h>
1917217845SJeremy Kerr #include <limits.h>
20d831f960SJeremy Kerr 
21d831f960SJeremy Kerr #include <sys/types.h>
22d831f960SJeremy Kerr #include <sys/poll.h>
23d831f960SJeremy Kerr 
24d831f960SJeremy Kerr #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
25d831f960SJeremy Kerr 
26d831f960SJeremy Kerr static const char esc_str[] = { '\r', '~', '.' };
27d831f960SJeremy Kerr 
28d831f960SJeremy Kerr struct console_ctx {
2917217845SJeremy Kerr 	const char	*tty_kname;
3017217845SJeremy Kerr 	char		*tty_sysfs_devnode;
3117217845SJeremy Kerr 	char		*tty_dev;
32*957818b4SJeremy Kerr 	int		tty_sirq;
33*957818b4SJeremy Kerr 	int		tty_lpc_addr;
34d831f960SJeremy Kerr 	int		tty_fd;
35d831f960SJeremy Kerr 	int		console_fd_in;
36d831f960SJeremy Kerr 	int		console_fd_out;
37d831f960SJeremy Kerr 	bool		console_is_tty;
38d831f960SJeremy Kerr 	struct termios	orig_termios;
39d831f960SJeremy Kerr 	int		esc_str_pos;
40d831f960SJeremy Kerr };
41d831f960SJeremy Kerr 
42d831f960SJeremy Kerr static void usage(const char *progname)
43d831f960SJeremy Kerr {
44d831f960SJeremy Kerr 	fprintf(stderr,
45d831f960SJeremy Kerr "usage: %s [options]\n"
46d831f960SJeremy Kerr "\n"
47d831f960SJeremy Kerr "Options:\n"
4817217845SJeremy Kerr "  --device <TTY>  Use serial device TTY (eg, ttyS0)\n"
49d831f960SJeremy Kerr "",
50d831f960SJeremy Kerr 		progname);
51d831f960SJeremy Kerr }
52d831f960SJeremy Kerr 
5317217845SJeremy Kerr /* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
5417217845SJeremy Kerr static int tty_find_device(struct console_ctx *ctx)
5517217845SJeremy Kerr {
5617217845SJeremy Kerr 	char *tty_class_device_link;
5717217845SJeremy Kerr 	char *tty_device_tty_dir;
5817217845SJeremy Kerr 	char *tty_device_reldir;
5917217845SJeremy Kerr 	int rc;
6017217845SJeremy Kerr 
6117217845SJeremy Kerr 	rc = -1;
6217217845SJeremy Kerr 	tty_class_device_link = NULL;
6317217845SJeremy Kerr 	tty_device_tty_dir = NULL;
6417217845SJeremy Kerr 	tty_device_reldir = NULL;
6517217845SJeremy Kerr 
6617217845SJeremy Kerr 	rc = asprintf(&tty_class_device_link,
6717217845SJeremy Kerr 			"/sys/class/tty/%s", ctx->tty_kname);
6817217845SJeremy Kerr 	if (rc < 0)
6917217845SJeremy Kerr 		return -1;
7017217845SJeremy Kerr 
7117217845SJeremy Kerr 	tty_device_tty_dir = realpath(tty_class_device_link, NULL);
7217217845SJeremy Kerr 	if (rc < 0) {
7317217845SJeremy Kerr 		warn("Can't query sysfs for device %s", ctx->tty_kname);
7417217845SJeremy Kerr 		goto out_free;
7517217845SJeremy Kerr 	}
7617217845SJeremy Kerr 
7717217845SJeremy Kerr 	rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
7817217845SJeremy Kerr 	if (rc < 0)
7917217845SJeremy Kerr 		goto out_free;
8017217845SJeremy Kerr 
8117217845SJeremy Kerr 	ctx->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
8217217845SJeremy Kerr 	if (!ctx->tty_sysfs_devnode)
8317217845SJeremy Kerr 		warn("Can't find parent device for %s", ctx->tty_kname);
8417217845SJeremy Kerr 
8517217845SJeremy Kerr 
8617217845SJeremy Kerr 	/* todo: lookup from major/minor info in sysfs, in case udev has
8717217845SJeremy Kerr 	 * renamed us */
8817217845SJeremy Kerr 	rc = asprintf(&ctx->tty_dev, "/dev/%s", ctx->tty_kname);
8917217845SJeremy Kerr 	if (rc < 0)
9017217845SJeremy Kerr 		goto out_free;
9117217845SJeremy Kerr 
9217217845SJeremy Kerr 	rc = 0;
9317217845SJeremy Kerr 
9417217845SJeremy Kerr out_free:
9517217845SJeremy Kerr 	free(tty_class_device_link);
9617217845SJeremy Kerr 	free(tty_device_tty_dir);
9717217845SJeremy Kerr 	free(tty_device_reldir);
9817217845SJeremy Kerr 	return rc;
9917217845SJeremy Kerr }
10017217845SJeremy Kerr 
101*957818b4SJeremy Kerr static int tty_set_sysfs_attr(struct console_ctx *ctx, const char *name,
102*957818b4SJeremy Kerr 		int value)
103*957818b4SJeremy Kerr {
104*957818b4SJeremy Kerr 	char *path;
105*957818b4SJeremy Kerr 	FILE *fp;
106*957818b4SJeremy Kerr 	int rc;
107*957818b4SJeremy Kerr 
108*957818b4SJeremy Kerr 	rc = asprintf(&path, "%s/%s", ctx->tty_sysfs_devnode, name);
109*957818b4SJeremy Kerr 	if (rc < 0)
110*957818b4SJeremy Kerr 		return -1;
111*957818b4SJeremy Kerr 
112*957818b4SJeremy Kerr 	fp = fopen(path, "w");
113*957818b4SJeremy Kerr 	if (!fp) {
114*957818b4SJeremy Kerr 		warn("Can't access attribute %s on device %s",
115*957818b4SJeremy Kerr 				name, ctx->tty_kname);
116*957818b4SJeremy Kerr 		rc = -1;
117*957818b4SJeremy Kerr 		goto out_free;
118*957818b4SJeremy Kerr 	}
119*957818b4SJeremy Kerr 	setvbuf(fp, NULL, _IONBF, 0);
120*957818b4SJeremy Kerr 
121*957818b4SJeremy Kerr 	rc = fprintf(fp, "0x%x", value);
122*957818b4SJeremy Kerr 	if (rc < 0)
123*957818b4SJeremy Kerr 		warn("Error writing to %s attribute of device %s",
124*957818b4SJeremy Kerr 				name, ctx->tty_kname);
125*957818b4SJeremy Kerr 	fclose(fp);
126*957818b4SJeremy Kerr 
127*957818b4SJeremy Kerr 
128*957818b4SJeremy Kerr 
129*957818b4SJeremy Kerr out_free:
130*957818b4SJeremy Kerr 	free(path);
131*957818b4SJeremy Kerr 	return rc;
132*957818b4SJeremy Kerr }
133*957818b4SJeremy Kerr 
134d831f960SJeremy Kerr /**
135d831f960SJeremy Kerr  * Open and initialise the serial device
136d831f960SJeremy Kerr  */
137d831f960SJeremy Kerr static int tty_init_io(struct console_ctx *ctx)
138d831f960SJeremy Kerr {
139*957818b4SJeremy Kerr 	if (ctx->tty_sirq)
140*957818b4SJeremy Kerr 		tty_set_sysfs_attr(ctx, "sirq", ctx->tty_sirq);
141*957818b4SJeremy Kerr 	if (ctx->tty_lpc_addr)
142*957818b4SJeremy Kerr 		tty_set_sysfs_attr(ctx, "lpc_address", ctx->tty_lpc_addr);
143*957818b4SJeremy Kerr 	tty_set_sysfs_attr(ctx, "enabled", 1);
144*957818b4SJeremy Kerr 
14517217845SJeremy Kerr 
146d831f960SJeremy Kerr 	ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
147d831f960SJeremy Kerr 	if (ctx->tty_fd <= 0) {
148d831f960SJeremy Kerr 		warn("Can't open tty %s", ctx->tty_dev);
149d831f960SJeremy Kerr 		return -1;
150d831f960SJeremy Kerr 	}
151d831f960SJeremy Kerr 
152d831f960SJeremy Kerr 	/* Disable character delay. We may want to later enable this when
153d831f960SJeremy Kerr 	 * we detect larger amounts of data
154d831f960SJeremy Kerr 	 */
155d831f960SJeremy Kerr 	fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
156d831f960SJeremy Kerr 
157d831f960SJeremy Kerr 	return 0;
158d831f960SJeremy Kerr }
159d831f960SJeremy Kerr 
160d831f960SJeremy Kerr /*
161d831f960SJeremy Kerr  * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
162d831f960SJeremy Kerr  * put it in canonical mode
163d831f960SJeremy Kerr  */
164d831f960SJeremy Kerr static int console_init_io(struct console_ctx *ctx)
165d831f960SJeremy Kerr {
166d831f960SJeremy Kerr 	struct termios termios;
167d831f960SJeremy Kerr 	int rc;
168d831f960SJeremy Kerr 
169d831f960SJeremy Kerr 	ctx->console_fd_in = STDIN_FILENO;
170d831f960SJeremy Kerr 	ctx->console_fd_out = STDOUT_FILENO;
171d831f960SJeremy Kerr 	ctx->console_is_tty = isatty(ctx->console_fd_in);
172d831f960SJeremy Kerr 
173d831f960SJeremy Kerr 	if (!ctx->console_is_tty)
174d831f960SJeremy Kerr 		return 0;
175d831f960SJeremy Kerr 
176d831f960SJeremy Kerr 	rc = tcgetattr(ctx->console_fd_in, &termios);
177d831f960SJeremy Kerr 	if (rc) {
178d831f960SJeremy Kerr 		warn("Can't get terminal attributes for console");
179d831f960SJeremy Kerr 		return -1;
180d831f960SJeremy Kerr 	}
181d831f960SJeremy Kerr 	memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
182d831f960SJeremy Kerr 	cfmakeraw(&termios);
183d831f960SJeremy Kerr 
184d831f960SJeremy Kerr 	rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
185d831f960SJeremy Kerr 	if (rc) {
186d831f960SJeremy Kerr 		warn("Can't set terminal attributes for console");
187d831f960SJeremy Kerr 		return -1;
188d831f960SJeremy Kerr 	}
189d831f960SJeremy Kerr 
190d831f960SJeremy Kerr 	return 0;
191d831f960SJeremy Kerr }
192d831f960SJeremy Kerr 
193d831f960SJeremy Kerr static int console_process_input(struct console_ctx *ctx,
194d831f960SJeremy Kerr 		uint8_t *buf, size_t len)
195d831f960SJeremy Kerr {
196d831f960SJeremy Kerr 	unsigned long i;
197d831f960SJeremy Kerr 	uint8_t e;
198d831f960SJeremy Kerr 
199d831f960SJeremy Kerr 	e = esc_str[ctx->esc_str_pos];
200d831f960SJeremy Kerr 
201d831f960SJeremy Kerr 	for (i = 0; i < len; i++) {
202d831f960SJeremy Kerr 		if (buf[i] == e) {
203d831f960SJeremy Kerr 			ctx->esc_str_pos++;
204d831f960SJeremy Kerr 			if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
205d831f960SJeremy Kerr 				return 1;
206d831f960SJeremy Kerr 			e = esc_str[ctx->esc_str_pos];
207d831f960SJeremy Kerr 		} else {
208d831f960SJeremy Kerr 
209d831f960SJeremy Kerr 			ctx->esc_str_pos = 0;
210d831f960SJeremy Kerr 		}
211d831f960SJeremy Kerr 	}
212d831f960SJeremy Kerr 	return 0;
213d831f960SJeremy Kerr }
214d831f960SJeremy Kerr 
215d831f960SJeremy Kerr static void console_restore_termios(struct console_ctx *ctx)
216d831f960SJeremy Kerr {
217d831f960SJeremy Kerr 	if (ctx->console_is_tty)
218d831f960SJeremy Kerr 		tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
219d831f960SJeremy Kerr }
220d831f960SJeremy Kerr 
221d831f960SJeremy Kerr static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
222d831f960SJeremy Kerr {
223d831f960SJeremy Kerr 	size_t pos;
224d831f960SJeremy Kerr 	ssize_t rc;
225d831f960SJeremy Kerr 
226d831f960SJeremy Kerr 	for (pos = 0; pos < len; pos += rc) {
227d831f960SJeremy Kerr 		rc = write(fd, buf + pos, len - pos);
228d831f960SJeremy Kerr 		if (rc <= 0) {
229d831f960SJeremy Kerr 			warn("Write error");
230d831f960SJeremy Kerr 			return -1;
231d831f960SJeremy Kerr 		}
232d831f960SJeremy Kerr 	}
233d831f960SJeremy Kerr 
234d831f960SJeremy Kerr 	return 0;
235d831f960SJeremy Kerr }
236d831f960SJeremy Kerr 
237d831f960SJeremy Kerr int run_console(struct console_ctx *ctx)
238d831f960SJeremy Kerr {
239d831f960SJeremy Kerr 	struct pollfd pollfds[2];
240d831f960SJeremy Kerr 	int rc, len;
241d831f960SJeremy Kerr 
242d831f960SJeremy Kerr 	pollfds[0].fd = ctx->tty_fd;
243d831f960SJeremy Kerr 	pollfds[0].events = POLLIN;
244d831f960SJeremy Kerr 	pollfds[1].fd = ctx->console_fd_in;
245d831f960SJeremy Kerr 	pollfds[1].events = POLLIN;
246d831f960SJeremy Kerr 
247d831f960SJeremy Kerr 	for (;;) {
248d831f960SJeremy Kerr 		uint8_t buf[4096];
249d831f960SJeremy Kerr 
250d831f960SJeremy Kerr 		rc = poll(pollfds, 2, -1);
251d831f960SJeremy Kerr 		if (rc < 0) {
252d831f960SJeremy Kerr 			warn("poll error");
253d831f960SJeremy Kerr 			return -1;
254d831f960SJeremy Kerr 		}
255d831f960SJeremy Kerr 
256d831f960SJeremy Kerr 		if (pollfds[0].revents) {
257d831f960SJeremy Kerr 			rc = read(ctx->tty_fd, buf, sizeof(buf));
258d831f960SJeremy Kerr 			if (rc <= 0) {
259d831f960SJeremy Kerr 				warn("Error reading from tty device");
260d831f960SJeremy Kerr 				return -1;
261d831f960SJeremy Kerr 			}
262d831f960SJeremy Kerr 			rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
263d831f960SJeremy Kerr 			if (rc < 0)
264d831f960SJeremy Kerr 				return -1;
265d831f960SJeremy Kerr 		}
266d831f960SJeremy Kerr 		if (pollfds[1].revents) {
267d831f960SJeremy Kerr 			rc = read(ctx->console_fd_in, buf, sizeof(buf));
268d831f960SJeremy Kerr 			if (rc == 0)
269d831f960SJeremy Kerr 				return 0;
270d831f960SJeremy Kerr 
271d831f960SJeremy Kerr 			if (rc <= 0) {
272d831f960SJeremy Kerr 				warn("Error reading from console");
273d831f960SJeremy Kerr 				return -1;
274d831f960SJeremy Kerr 			}
275d831f960SJeremy Kerr 			len = rc;
276d831f960SJeremy Kerr 			rc = console_process_input(ctx, buf, len);
277d831f960SJeremy Kerr 			if (rc) {
278d831f960SJeremy Kerr 				rc = 0;
279d831f960SJeremy Kerr 				return 0;
280d831f960SJeremy Kerr 			}
281d831f960SJeremy Kerr 			rc = write_buf_to_fd(ctx->tty_fd, buf, len);
282d831f960SJeremy Kerr 			if (rc < 0)
283d831f960SJeremy Kerr 				return -1;
284d831f960SJeremy Kerr 		}
285d831f960SJeremy Kerr 	}
286d831f960SJeremy Kerr }
287d831f960SJeremy Kerr 
288d831f960SJeremy Kerr static const struct option options[] = {
289d831f960SJeremy Kerr 	{ "device",	required_argument,	0, 'd'},
290*957818b4SJeremy Kerr 	{ "sirq",	required_argument,	0, 's'},
291*957818b4SJeremy Kerr 	{ "lpc-addr",	required_argument,	0, 'l'},
292d831f960SJeremy Kerr 	{ },
293d831f960SJeremy Kerr };
294d831f960SJeremy Kerr 
295d831f960SJeremy Kerr int main(int argc, char **argv)
296d831f960SJeremy Kerr {
297d831f960SJeremy Kerr 	struct console_ctx *ctx;
298d831f960SJeremy Kerr 	int rc;
299d831f960SJeremy Kerr 
300d831f960SJeremy Kerr 	ctx = malloc(sizeof(struct console_ctx));
301d831f960SJeremy Kerr 	memset(ctx, 0, sizeof(*ctx));
302*957818b4SJeremy Kerr 	rc = -1;
303d831f960SJeremy Kerr 
304d831f960SJeremy Kerr 	for (;;) {
305*957818b4SJeremy Kerr 		char *endp;
306d831f960SJeremy Kerr 		int c, idx;
307d831f960SJeremy Kerr 
308*957818b4SJeremy Kerr 		c = getopt_long(argc, argv, "d:s:l:", options, &idx);
309d831f960SJeremy Kerr 		if (c == -1)
310d831f960SJeremy Kerr 			break;
311d831f960SJeremy Kerr 
312d831f960SJeremy Kerr 		switch (c) {
313d831f960SJeremy Kerr 		case 'd':
31417217845SJeremy Kerr 			ctx->tty_kname = optarg;
315d831f960SJeremy Kerr 			break;
316*957818b4SJeremy Kerr 		case 'l':
317*957818b4SJeremy Kerr 			ctx->tty_lpc_addr = strtoul(optarg, &endp, 0);
318*957818b4SJeremy Kerr 			if (endp == optarg) {
319*957818b4SJeremy Kerr 				warnx("Invalid sirq: '%s'", optarg);
320*957818b4SJeremy Kerr 				goto out_free;
321*957818b4SJeremy Kerr 			}
322*957818b4SJeremy Kerr 			break;
323*957818b4SJeremy Kerr 
324*957818b4SJeremy Kerr 		case 's':
325*957818b4SJeremy Kerr 			ctx->tty_sirq = strtoul(optarg, &endp, 0);
326*957818b4SJeremy Kerr 			if (endp == optarg) {
327*957818b4SJeremy Kerr 				warnx("Invalid sirq: '%s'", optarg);
328*957818b4SJeremy Kerr 				goto out_free;
329*957818b4SJeremy Kerr 			}
330*957818b4SJeremy Kerr 			break;
331d831f960SJeremy Kerr 
332d831f960SJeremy Kerr 		case 'h':
333d831f960SJeremy Kerr 		case '?':
334d831f960SJeremy Kerr 			usage(argv[0]);
335*957818b4SJeremy Kerr 			rc = 0;
336*957818b4SJeremy Kerr 			goto out_free;
337d831f960SJeremy Kerr 		}
338d831f960SJeremy Kerr 	}
339d831f960SJeremy Kerr 
34017217845SJeremy Kerr 	if (!ctx->tty_kname) {
341d831f960SJeremy Kerr 		fprintf(stderr,
342d831f960SJeremy Kerr 			"Error: No TTY device specified (use --device)\n");
343d831f960SJeremy Kerr 		return EXIT_FAILURE;
344d831f960SJeremy Kerr 	}
345d831f960SJeremy Kerr 
34617217845SJeremy Kerr 	rc = tty_find_device(ctx);
34717217845SJeremy Kerr 	if (rc)
34817217845SJeremy Kerr 		return EXIT_FAILURE;
34917217845SJeremy Kerr 
350d831f960SJeremy Kerr 	rc = tty_init_io(ctx);
351d831f960SJeremy Kerr 	if (rc)
352d831f960SJeremy Kerr 		return EXIT_FAILURE;
353d831f960SJeremy Kerr 
354d831f960SJeremy Kerr 	rc = console_init_io(ctx);
355d831f960SJeremy Kerr 	if (rc)
356d831f960SJeremy Kerr 		return EXIT_FAILURE;
357d831f960SJeremy Kerr 
358d831f960SJeremy Kerr 	rc = run_console(ctx);
359d831f960SJeremy Kerr 
360d831f960SJeremy Kerr 	console_restore_termios(ctx);
361d831f960SJeremy Kerr 
362*957818b4SJeremy Kerr out_free:
36317217845SJeremy Kerr 	free(ctx->tty_sysfs_devnode);
36417217845SJeremy Kerr 	free(ctx->tty_dev);
365d831f960SJeremy Kerr 	free(ctx);
366d831f960SJeremy Kerr 
367d831f960SJeremy Kerr 	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
368d831f960SJeremy Kerr }
369