blob: de6a34acf2c7e68a5751a0c5d12324aea8a1a0c2 [file] [log] [blame]
/**
* Console server process for OpenBMC
*
* Copyright © 2016 IBM Corporation <jk@ozlabs.org>
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <termios.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/poll.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
static const char esc_str[] = { '\r', '~', '.' };
struct console_ctx {
const char *tty_dev;
int tty_fd;
int console_fd_in;
int console_fd_out;
bool console_is_tty;
struct termios orig_termios;
int esc_str_pos;
};
static void usage(const char *progname)
{
fprintf(stderr,
"usage: %s [options]\n"
"\n"
"Options:\n"
" --device <TTY> Use serial device TTY\n"
"",
progname);
}
/**
* Open and initialise the serial device
*/
static int tty_init_io(struct console_ctx *ctx)
{
ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
if (ctx->tty_fd <= 0) {
warn("Can't open tty %s", ctx->tty_dev);
return -1;
}
/* Disable character delay. We may want to later enable this when
* we detect larger amounts of data
*/
fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
return 0;
}
/*
* Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
* put it in canonical mode
*/
static int console_init_io(struct console_ctx *ctx)
{
struct termios termios;
int rc;
ctx->console_fd_in = STDIN_FILENO;
ctx->console_fd_out = STDOUT_FILENO;
ctx->console_is_tty = isatty(ctx->console_fd_in);
if (!ctx->console_is_tty)
return 0;
rc = tcgetattr(ctx->console_fd_in, &termios);
if (rc) {
warn("Can't get terminal attributes for console");
return -1;
}
memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
cfmakeraw(&termios);
rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
if (rc) {
warn("Can't set terminal attributes for console");
return -1;
}
return 0;
}
static int console_process_input(struct console_ctx *ctx,
uint8_t *buf, size_t len)
{
unsigned long i;
uint8_t e;
e = esc_str[ctx->esc_str_pos];
for (i = 0; i < len; i++) {
if (buf[i] == e) {
ctx->esc_str_pos++;
if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
return 1;
e = esc_str[ctx->esc_str_pos];
} else {
ctx->esc_str_pos = 0;
}
}
return 0;
}
static void console_restore_termios(struct console_ctx *ctx)
{
if (ctx->console_is_tty)
tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
}
static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
{
size_t pos;
ssize_t rc;
for (pos = 0; pos < len; pos += rc) {
rc = write(fd, buf + pos, len - pos);
if (rc <= 0) {
warn("Write error");
return -1;
}
}
return 0;
}
int run_console(struct console_ctx *ctx)
{
struct pollfd pollfds[2];
int rc, len;
pollfds[0].fd = ctx->tty_fd;
pollfds[0].events = POLLIN;
pollfds[1].fd = ctx->console_fd_in;
pollfds[1].events = POLLIN;
for (;;) {
uint8_t buf[4096];
rc = poll(pollfds, 2, -1);
if (rc < 0) {
warn("poll error");
return -1;
}
if (pollfds[0].revents) {
rc = read(ctx->tty_fd, buf, sizeof(buf));
if (rc <= 0) {
warn("Error reading from tty device");
return -1;
}
rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
if (rc < 0)
return -1;
}
if (pollfds[1].revents) {
rc = read(ctx->console_fd_in, buf, sizeof(buf));
if (rc == 0)
return 0;
if (rc <= 0) {
warn("Error reading from console");
return -1;
}
len = rc;
rc = console_process_input(ctx, buf, len);
if (rc) {
rc = 0;
return 0;
}
rc = write_buf_to_fd(ctx->tty_fd, buf, len);
if (rc < 0)
return -1;
}
}
}
static const struct option options[] = {
{ "device", required_argument, 0, 'd'},
{ },
};
int main(int argc, char **argv)
{
struct console_ctx *ctx;
int rc;
ctx = malloc(sizeof(struct console_ctx));
memset(ctx, 0, sizeof(*ctx));
for (;;) {
int c, idx;
c = getopt_long(argc, argv, "d", options, &idx);
if (c == -1)
break;
switch (c) {
case 'd':
ctx->tty_dev = optarg;
break;
case 'h':
case '?':
usage(argv[0]);
break;
}
}
if (!ctx->tty_dev) {
fprintf(stderr,
"Error: No TTY device specified (use --device)\n");
return EXIT_FAILURE;
}
rc = tty_init_io(ctx);
if (rc)
return EXIT_FAILURE;
rc = console_init_io(ctx);
if (rc)
return EXIT_FAILURE;
rc = run_console(ctx);
console_restore_termios(ctx);
free(ctx);
return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}