aboutsummaryrefslogtreecommitdiffstats
path: root/src/linux/console.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux/console.c')
-rw-r--r--src/linux/console.c210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/linux/console.c b/src/linux/console.c
new file mode 100644
index 00000000..dcffc701
--- /dev/null
+++ b/src/linux/console.c
@@ -0,0 +1,210 @@
+// TTY based IO
+//
+// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include <errno.h> // errno
+#include <fcntl.h> // fcntl
+#include <poll.h> // poll
+#include <pty.h> // openpty
+#include <stdio.h> // fprintf
+#include <string.h> // memmove
+#include <sys/stat.h> // chmod
+#include <sys/timerfd.h> // timerfd_create
+#include <time.h> // struct timespec
+#include <unistd.h> // ttyname
+#include "board/irq.h" // irq_poll
+#include "board/misc.h" // console_sendf
+#include "command.h" // command_find_block
+#include "internal.h" // console_setup
+#include "sched.h" // sched_wake_task
+
+static struct pollfd main_pfd[2];
+#define MP_TIMER_IDX 0
+#define MP_TTY_IDX 1
+
+// Report 'errno' in a message written to stderr
+static void
+report_errno(char *where, int rc)
+{
+ int e = errno;
+ fprintf(stderr, "Got error %d in %s: (%d)%s\n", rc, where, e, strerror(e));
+}
+
+
+/****************************************************************
+ * Setup
+ ****************************************************************/
+
+static int
+set_non_blocking(int fd)
+{
+ int flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ report_errno("fcntl getfl", flags);
+ return -1;
+ }
+ int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if (ret < 0) {
+ report_errno("fcntl setfl", flags);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+set_close_on_exec(int fd)
+{
+ int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
+ if (ret < 0) {
+ report_errno("fcntl set cloexec", ret);
+ return -1;
+ }
+ return 0;
+}
+
+int
+console_setup(char *name)
+{
+ // Open pseudo-tty
+ struct termios ti;
+ memset(&ti, 0, sizeof(ti));
+ int mfd, sfd, ret = openpty(&mfd, &sfd, NULL, &ti, NULL);
+ if (ret) {
+ report_errno("openpty", ret);
+ return -1;
+ }
+ ret = set_non_blocking(mfd);
+ if (ret)
+ return -1;
+ ret = set_close_on_exec(mfd);
+ if (ret)
+ return -1;
+ ret = set_close_on_exec(sfd);
+ if (ret)
+ return -1;
+ main_pfd[MP_TTY_IDX].fd = mfd;
+ main_pfd[MP_TTY_IDX].events = POLLIN;
+
+ // Create symlink to tty
+ unlink(name);
+ char *tname = ttyname(sfd);
+ if (!tname) {
+ report_errno("ttyname", 0);
+ return -1;
+ }
+ ret = symlink(tname, name);
+ if (ret) {
+ report_errno("symlink", ret);
+ return -1;
+ }
+ ret = chmod(tname, 0660);
+ if (ret) {
+ report_errno("chmod", ret);
+ return -1;
+ }
+
+ // Make sure stderr is non-blocking
+ ret = set_non_blocking(STDERR_FILENO);
+ if (ret)
+ return -1;
+
+ // Create sleep wakeup timer fd
+ ret = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC|TFD_NONBLOCK);
+ if (ret < 0) {
+ report_errno("timerfd_create", ret);
+ return -1;
+ }
+ main_pfd[MP_TIMER_IDX].fd = ret;
+ main_pfd[MP_TIMER_IDX].events = POLLIN;
+
+ return 0;
+}
+
+
+/****************************************************************
+ * Console handling
+ ****************************************************************/
+
+static struct task_wake console_wake;
+static char receive_buf[4096];
+static int receive_pos;
+
+// Process any incoming commands
+void
+console_task(void)
+{
+ if (!sched_check_wake(&console_wake))
+ return;
+
+ // Read data
+ int ret = read(main_pfd[MP_TTY_IDX].fd, &receive_buf[receive_pos]
+ , sizeof(receive_buf) - receive_pos);
+ if (ret < 0) {
+ if (errno == EWOULDBLOCK) {
+ ret = 0;
+ } else {
+ report_errno("read", ret);
+ return;
+ }
+ }
+ if (ret == 15 && receive_buf[receive_pos+14] == '\n'
+ && memcmp(&receive_buf[receive_pos], "FORCE_SHUTDOWN\n", 15) == 0)
+ shutdown("Force shutdown command");
+
+ // Find and dispatch message blocks in the input
+ int len = receive_pos + ret;
+ uint8_t pop_count, msglen = len > MESSAGE_MAX ? MESSAGE_MAX : len;
+ ret = command_find_block(receive_buf, msglen, &pop_count);
+ if (ret > 0)
+ command_dispatch(receive_buf, pop_count);
+ if (ret) {
+ len -= pop_count;
+ if (len) {
+ memmove(receive_buf, &receive_buf[pop_count], len);
+ sched_wake_task(&console_wake);
+ }
+ }
+ receive_pos = len;
+}
+DECL_TASK(console_task);
+
+// Encode and transmit a "response" message
+void
+console_sendf(const struct command_encoder *ce, va_list args)
+{
+ // Generate message
+ char buf[MESSAGE_MAX];
+ uint8_t msglen = command_encodef(buf, ce, args);
+ command_add_frame(buf, msglen);
+
+ // Transmit message
+ int ret = write(main_pfd[MP_TTY_IDX].fd, buf, msglen);
+ if (ret < 0)
+ report_errno("write", ret);
+}
+
+// Sleep until the specified time (waking early for console input if needed)
+void
+console_sleep(struct timespec ts)
+{
+ struct itimerspec its;
+ its.it_interval = (struct timespec){0, 0};
+ its.it_value = ts;
+ int ret = timerfd_settime(main_pfd[MP_TIMER_IDX].fd, TFD_TIMER_ABSTIME
+ , &its, NULL);
+ if (ret < 0) {
+ report_errno("timerfd_settime", ret);
+ return;
+ }
+ ret = poll(main_pfd, ARRAY_SIZE(main_pfd), -1);
+ if (ret <= 0) {
+ report_errno("poll main_pfd", ret);
+ return;
+ }
+ if (main_pfd[MP_TTY_IDX].revents)
+ sched_wake_task(&console_wake);
+ if (main_pfd[MP_TIMER_IDX].revents)
+ irq_poll();
+}