blob: 005af60e738967cfe1a8711d15a5e85059a9f8a3 [file] [log] [blame]
Jeremy Kerrd831f962016-01-29 17:18:01 +08001/**
2 * Console server process for OpenBMC
3 *
4 * Copyright © 2016 IBM Corporation <jk@ozlabs.org>
5 */
6
Jeremy Kerr17217842016-01-29 18:44:21 +08007#define _GNU_SOURCE
8
Jeremy Kerrd831f962016-01-29 17:18:01 +08009#include <stdint.h>
10#include <stdbool.h>
11#include <stdlib.h>
12#include <stdio.h>
13#include <fcntl.h>
14#include <unistd.h>
15#include <err.h>
16#include <termios.h>
17#include <string.h>
18#include <getopt.h>
Jeremy Kerr17217842016-01-29 18:44:21 +080019#include <limits.h>
Jeremy Kerrd831f962016-01-29 17:18:01 +080020
21#include <sys/types.h>
22#include <sys/poll.h>
23
24#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
25
26static const char esc_str[] = { '\r', '~', '.' };
27
28struct console_ctx {
Jeremy Kerr17217842016-01-29 18:44:21 +080029 const char *tty_kname;
30 char *tty_sysfs_devnode;
31 char *tty_dev;
Jeremy Kerrd831f962016-01-29 17:18:01 +080032 int tty_fd;
33 int console_fd_in;
34 int console_fd_out;
35 bool console_is_tty;
36 struct termios orig_termios;
37 int esc_str_pos;
38};
39
40static void usage(const char *progname)
41{
42 fprintf(stderr,
43"usage: %s [options]\n"
44"\n"
45"Options:\n"
Jeremy Kerr17217842016-01-29 18:44:21 +080046" --device <TTY> Use serial device TTY (eg, ttyS0)\n"
Jeremy Kerrd831f962016-01-29 17:18:01 +080047"",
48 progname);
49}
50
Jeremy Kerr17217842016-01-29 18:44:21 +080051/* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
52static int tty_find_device(struct console_ctx *ctx)
53{
54 char *tty_class_device_link;
55 char *tty_device_tty_dir;
56 char *tty_device_reldir;
57 int rc;
58
59 rc = -1;
60 tty_class_device_link = NULL;
61 tty_device_tty_dir = NULL;
62 tty_device_reldir = NULL;
63
64 rc = asprintf(&tty_class_device_link,
65 "/sys/class/tty/%s", ctx->tty_kname);
66 if (rc < 0)
67 return -1;
68
69 tty_device_tty_dir = realpath(tty_class_device_link, NULL);
70 if (rc < 0) {
71 warn("Can't query sysfs for device %s", ctx->tty_kname);
72 goto out_free;
73 }
74
75 rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
76 if (rc < 0)
77 goto out_free;
78
79 ctx->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
80 if (!ctx->tty_sysfs_devnode)
81 warn("Can't find parent device for %s", ctx->tty_kname);
82
83
84 /* todo: lookup from major/minor info in sysfs, in case udev has
85 * renamed us */
86 rc = asprintf(&ctx->tty_dev, "/dev/%s", ctx->tty_kname);
87 if (rc < 0)
88 goto out_free;
89
90 rc = 0;
91
92out_free:
93 free(tty_class_device_link);
94 free(tty_device_tty_dir);
95 free(tty_device_reldir);
96 return rc;
97}
98
Jeremy Kerrd831f962016-01-29 17:18:01 +080099/**
100 * Open and initialise the serial device
101 */
102static int tty_init_io(struct console_ctx *ctx)
103{
Jeremy Kerr17217842016-01-29 18:44:21 +0800104
Jeremy Kerrd831f962016-01-29 17:18:01 +0800105 ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
106 if (ctx->tty_fd <= 0) {
107 warn("Can't open tty %s", ctx->tty_dev);
108 return -1;
109 }
110
111 /* Disable character delay. We may want to later enable this when
112 * we detect larger amounts of data
113 */
114 fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
115
116 return 0;
117}
118
119/*
120 * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
121 * put it in canonical mode
122 */
123static int console_init_io(struct console_ctx *ctx)
124{
125 struct termios termios;
126 int rc;
127
128 ctx->console_fd_in = STDIN_FILENO;
129 ctx->console_fd_out = STDOUT_FILENO;
130 ctx->console_is_tty = isatty(ctx->console_fd_in);
131
132 if (!ctx->console_is_tty)
133 return 0;
134
135 rc = tcgetattr(ctx->console_fd_in, &termios);
136 if (rc) {
137 warn("Can't get terminal attributes for console");
138 return -1;
139 }
140 memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
141 cfmakeraw(&termios);
142
143 rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
144 if (rc) {
145 warn("Can't set terminal attributes for console");
146 return -1;
147 }
148
149 return 0;
150}
151
152static int console_process_input(struct console_ctx *ctx,
153 uint8_t *buf, size_t len)
154{
155 unsigned long i;
156 uint8_t e;
157
158 e = esc_str[ctx->esc_str_pos];
159
160 for (i = 0; i < len; i++) {
161 if (buf[i] == e) {
162 ctx->esc_str_pos++;
163 if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
164 return 1;
165 e = esc_str[ctx->esc_str_pos];
166 } else {
167
168 ctx->esc_str_pos = 0;
169 }
170 }
171 return 0;
172}
173
174static void console_restore_termios(struct console_ctx *ctx)
175{
176 if (ctx->console_is_tty)
177 tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
178}
179
180static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
181{
182 size_t pos;
183 ssize_t rc;
184
185 for (pos = 0; pos < len; pos += rc) {
186 rc = write(fd, buf + pos, len - pos);
187 if (rc <= 0) {
188 warn("Write error");
189 return -1;
190 }
191 }
192
193 return 0;
194}
195
196int run_console(struct console_ctx *ctx)
197{
198 struct pollfd pollfds[2];
199 int rc, len;
200
201 pollfds[0].fd = ctx->tty_fd;
202 pollfds[0].events = POLLIN;
203 pollfds[1].fd = ctx->console_fd_in;
204 pollfds[1].events = POLLIN;
205
206 for (;;) {
207 uint8_t buf[4096];
208
209 rc = poll(pollfds, 2, -1);
210 if (rc < 0) {
211 warn("poll error");
212 return -1;
213 }
214
215 if (pollfds[0].revents) {
216 rc = read(ctx->tty_fd, buf, sizeof(buf));
217 if (rc <= 0) {
218 warn("Error reading from tty device");
219 return -1;
220 }
221 rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
222 if (rc < 0)
223 return -1;
224 }
225 if (pollfds[1].revents) {
226 rc = read(ctx->console_fd_in, buf, sizeof(buf));
227 if (rc == 0)
228 return 0;
229
230 if (rc <= 0) {
231 warn("Error reading from console");
232 return -1;
233 }
234 len = rc;
235 rc = console_process_input(ctx, buf, len);
236 if (rc) {
237 rc = 0;
238 return 0;
239 }
240 rc = write_buf_to_fd(ctx->tty_fd, buf, len);
241 if (rc < 0)
242 return -1;
243 }
244 }
245}
246
247static const struct option options[] = {
248 { "device", required_argument, 0, 'd'},
249 { },
250};
251
252int main(int argc, char **argv)
253{
254 struct console_ctx *ctx;
255 int rc;
256
257 ctx = malloc(sizeof(struct console_ctx));
258 memset(ctx, 0, sizeof(*ctx));
259
260 for (;;) {
261 int c, idx;
262
263 c = getopt_long(argc, argv, "d", options, &idx);
264 if (c == -1)
265 break;
266
267 switch (c) {
268 case 'd':
Jeremy Kerr17217842016-01-29 18:44:21 +0800269 ctx->tty_kname = optarg;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800270 break;
271
272 case 'h':
273 case '?':
274 usage(argv[0]);
275 break;
276 }
277 }
278
Jeremy Kerr17217842016-01-29 18:44:21 +0800279 if (!ctx->tty_kname) {
Jeremy Kerrd831f962016-01-29 17:18:01 +0800280 fprintf(stderr,
281 "Error: No TTY device specified (use --device)\n");
282 return EXIT_FAILURE;
283 }
284
Jeremy Kerr17217842016-01-29 18:44:21 +0800285 rc = tty_find_device(ctx);
286 if (rc)
287 return EXIT_FAILURE;
288
289 return EXIT_SUCCESS;
290
Jeremy Kerrd831f962016-01-29 17:18:01 +0800291 rc = tty_init_io(ctx);
292 if (rc)
293 return EXIT_FAILURE;
294
295 rc = console_init_io(ctx);
296 if (rc)
297 return EXIT_FAILURE;
298
299 rc = run_console(ctx);
300
301 console_restore_termios(ctx);
302
Jeremy Kerr17217842016-01-29 18:44:21 +0800303 free(ctx->tty_sysfs_devnode);
304 free(ctx->tty_dev);
Jeremy Kerrd831f962016-01-29 17:18:01 +0800305 free(ctx);
306
307 return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
308}