blob: de6a34acf2c7e68a5751a0c5d12324aea8a1a0c2 [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
7#include <stdint.h>
8#include <stdbool.h>
9#include <stdlib.h>
10#include <stdio.h>
11#include <fcntl.h>
12#include <unistd.h>
13#include <err.h>
14#include <termios.h>
15#include <string.h>
16#include <getopt.h>
17
18#include <sys/types.h>
19#include <sys/poll.h>
20
21#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
22
23static const char esc_str[] = { '\r', '~', '.' };
24
25struct console_ctx {
26 const char *tty_dev;
27 int tty_fd;
28 int console_fd_in;
29 int console_fd_out;
30 bool console_is_tty;
31 struct termios orig_termios;
32 int esc_str_pos;
33};
34
35static void usage(const char *progname)
36{
37 fprintf(stderr,
38"usage: %s [options]\n"
39"\n"
40"Options:\n"
41" --device <TTY> Use serial device TTY\n"
42"",
43 progname);
44}
45
46/**
47 * Open and initialise the serial device
48 */
49static int tty_init_io(struct console_ctx *ctx)
50{
51 ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
52 if (ctx->tty_fd <= 0) {
53 warn("Can't open tty %s", ctx->tty_dev);
54 return -1;
55 }
56
57 /* Disable character delay. We may want to later enable this when
58 * we detect larger amounts of data
59 */
60 fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
61
62 return 0;
63}
64
65/*
66 * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
67 * put it in canonical mode
68 */
69static int console_init_io(struct console_ctx *ctx)
70{
71 struct termios termios;
72 int rc;
73
74 ctx->console_fd_in = STDIN_FILENO;
75 ctx->console_fd_out = STDOUT_FILENO;
76 ctx->console_is_tty = isatty(ctx->console_fd_in);
77
78 if (!ctx->console_is_tty)
79 return 0;
80
81 rc = tcgetattr(ctx->console_fd_in, &termios);
82 if (rc) {
83 warn("Can't get terminal attributes for console");
84 return -1;
85 }
86 memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
87 cfmakeraw(&termios);
88
89 rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
90 if (rc) {
91 warn("Can't set terminal attributes for console");
92 return -1;
93 }
94
95 return 0;
96}
97
98static int console_process_input(struct console_ctx *ctx,
99 uint8_t *buf, size_t len)
100{
101 unsigned long i;
102 uint8_t e;
103
104 e = esc_str[ctx->esc_str_pos];
105
106 for (i = 0; i < len; i++) {
107 if (buf[i] == e) {
108 ctx->esc_str_pos++;
109 if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
110 return 1;
111 e = esc_str[ctx->esc_str_pos];
112 } else {
113
114 ctx->esc_str_pos = 0;
115 }
116 }
117 return 0;
118}
119
120static void console_restore_termios(struct console_ctx *ctx)
121{
122 if (ctx->console_is_tty)
123 tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
124}
125
126static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
127{
128 size_t pos;
129 ssize_t rc;
130
131 for (pos = 0; pos < len; pos += rc) {
132 rc = write(fd, buf + pos, len - pos);
133 if (rc <= 0) {
134 warn("Write error");
135 return -1;
136 }
137 }
138
139 return 0;
140}
141
142int run_console(struct console_ctx *ctx)
143{
144 struct pollfd pollfds[2];
145 int rc, len;
146
147 pollfds[0].fd = ctx->tty_fd;
148 pollfds[0].events = POLLIN;
149 pollfds[1].fd = ctx->console_fd_in;
150 pollfds[1].events = POLLIN;
151
152 for (;;) {
153 uint8_t buf[4096];
154
155 rc = poll(pollfds, 2, -1);
156 if (rc < 0) {
157 warn("poll error");
158 return -1;
159 }
160
161 if (pollfds[0].revents) {
162 rc = read(ctx->tty_fd, buf, sizeof(buf));
163 if (rc <= 0) {
164 warn("Error reading from tty device");
165 return -1;
166 }
167 rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
168 if (rc < 0)
169 return -1;
170 }
171 if (pollfds[1].revents) {
172 rc = read(ctx->console_fd_in, buf, sizeof(buf));
173 if (rc == 0)
174 return 0;
175
176 if (rc <= 0) {
177 warn("Error reading from console");
178 return -1;
179 }
180 len = rc;
181 rc = console_process_input(ctx, buf, len);
182 if (rc) {
183 rc = 0;
184 return 0;
185 }
186 rc = write_buf_to_fd(ctx->tty_fd, buf, len);
187 if (rc < 0)
188 return -1;
189 }
190 }
191}
192
193static const struct option options[] = {
194 { "device", required_argument, 0, 'd'},
195 { },
196};
197
198int main(int argc, char **argv)
199{
200 struct console_ctx *ctx;
201 int rc;
202
203 ctx = malloc(sizeof(struct console_ctx));
204 memset(ctx, 0, sizeof(*ctx));
205
206 for (;;) {
207 int c, idx;
208
209 c = getopt_long(argc, argv, "d", options, &idx);
210 if (c == -1)
211 break;
212
213 switch (c) {
214 case 'd':
215 ctx->tty_dev = optarg;
216 break;
217
218 case 'h':
219 case '?':
220 usage(argv[0]);
221 break;
222 }
223 }
224
225 if (!ctx->tty_dev) {
226 fprintf(stderr,
227 "Error: No TTY device specified (use --device)\n");
228 return EXIT_FAILURE;
229 }
230
231 rc = tty_init_io(ctx);
232 if (rc)
233 return EXIT_FAILURE;
234
235 rc = console_init_io(ctx);
236 if (rc)
237 return EXIT_FAILURE;
238
239 rc = run_console(ctx);
240
241 console_restore_termios(ctx);
242
243 free(ctx);
244
245 return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
246}