xref: /openbmc/obmc-console/tty-handler.c (revision 1e04f449)
1 /**
2  * Copyright © 2016 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <assert.h>
18 #include <err.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <termios.h>
26 
27 #include "console-server.h"
28 #include "config.h"
29 
30 struct tty_handler {
31 	struct handler handler;
32 	struct console *console;
33 	struct ringbuffer_consumer *rbc;
34 	struct poller *poller;
35 	int fd;
36 	int fd_flags;
37 	bool blocked;
38 };
39 
to_tty_handler(struct handler * handler)40 static struct tty_handler *to_tty_handler(struct handler *handler)
41 {
42 	return container_of(handler, struct tty_handler, handler);
43 }
44 
tty_set_fd_blocking(struct tty_handler * th,bool fd_blocking)45 static void tty_set_fd_blocking(struct tty_handler *th, bool fd_blocking)
46 {
47 	int flags;
48 
49 	flags = th->fd_flags & ~O_NONBLOCK;
50 	if (!fd_blocking) {
51 		flags |= O_NONBLOCK;
52 	}
53 
54 	if (flags != th->fd_flags) {
55 		fcntl(th->fd, F_SETFL, flags);
56 		th->fd_flags = flags;
57 	}
58 }
59 
60 /*
61  * A "blocked" handler indicates that the last write returned EAGAIN
62  * (==EWOULDBLOCK), so we know not to continue writing (for non-forced output),
63  * as it'll just return EAGAIN again.
64  *
65  * Once we detect this, we watch for POLLOUT in the poller events. A
66  * POLLOUT indicates that the fd is no longer blocking, so we clear
67  * blocked mode and can continue writing.
68  */
tty_set_blocked(struct tty_handler * th,bool blocked)69 static void tty_set_blocked(struct tty_handler *th, bool blocked)
70 {
71 	int events;
72 
73 	if (blocked == th->blocked) {
74 		return;
75 	}
76 
77 	th->blocked = blocked;
78 	events = POLLIN;
79 
80 	if (th->blocked) {
81 		events |= POLLOUT;
82 	}
83 
84 	console_poller_set_events(th->console, th->poller, events);
85 }
86 
tty_drain_queue(struct tty_handler * th,size_t force_len)87 static int tty_drain_queue(struct tty_handler *th, size_t force_len)
88 {
89 	size_t len;
90 	size_t total_len;
91 	ssize_t wlen;
92 	uint8_t *buf;
93 
94 	/* if we're forcing data, we need to clear non-blocking mode */
95 	if (force_len) {
96 		tty_set_fd_blocking(th, true);
97 
98 		/* no point writing, we'll just see -EAGAIN */
99 	} else if (th->blocked) {
100 		return 0;
101 	}
102 
103 	total_len = 0;
104 
105 	for (;;) {
106 		len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf);
107 		if (!len) {
108 			break;
109 		}
110 
111 		/* write as little as possible while blocking */
112 		if (force_len && force_len < total_len + len) {
113 			len = force_len - total_len;
114 		}
115 
116 		wlen = write(th->fd, buf, len);
117 		if (wlen < 0) {
118 			if (errno == EINTR) {
119 				continue;
120 			}
121 			if ((errno == EAGAIN || errno == EWOULDBLOCK) &&
122 			    !force_len) {
123 				tty_set_blocked(th, true);
124 				break;
125 			}
126 			warn("failed writing to local tty; disabling");
127 			return -1;
128 		}
129 
130 		total_len += wlen;
131 
132 		if (force_len && total_len >= force_len) {
133 			break;
134 		}
135 	}
136 
137 	ringbuffer_dequeue_commit(th->rbc, total_len);
138 
139 	if (force_len) {
140 		tty_set_fd_blocking(th, false);
141 	}
142 
143 	return 0;
144 }
145 
tty_ringbuffer_poll(void * arg,size_t force_len)146 static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len)
147 {
148 	struct tty_handler *th = arg;
149 	int rc;
150 
151 	rc = tty_drain_queue(th, force_len);
152 	if (rc) {
153 		console_poller_unregister(th->console, th->poller);
154 		return RINGBUFFER_POLL_REMOVE;
155 	}
156 
157 	return RINGBUFFER_POLL_OK;
158 }
159 
tty_poll(struct handler * handler,int events,void * data)160 static enum poller_ret tty_poll(struct handler *handler, int events,
161 				void __attribute__((unused)) * data)
162 {
163 	struct tty_handler *th = to_tty_handler(handler);
164 	uint8_t buf[4096];
165 	ssize_t len;
166 	int rc;
167 
168 	if (events & POLLIN) {
169 		len = read(th->fd, buf, sizeof(buf));
170 		if (len <= 0) {
171 			goto err;
172 		}
173 
174 		console_data_out(th->console, buf, len);
175 	}
176 
177 	if (events & POLLOUT) {
178 		tty_set_blocked(th, false);
179 		rc = tty_drain_queue(th, 0);
180 		if (rc) {
181 			goto err;
182 		}
183 	}
184 
185 	return POLLER_OK;
186 
187 err:
188 	th->poller = NULL;
189 	close(th->fd);
190 	ringbuffer_consumer_unregister(th->rbc);
191 	return POLLER_REMOVE;
192 }
193 
set_terminal_baud(struct tty_handler * th,const char * tty_name,speed_t speed)194 static int set_terminal_baud(struct tty_handler *th, const char *tty_name,
195 			     speed_t speed)
196 {
197 	struct termios term_options;
198 
199 	if (tcgetattr(th->fd, &term_options) < 0) {
200 		warn("Can't get config for %s", tty_name);
201 		return -1;
202 	}
203 
204 	if (cfsetspeed(&term_options, speed) < 0) {
205 		warn("Couldn't set speeds for %s", tty_name);
206 		return -1;
207 	}
208 
209 	if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
210 		warn("Couldn't commit terminal options for %s", tty_name);
211 		return -1;
212 	}
213 
214 	return 0;
215 }
216 
make_terminal_raw(struct tty_handler * th,const char * tty_name)217 static int make_terminal_raw(struct tty_handler *th, const char *tty_name)
218 {
219 	struct termios term_options;
220 
221 	if (tcgetattr(th->fd, &term_options) < 0) {
222 		warn("Can't get config for %s", tty_name);
223 		return -1;
224 	}
225 
226 	/* Disable various input and output processing including character
227 	 * translation, line edit (canonical) mode, flow control, and special signal
228 	 * generating characters. */
229 	cfmakeraw(&term_options);
230 
231 	if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
232 		warn("Couldn't commit terminal options for %s", tty_name);
233 		return -1;
234 	}
235 	printf("Set %s for raw byte handling\n", tty_name);
236 
237 	return 0;
238 }
239 
tty_init(const struct handler_type * type,struct console * console,struct config * config)240 static struct handler *tty_init(const struct handler_type *type
241 				__attribute__((unused)),
242 				struct console *console,
243 				struct config *config __attribute__((unused)))
244 {
245 	struct tty_handler *th;
246 	speed_t desired_speed;
247 	const char *tty_name;
248 	const char *tty_baud;
249 	char *tty_path;
250 	int rc;
251 
252 	tty_name = config_get_value(config, "local-tty");
253 	if (!tty_name) {
254 		return NULL;
255 	}
256 
257 	rc = asprintf(&tty_path, "/dev/%s", tty_name);
258 	if (!rc) {
259 		return NULL;
260 	}
261 
262 	th = malloc(sizeof(*th));
263 	if (!th) {
264 		return NULL;
265 	}
266 
267 	th->fd = open(tty_path, O_RDWR | O_NONBLOCK);
268 	if (th->fd < 0) {
269 		warn("Can't open %s; disabling local tty", tty_name);
270 		free(tty_path);
271 		free(th);
272 		return NULL;
273 	}
274 
275 	free(tty_path);
276 	th->fd_flags = fcntl(th->fd, F_GETFL, 0);
277 
278 	tty_baud = config_get_value(config, "local-tty-baud");
279 	if (tty_baud != NULL) {
280 		rc = config_parse_baud(&desired_speed, tty_baud);
281 		if (rc) {
282 			fprintf(stderr, "%s is not a valid baud rate\n",
283 				tty_baud);
284 		} else {
285 			rc = set_terminal_baud(th, tty_name, desired_speed);
286 			if (rc) {
287 				fprintf(stderr,
288 					"Couldn't set baud rate for %s to %s\n",
289 					tty_name, tty_baud);
290 			}
291 		}
292 	}
293 
294 	if (make_terminal_raw(th, tty_name) != 0) {
295 		fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name);
296 	}
297 
298 	th->poller = console_poller_register(console, &th->handler, tty_poll,
299 					     NULL, th->fd, POLLIN, NULL);
300 	th->console = console;
301 	th->rbc = console_ringbuffer_consumer_register(console,
302 						       tty_ringbuffer_poll, th);
303 
304 	return &th->handler;
305 }
306 
tty_fini(struct handler * handler)307 static void tty_fini(struct handler *handler)
308 {
309 	struct tty_handler *th = to_tty_handler(handler);
310 	if (th->poller) {
311 		console_poller_unregister(th->console, th->poller);
312 	}
313 	close(th->fd);
314 	free(th);
315 }
316 
tty_baudrate(struct handler * handler,speed_t baudrate)317 static int tty_baudrate(struct handler *handler, speed_t baudrate)
318 {
319 	const char *tty_name = "local-tty";
320 	struct tty_handler *th = to_tty_handler(handler);
321 
322 	if (baudrate == 0) {
323 		return -1;
324 	}
325 
326 	if (set_terminal_baud(th, tty_name, baudrate) != 0) {
327 		fprintf(stderr, "Couldn't set baud rate for %s to %d\n",
328 			tty_name, baudrate);
329 		return -1;
330 	}
331 	return 0;
332 }
333 
334 static const struct handler_type tty_handler = {
335 	.name = "tty",
336 	.init = tty_init,
337 	.fini = tty_fini,
338 	.baudrate = tty_baudrate,
339 };
340 
341 console_handler_register(&tty_handler);
342