blob: e68eab1ef5ced4a1e351485a7df4fb2c4b383976 [file] [log] [blame]
/**
* Console server process for OpenBMC
*
* Copyright © 2016 IBM Corporation <jk@ozlabs.org>
*/
#define _GNU_SOURCE
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/poll.h>
#include "console-server.h"
struct console {
const char *tty_kname;
char *tty_sysfs_devnode;
char *tty_dev;
int tty_sirq;
int tty_lpc_addr;
int tty_fd;
struct handler **handlers;
int n_handlers;
};
static void usage(const char *progname)
{
fprintf(stderr,
"usage: %s [options]\n"
"\n"
"Options:\n"
" --device <TTY> Use serial device TTY (eg, ttyS0)\n"
"",
progname);
}
/* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
static int tty_find_device(struct console *console)
{
char *tty_class_device_link;
char *tty_device_tty_dir;
char *tty_device_reldir;
int rc;
rc = -1;
tty_class_device_link = NULL;
tty_device_tty_dir = NULL;
tty_device_reldir = NULL;
rc = asprintf(&tty_class_device_link,
"/sys/class/tty/%s", console->tty_kname);
if (rc < 0)
return -1;
tty_device_tty_dir = realpath(tty_class_device_link, NULL);
if (rc < 0) {
warn("Can't query sysfs for device %s", console->tty_kname);
goto out_free;
}
rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
if (rc < 0)
goto out_free;
console->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
if (!console->tty_sysfs_devnode)
warn("Can't find parent device for %s", console->tty_kname);
/* todo: lookup from major/minor info in sysfs, in case udev has
* renamed us */
rc = asprintf(&console->tty_dev, "/dev/%s", console->tty_kname);
if (rc < 0)
goto out_free;
rc = 0;
out_free:
free(tty_class_device_link);
free(tty_device_tty_dir);
free(tty_device_reldir);
return rc;
}
static int tty_set_sysfs_attr(struct console *console, const char *name,
int value)
{
char *path;
FILE *fp;
int rc;
rc = asprintf(&path, "%s/%s", console->tty_sysfs_devnode, name);
if (rc < 0)
return -1;
fp = fopen(path, "w");
if (!fp) {
warn("Can't access attribute %s on device %s",
name, console->tty_kname);
rc = -1;
goto out_free;
}
setvbuf(fp, NULL, _IONBF, 0);
rc = fprintf(fp, "0x%x", value);
if (rc < 0)
warn("Error writing to %s attribute of device %s",
name, console->tty_kname);
fclose(fp);
out_free:
free(path);
return rc;
}
/**
* Open and initialise the serial device
*/
static int tty_init_io(struct console *console)
{
if (console->tty_sirq)
tty_set_sysfs_attr(console, "sirq", console->tty_sirq);
if (console->tty_lpc_addr)
tty_set_sysfs_attr(console, "lpc_address",
console->tty_lpc_addr);
tty_set_sysfs_attr(console, "enabled", 1);
console->tty_fd = open(console->tty_dev, O_RDWR);
if (console->tty_fd <= 0) {
warn("Can't open tty %s", console->tty_dev);
return -1;
}
/* Disable character delay. We may want to later enable this when
* we detect larger amounts of data
*/
fcntl(console->tty_fd, F_SETFL, FNDELAY);
return 0;
}
int console_data_out(struct console *console, const uint8_t *data, size_t len)
{
return write_buf_to_fd(console->tty_fd, data, len);
}
static void handlers_init(struct console *console)
{
extern struct handler *__start_handlers, *__stop_handlers;
struct handler *handler;
int i;
console->n_handlers = &__stop_handlers - &__start_handlers;
console->handlers = &__start_handlers;
printf("%d handler%s\n", console->n_handlers,
console->n_handlers == 1 ? "" : "s");
for (i = 0; i < console->n_handlers; i++) {
handler = console->handlers[i];
printf(" %s\n", handler->name);
if (handler->init)
handler->init(handler, console);
}
}
static void handlers_fini(struct console *console)
{
struct handler *handler;
int i;
for (i = 0; i < console->n_handlers; i++) {
handler = console->handlers[i];
if (handler->fini)
handler->fini(handler);
}
}
static int handlers_data_in(struct console *console, uint8_t *buf, size_t len)
{
struct handler *handler;
int i, rc, tmp;
rc = 0;
for (i = 0; i < console->n_handlers; i++) {
handler = console->handlers[i];
if (!handler->data_in)
continue;
tmp = handler->data_in(handler, buf, len);
if (tmp == HANDLER_EXIT)
rc = 1;
}
return rc;
}
static int handlers_poll_event(struct console *console,
struct pollfd *pollfds)
{
struct handler *handler;
int i, rc, tmp;
rc = 0;
for (i = 0; i < console->n_handlers; i++) {
handler = console->handlers[i];
if (!handler->poll_event)
continue;
tmp = handler->poll_event(handler, pollfds[i].revents);
if (tmp == HANDLER_EXIT)
rc = 1;
}
return rc;
}
int run_console(struct console *console)
{
struct handler *handler;
struct pollfd *pollfds;
int i, rc;
pollfds = calloc(console->n_handlers + 1, sizeof(*pollfds));
pollfds[0].fd = console->tty_fd;
pollfds[0].events = POLLIN;
for (;;) {
uint8_t buf[4096];
/* init pollers */
for (i = 0; i < console->n_handlers; i++) {
handler = console->handlers[i];
handler->init_poll(handler, &pollfds[i+1]);
}
rc = poll(pollfds, console->n_handlers + 1, -1);
if (rc < 0) {
warn("poll error");
return -1;
}
if (pollfds[0].revents) {
rc = read(console->tty_fd, buf, sizeof(buf));
if (rc <= 0) {
warn("Error reading from tty device");
return -1;
}
rc = handlers_data_in(console, buf, rc);
if (rc)
return 0;
}
rc = handlers_poll_event(console, pollfds + 1);
if (rc)
return 0;
}
}
static const struct option options[] = {
{ "device", required_argument, 0, 'd'},
{ "sirq", required_argument, 0, 's'},
{ "lpc-addr", required_argument, 0, 'l'},
{ },
};
int main(int argc, char **argv)
{
struct console *console;
int rc;
console = malloc(sizeof(struct console));
memset(console, 0, sizeof(*console));
rc = -1;
for (;;) {
char *endp;
int c, idx;
c = getopt_long(argc, argv, "d:s:l:", options, &idx);
if (c == -1)
break;
switch (c) {
case 'd':
console->tty_kname = optarg;
break;
case 'l':
console->tty_lpc_addr = strtoul(optarg, &endp, 0);
if (endp == optarg) {
warnx("Invalid sirq: '%s'", optarg);
goto out_free;
}
break;
case 's':
console->tty_sirq = strtoul(optarg, &endp, 0);
if (endp == optarg) {
warnx("Invalid sirq: '%s'", optarg);
goto out_free;
}
break;
case 'h':
case '?':
usage(argv[0]);
rc = 0;
goto out_free;
}
}
if (!console->tty_kname) {
fprintf(stderr,
"Error: No TTY device specified (use --device)\n");
return EXIT_FAILURE;
}
rc = tty_find_device(console);
if (rc)
return EXIT_FAILURE;
rc = tty_init_io(console);
if (rc)
return EXIT_FAILURE;
handlers_init(console);
rc = run_console(console);
handlers_fini(console);
out_free:
free(console->tty_sysfs_devnode);
free(console->tty_dev);
free(console);
return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}