blob: cc9d52d9fa675abf5fbd00826ad882a9e1b007ce [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 Kerr957818b2016-03-08 14:35:15 +080032 int tty_sirq;
33 int tty_lpc_addr;
Jeremy Kerrd831f962016-01-29 17:18:01 +080034 int tty_fd;
35 int console_fd_in;
36 int console_fd_out;
37 bool console_is_tty;
38 struct termios orig_termios;
39 int esc_str_pos;
40};
41
42static void usage(const char *progname)
43{
44 fprintf(stderr,
45"usage: %s [options]\n"
46"\n"
47"Options:\n"
Jeremy Kerr17217842016-01-29 18:44:21 +080048" --device <TTY> Use serial device TTY (eg, ttyS0)\n"
Jeremy Kerrd831f962016-01-29 17:18:01 +080049"",
50 progname);
51}
52
Jeremy Kerr17217842016-01-29 18:44:21 +080053/* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
54static int tty_find_device(struct console_ctx *ctx)
55{
56 char *tty_class_device_link;
57 char *tty_device_tty_dir;
58 char *tty_device_reldir;
59 int rc;
60
61 rc = -1;
62 tty_class_device_link = NULL;
63 tty_device_tty_dir = NULL;
64 tty_device_reldir = NULL;
65
66 rc = asprintf(&tty_class_device_link,
67 "/sys/class/tty/%s", ctx->tty_kname);
68 if (rc < 0)
69 return -1;
70
71 tty_device_tty_dir = realpath(tty_class_device_link, NULL);
72 if (rc < 0) {
73 warn("Can't query sysfs for device %s", ctx->tty_kname);
74 goto out_free;
75 }
76
77 rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
78 if (rc < 0)
79 goto out_free;
80
81 ctx->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
82 if (!ctx->tty_sysfs_devnode)
83 warn("Can't find parent device for %s", ctx->tty_kname);
84
85
86 /* todo: lookup from major/minor info in sysfs, in case udev has
87 * renamed us */
88 rc = asprintf(&ctx->tty_dev, "/dev/%s", ctx->tty_kname);
89 if (rc < 0)
90 goto out_free;
91
92 rc = 0;
93
94out_free:
95 free(tty_class_device_link);
96 free(tty_device_tty_dir);
97 free(tty_device_reldir);
98 return rc;
99}
100
Jeremy Kerr957818b2016-03-08 14:35:15 +0800101static int tty_set_sysfs_attr(struct console_ctx *ctx, const char *name,
102 int value)
103{
104 char *path;
105 FILE *fp;
106 int rc;
107
108 rc = asprintf(&path, "%s/%s", ctx->tty_sysfs_devnode, name);
109 if (rc < 0)
110 return -1;
111
112 fp = fopen(path, "w");
113 if (!fp) {
114 warn("Can't access attribute %s on device %s",
115 name, ctx->tty_kname);
116 rc = -1;
117 goto out_free;
118 }
119 setvbuf(fp, NULL, _IONBF, 0);
120
121 rc = fprintf(fp, "0x%x", value);
122 if (rc < 0)
123 warn("Error writing to %s attribute of device %s",
124 name, ctx->tty_kname);
125 fclose(fp);
126
127
128
129out_free:
130 free(path);
131 return rc;
132}
133
Jeremy Kerrd831f962016-01-29 17:18:01 +0800134/**
135 * Open and initialise the serial device
136 */
137static int tty_init_io(struct console_ctx *ctx)
138{
Jeremy Kerr957818b2016-03-08 14:35:15 +0800139 if (ctx->tty_sirq)
140 tty_set_sysfs_attr(ctx, "sirq", ctx->tty_sirq);
141 if (ctx->tty_lpc_addr)
142 tty_set_sysfs_attr(ctx, "lpc_address", ctx->tty_lpc_addr);
143 tty_set_sysfs_attr(ctx, "enabled", 1);
144
Jeremy Kerr17217842016-01-29 18:44:21 +0800145
Jeremy Kerrd831f962016-01-29 17:18:01 +0800146 ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
147 if (ctx->tty_fd <= 0) {
148 warn("Can't open tty %s", ctx->tty_dev);
149 return -1;
150 }
151
152 /* Disable character delay. We may want to later enable this when
153 * we detect larger amounts of data
154 */
155 fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
156
157 return 0;
158}
159
160/*
161 * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
162 * put it in canonical mode
163 */
164static int console_init_io(struct console_ctx *ctx)
165{
166 struct termios termios;
167 int rc;
168
169 ctx->console_fd_in = STDIN_FILENO;
170 ctx->console_fd_out = STDOUT_FILENO;
171 ctx->console_is_tty = isatty(ctx->console_fd_in);
172
173 if (!ctx->console_is_tty)
174 return 0;
175
176 rc = tcgetattr(ctx->console_fd_in, &termios);
177 if (rc) {
178 warn("Can't get terminal attributes for console");
179 return -1;
180 }
181 memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
182 cfmakeraw(&termios);
183
184 rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
185 if (rc) {
186 warn("Can't set terminal attributes for console");
187 return -1;
188 }
189
190 return 0;
191}
192
193static int console_process_input(struct console_ctx *ctx,
194 uint8_t *buf, size_t len)
195{
196 unsigned long i;
197 uint8_t e;
198
199 e = esc_str[ctx->esc_str_pos];
200
201 for (i = 0; i < len; i++) {
202 if (buf[i] == e) {
203 ctx->esc_str_pos++;
204 if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
205 return 1;
206 e = esc_str[ctx->esc_str_pos];
207 } else {
208
209 ctx->esc_str_pos = 0;
210 }
211 }
212 return 0;
213}
214
215static void console_restore_termios(struct console_ctx *ctx)
216{
217 if (ctx->console_is_tty)
218 tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
219}
220
221static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
222{
223 size_t pos;
224 ssize_t rc;
225
226 for (pos = 0; pos < len; pos += rc) {
227 rc = write(fd, buf + pos, len - pos);
228 if (rc <= 0) {
229 warn("Write error");
230 return -1;
231 }
232 }
233
234 return 0;
235}
236
237int run_console(struct console_ctx *ctx)
238{
239 struct pollfd pollfds[2];
240 int rc, len;
241
242 pollfds[0].fd = ctx->tty_fd;
243 pollfds[0].events = POLLIN;
244 pollfds[1].fd = ctx->console_fd_in;
245 pollfds[1].events = POLLIN;
246
247 for (;;) {
248 uint8_t buf[4096];
249
250 rc = poll(pollfds, 2, -1);
251 if (rc < 0) {
252 warn("poll error");
253 return -1;
254 }
255
256 if (pollfds[0].revents) {
257 rc = read(ctx->tty_fd, buf, sizeof(buf));
258 if (rc <= 0) {
259 warn("Error reading from tty device");
260 return -1;
261 }
262 rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
263 if (rc < 0)
264 return -1;
265 }
266 if (pollfds[1].revents) {
267 rc = read(ctx->console_fd_in, buf, sizeof(buf));
268 if (rc == 0)
269 return 0;
270
271 if (rc <= 0) {
272 warn("Error reading from console");
273 return -1;
274 }
275 len = rc;
276 rc = console_process_input(ctx, buf, len);
277 if (rc) {
278 rc = 0;
279 return 0;
280 }
281 rc = write_buf_to_fd(ctx->tty_fd, buf, len);
282 if (rc < 0)
283 return -1;
284 }
285 }
286}
287
288static const struct option options[] = {
289 { "device", required_argument, 0, 'd'},
Jeremy Kerr957818b2016-03-08 14:35:15 +0800290 { "sirq", required_argument, 0, 's'},
291 { "lpc-addr", required_argument, 0, 'l'},
Jeremy Kerrd831f962016-01-29 17:18:01 +0800292 { },
293};
294
295int main(int argc, char **argv)
296{
297 struct console_ctx *ctx;
298 int rc;
299
300 ctx = malloc(sizeof(struct console_ctx));
301 memset(ctx, 0, sizeof(*ctx));
Jeremy Kerr957818b2016-03-08 14:35:15 +0800302 rc = -1;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800303
304 for (;;) {
Jeremy Kerr957818b2016-03-08 14:35:15 +0800305 char *endp;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800306 int c, idx;
307
Jeremy Kerr957818b2016-03-08 14:35:15 +0800308 c = getopt_long(argc, argv, "d:s:l:", options, &idx);
Jeremy Kerrd831f962016-01-29 17:18:01 +0800309 if (c == -1)
310 break;
311
312 switch (c) {
313 case 'd':
Jeremy Kerr17217842016-01-29 18:44:21 +0800314 ctx->tty_kname = optarg;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800315 break;
Jeremy Kerr957818b2016-03-08 14:35:15 +0800316 case 'l':
317 ctx->tty_lpc_addr = strtoul(optarg, &endp, 0);
318 if (endp == optarg) {
319 warnx("Invalid sirq: '%s'", optarg);
320 goto out_free;
321 }
322 break;
323
324 case 's':
325 ctx->tty_sirq = strtoul(optarg, &endp, 0);
326 if (endp == optarg) {
327 warnx("Invalid sirq: '%s'", optarg);
328 goto out_free;
329 }
330 break;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800331
332 case 'h':
333 case '?':
334 usage(argv[0]);
Jeremy Kerr957818b2016-03-08 14:35:15 +0800335 rc = 0;
336 goto out_free;
Jeremy Kerrd831f962016-01-29 17:18:01 +0800337 }
338 }
339
Jeremy Kerr17217842016-01-29 18:44:21 +0800340 if (!ctx->tty_kname) {
Jeremy Kerrd831f962016-01-29 17:18:01 +0800341 fprintf(stderr,
342 "Error: No TTY device specified (use --device)\n");
343 return EXIT_FAILURE;
344 }
345
Jeremy Kerr17217842016-01-29 18:44:21 +0800346 rc = tty_find_device(ctx);
347 if (rc)
348 return EXIT_FAILURE;
349
Jeremy Kerrd831f962016-01-29 17:18:01 +0800350 rc = tty_init_io(ctx);
351 if (rc)
352 return EXIT_FAILURE;
353
354 rc = console_init_io(ctx);
355 if (rc)
356 return EXIT_FAILURE;
357
358 rc = run_console(ctx);
359
360 console_restore_termios(ctx);
361
Jeremy Kerr957818b2016-03-08 14:35:15 +0800362out_free:
Jeremy Kerr17217842016-01-29 18:44:21 +0800363 free(ctx->tty_sysfs_devnode);
364 free(ctx->tty_dev);
Jeremy Kerrd831f962016-01-29 17:18:01 +0800365 free(ctx);
366
367 return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
368}