From c0a1e5552fc31c0d2545388f7bdebee110eafab3 Mon Sep 17 00:00:00 2001
From: Takuya ASADA <syuu@cloudius-systems.com>
Date: Thu, 24 Apr 2014 23:41:23 +0900
Subject: [PATCH] Implement console multiplexer

Signed-off-by: Takuya ASADA <syuu@cloudius-systems.com>
Signed-off-by: Pekka Enberg <penberg@cloudius-systems.com>
---
 build.mk                       |   3 +
 drivers/console-driver.cc      |  20 +++
 drivers/console-driver.hh      |  33 +++++
 drivers/console-multiplexer.cc |  93 ++++++++++++
 drivers/console-multiplexer.hh |  42 ++++++
 drivers/console.cc             | 255 +++++----------------------------
 drivers/console.hh             |   8 --
 drivers/isa-serial.cc          |  18 +--
 drivers/isa-serial.hh          |  13 +-
 drivers/line-discipline.cc     | 165 +++++++++++++++++++++
 drivers/line-discipline.hh     |  41 ++++++
 drivers/vga.cc                 |  32 +++--
 drivers/vga.hh                 |  13 +-
 13 files changed, 467 insertions(+), 269 deletions(-)
 create mode 100644 drivers/console-driver.cc
 create mode 100644 drivers/console-driver.hh
 create mode 100644 drivers/console-multiplexer.cc
 create mode 100644 drivers/console-multiplexer.hh
 create mode 100644 drivers/line-discipline.cc
 create mode 100644 drivers/line-discipline.hh

diff --git a/build.mk b/build.mk
index 52444c766..cc409f84e 100644
--- a/build.mk
+++ b/build.mk
@@ -664,6 +664,9 @@ libtsm += drivers/libtsm/tsm_vte_charsets.o
 drivers := $(bsd) $(solaris)
 drivers += core/mmu.o
 drivers += drivers/console.o
+drivers += drivers/console-multiplexer.o
+drivers += drivers/console-driver.o
+drivers += drivers/line-discipline.o
 drivers += drivers/clock.o
 drivers += drivers/clockevent.o
 drivers += drivers/ramdisk.o
diff --git a/drivers/console-driver.cc b/drivers/console-driver.cc
new file mode 100644
index 000000000..f9cd199d4
--- /dev/null
+++ b/drivers/console-driver.cc
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#include "console-driver.hh"
+
+namespace console {
+
+void ConsoleDriver::start(std::function<void()> read_poll)
+{
+        _thread = new sched::thread([&] { read_poll(); },
+            sched::thread::attr().name(thread_name()));
+        dev_start();
+        _thread->start();
+}
+
+};
diff --git a/drivers/console-driver.hh b/drivers/console-driver.hh
new file mode 100644
index 000000000..348242af4
--- /dev/null
+++ b/drivers/console-driver.hh
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#ifndef DRIVERS_CONSOLE_DRIVER_HH
+#define DRIVERS_CONSOLE_DRIVER_HH
+
+#include <functional>
+#include <osv/sched.hh>
+
+namespace console {
+
+class ConsoleDriver {
+public:
+    virtual ~ConsoleDriver() {}
+    virtual void write(const char *str, size_t len) = 0;
+    virtual void flush() = 0;
+    virtual bool input_ready() = 0;
+    virtual char readch() = 0;
+    void start(std::function<void()> read_poll);
+protected:
+    sched::thread *_thread;
+private:
+    virtual void dev_start() = 0;
+    virtual const char *thread_name() = 0;
+};
+
+};
+
+#endif
diff --git a/drivers/console-multiplexer.cc b/drivers/console-multiplexer.cc
new file mode 100644
index 000000000..f0c4a1b5e
--- /dev/null
+++ b/drivers/console-multiplexer.cc
@@ -0,0 +1,93 @@
+/* Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#include <osv/device.h>
+#include <osv/debug.hh>
+#include <signal.h>
+#include "console-multiplexer.hh"
+
+namespace console {
+
+ConsoleMultiplexer::ConsoleMultiplexer(const termios *tio, ConsoleDriver *early_driver)
+    : _tio(tio)
+    , _early_driver(early_driver)
+{
+}
+
+void ConsoleMultiplexer::driver_add(ConsoleDriver *driver)
+{
+    _drivers.push_back(driver);
+}
+
+void ConsoleMultiplexer::start()
+{
+    _ldisc = new LineDiscipline(_tio);
+    for (auto driver : _drivers)
+        driver->start([&] { _ldisc->read_poll(driver); });
+    _started = true;
+}
+
+void ConsoleMultiplexer::read(struct uio *uio, int ioflag) {
+    if (!_started)
+        return;
+    _ldisc->read(uio, ioflag);
+}
+
+void ConsoleMultiplexer::drivers_write(const char *str, size_t len)
+{
+    for (auto driver : _drivers)
+        driver->write(str, len);
+}
+
+void ConsoleMultiplexer::drivers_flush()
+{
+    for (auto driver : _drivers)
+        driver->flush();
+}
+
+void ConsoleMultiplexer::write_ll(const char *str, size_t len)
+{
+    if (!_started) {
+        if (_early_driver != nullptr) {
+            _early_driver->write(str, len);
+            _early_driver->flush();
+        }
+    } else {
+        _ldisc->write(str, len,
+            [&] (const char *str, size_t len) { drivers_write(str, len); });
+        drivers_flush();
+    }
+}
+
+void ConsoleMultiplexer::write(const char *str, size_t len)
+{
+    if (!_started) {
+        WITH_LOCK(_early_lock) {
+            write_ll(str, len);
+        }
+    } else {
+        WITH_LOCK(_mutex) {
+            write_ll(str, len);
+        }
+    }
+}
+
+void ConsoleMultiplexer::write(struct uio *uio, int ioflag)
+{
+    linearize_uio_write(uio, ioflag, [&] (const char *str, size_t len) {
+        write(str, len);
+    });
+}
+
+int ConsoleMultiplexer::read_queue_size()
+{
+    if (!_started)
+        return -1;
+
+    return _ldisc->read_queue_size();
+}
+
+}
diff --git a/drivers/console-multiplexer.hh b/drivers/console-multiplexer.hh
new file mode 100644
index 000000000..f72497cfc
--- /dev/null
+++ b/drivers/console-multiplexer.hh
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#ifndef DRIVERS_CONSOLE_MULTIPLEXER_HH
+#define DRIVERS_CONSOLE_MULTIPLEXER_HH
+#include <osv/spinlock.h>
+#include <termios.h>
+#include "console-driver.hh"
+#include "line-discipline.hh"
+
+namespace console {
+
+class ConsoleMultiplexer {
+public:
+    explicit ConsoleMultiplexer(const termios *tio, ConsoleDriver *early_driver = nullptr);
+    ~ConsoleMultiplexer() {};
+    void driver_add(ConsoleDriver *driver);
+    void start();
+    void read(struct uio *uio, int ioflag);
+    void write_ll(const char *str, size_t len);
+    void write(const char *str, size_t len);
+    void write(struct uio *uio, int ioflag);
+    int read_queue_size();
+private:
+    void drivers_write(const char *str, size_t len);
+    void drivers_flush();
+    const termios *_tio;
+    spinlock _early_lock;
+    bool _started = false;
+    ConsoleDriver *_early_driver;
+    std::list<ConsoleDriver *> _drivers;
+    mutex _mutex;
+    LineDiscipline *_ldisc;
+};
+
+};
+
+#endif
diff --git a/drivers/console.cc b/drivers/console.cc
index 30183b4ee..a0f72a225 100644
--- a/drivers/console.cc
+++ b/drivers/console.cc
@@ -8,17 +8,19 @@
 
 #include <osv/prex.h>
 #include <osv/device.h>
-#include <osv/sched.hh>
-#include <osv/spinlock.h>
+#include <osv/debug.hh>
+#include <osv/prio.hh>
 #include <queue>
 #include <deque>
 #include <vector>
 #include <sys/ioctl.h>
+#include "console.hh"
+#include "console-multiplexer.hh"
 
 #ifdef __x86_64__
-#include "isa-serial.hh"
-#include "vga.hh"
-#endif /* __x86_64__ */
+#include "drivers/vga.hh"
+#include "drivers/isa-serial.hh"
+#endif
 
 #include <termios.h>
 #include <signal.h>
@@ -27,47 +29,6 @@
 
 namespace console {
 
-// should eventually become a list of console device that we chose the best from
-static Console* console = nullptr;
-static spinlock early_write_lock;
-
-static void early_write(const char *str, size_t len)
-{
-    IsaSerialConsole::early_write(str, len);
-}
-
-void write(const char *msg, size_t len)
-{
-    if (len == 0)
-        return;
-
-    if (console != nullptr) {
-        console->write(msg, len);
-    } else {
-        WITH_LOCK(early_write_lock) {
-            early_write(msg, len);
-        }
-    }
-}
-
-// lockless version
-void write_ll(const char *msg, size_t len)
-{
-    if (len == 0)
-        return;
-
-    if (console != nullptr)
-        console->write(msg, len);
-    else
-        early_write(msg, len);
-}
-
-mutex console_mutex;
-// characters available to be returned on read() from the console
-std::queue<char> console_queue;
-// who to wake when characters are added to console_queue
-std::list<sched::thread*> readers;
-
 termios tio = {
     .c_iflag = ICRNL,
     .c_oflag = OPOST | ONLCR,
@@ -80,170 +41,31 @@ termios tio = {
             /*VREPRINT*/0, /*VDISCARD*/0, /*VWERASE*/0,
             /*VLNEXT*/0, /*VEOL2*/0},
 };
+#ifdef __x86_64__
+IsaSerialConsole early_driver
+    __attribute__((init_priority((int)init_prio::console)));
+ConsoleMultiplexer mux __attribute__((init_priority((int)init_prio::console)))
+    (&tio, &early_driver);
+#else
+ConsoleMultiplexer mux __attribute__((init_priority((int)init_prio::console)))
+    (&tio, nullptr);
+#endif
 
-// Console line discipline thread.
-//
-// The "line discipline" is an intermediate layer between a physical device
-// (here a serial port) and a character-device interface (here console_read())
-// implementing features such as input echo, line editing, etc. In OSv, this
-// is implemented in a thread, which is also responsible for read-ahead (input
-// characters are read, echoed and buffered even if no-one is yet reading).
-//
-// The code below implements a fixed line discipline (actually two - canonical
-// and non-canonical). We resisted the temptation to make the line discipline
-// a stand-alone pluggable object: In the early 1980s, 8th Edition Research
-// Unix experimented with pluggable line disciplines, providing improved
-// editing features such as CRT erase (backspace outputs backspace-space-
-// backspace), word erase, etc. These pluggable line-disciplines led to the
-// development of Unix "STREAMS". However, today, these concepts are all but
-// considered obsolete: In the mid 80s it was realized that these editing
-// features can better be implemented in userspace code - Contemporary shells
-// introduced sophisticated command-line editing (tcsh and ksh were both
-// announced in 1983), and the line-editing libraries appeared (GNU Readline,
-// in 1989). Posix's standardization of termios(3) also more-or-less set in
-// stone the features that Posix-compliant line discipline should support.
-//
-// We currently support only a subset of the termios(3) features, which we
-// considered most useful. More of the features can be added as needed.
-
-static inline bool isctrl(char c) {
-    return ((c<' ' && c!='\t' && c!='\n') || c=='\177');
-}
-// inputed characters not yet made available to read() in ICANON mode
-std::deque<char> line_buffer;
-
-void console_poll()
-{
-    while (true) {
-        std::lock_guard<mutex> lock(console_mutex);
-        sched::thread::wait_until(console_mutex, [&] { return console->input_ready(); });
-        char c = console->readch();
-        if (c == 0)
-            continue;
-
-        if (c == '\r' && tio.c_iflag & ICRNL) {
-            c = '\n';
-        }
-        if (tio.c_lflag & ISIG) {
-            // Currently, INTR and QUIT signal OSv's only process, process 0.
-            if (c == tio.c_cc[VINTR]) {
-                kill(0, SIGINT);
-                continue;
-            } else if (c == tio.c_cc[VQUIT]) {
-                kill(0, SIGQUIT);
-                continue;
-            }
-        }
-
-        if (tio.c_lflag & ICANON) {
-            // canonical ("cooked") mode, where input is only made
-            // available to the reader after a newline (until then, the
-            // user can edit it with backspace, etc.).
-            if (c == '\n') {
-                if (tio.c_lflag && ECHO)
-                    console->write(&c, 1);
-                line_buffer.push_back('\n');
-                while (!line_buffer.empty()) {
-                    console_queue.push(line_buffer.front());
-                    line_buffer.pop_front();
-                }
-                for (auto t : readers) {
-                    t->wake();
-                }
-                continue; // already echoed
-            } else if (c == tio.c_cc[VERASE]) {
-                if (line_buffer.empty()) {
-                    continue; // do nothing, and echo nothing
-                }
-                char e = line_buffer.back();
-                line_buffer.pop_back();
-                if (tio.c_lflag && ECHO) {
-                    static const char eraser[] = {'\b',' ','\b','\b',' ','\b'};
-                    if (tio.c_lflag && ECHOE) {
-                        if (isctrl(e)) { // Erase the two characters ^X
-                            console->write(eraser, 6);
-                        } else {
-                            console->write(eraser, 3);
-                        }
-                    } else {
-                        if (isctrl(e)) {
-                            console->write(eraser+2, 2);
-                        } else {
-                            console->write(eraser, 1);
-                        }
-                    }
-                    continue; // already echoed
-                }
-            } else {
-                line_buffer.push_back(c);
-            }
-        } else {
-            // non-canonical ("cbreak") mode, where characters are made
-            // available for reading as soon as they are typed.
-            console_queue.push(c);
-            for (auto t : readers) {
-                t->wake();
-            }
-        }
-        if (tio.c_lflag & ECHO) {
-            if (isctrl(c) && (tio.c_lflag & ECHOCTL)) {
-                char out[2];
-                out[0] = '^';
-                out[1] = c^'@';
-                console->write(out, 2);
-            } else {
-                console->write(&c, 1);
-            }
-        }
-    }
-}
-
-static int
-console_read(struct uio *uio, int ioflag)
+void write(const char *msg, size_t len)
 {
-    WITH_LOCK(console_mutex) {
-        readers.push_back(sched::thread::current());
-        sched::thread::wait_until(console_mutex, [] { return !console_queue.empty(); });
-        readers.remove(sched::thread::current());
-        while (uio->uio_resid && !console_queue.empty()) {
-            struct iovec *iov = uio->uio_iov;
-            auto n = std::min(console_queue.size(), iov->iov_len);
-            for (size_t i = 0; i < n; ++i) {
-                static_cast<char*>(iov->iov_base)[i] = console_queue.front();
-                console_queue.pop();
-            }
+    if (len == 0)
+        return;
 
-            uio->uio_resid -= n;
-            uio->uio_offset += n;
-            if (n == iov->iov_len) {
-                uio->uio_iov++;
-                uio->uio_iovcnt--;
-            }
-        }
-        return 0;
-    }
+    mux.write(msg, len);
 }
 
-static int
-console_write(struct uio *uio, int ioflag)
+// lockless version
+void write_ll(const char *msg, size_t len)
 {
-    while (uio->uio_resid > 0) {
-        struct iovec *iov = uio->uio_iov;
-
-        if (iov->iov_len) {
-            WITH_LOCK(console_mutex) {
-                console::write(reinterpret_cast<const char *>(iov->iov_base),
-                               iov->iov_len);
-            }
-        }
-
-        uio->uio_iov++;
-        uio->uio_iovcnt--;
-        uio->uio_resid -= iov->iov_len;
-        uio->uio_offset += iov->iov_len;
-    }
+    if (len == 0)
+        return;
 
-    return 0;
+    mux.write_ll(msg, len);
 }
 
 static int
@@ -261,9 +83,7 @@ console_ioctl(u_long request, void *arg)
         return 0;
     case FIONREAD:
         // used in OpenJDK's os::available (os_linux.cpp)
-        console_mutex.lock();
-        *static_cast<int*>(arg) = console_queue.size();
-        console_mutex.unlock();
+        *static_cast<int*>(arg) = mux.read_queue_size();
         return 0;
     default:
         return -ENOTTY;
@@ -273,13 +93,15 @@ console_ioctl(u_long request, void *arg)
 template <class T> static int
 op_read(T *ignore, struct uio *uio, int ioflag)
 {
-    return console_read(uio, ioflag);
+    mux.read(uio, ioflag);
+    return 0;
 }
 
 template <class T> static int
 op_write(T *ignore, struct uio *uio, int ioflag)
 {
-    return console_write(uio, ioflag);
+    mux.write(uio, ioflag);
+    return 0;
 }
 
 template <class T> static int
@@ -304,19 +126,14 @@ struct driver console_driver = {
 
 void console_init(bool use_vga)
 {
-#ifdef AARCH64_PORT_STUB
-    abort();
-#else /* !AARCH64_PORT_STUB */
-    auto console_poll_thread = new sched::thread(console_poll,
-            sched::thread::attr().name("console"));
-
-    if (use_vga)
-        console = new VGAConsole(console_poll_thread, &tio);
+#ifdef __x86_64__
+    if (!use_vga)
+        mux.driver_add(&early_driver);
     else
-        console = new IsaSerialConsole(console_poll_thread, &tio);
-    console_poll_thread->start();
+        mux.driver_add(new VGAConsole());
+#endif
     device_create(&console_driver, "console", D_CHR);
-#endif /* !AARCH64_PORT_STUB */
+    mux.start();
 }
 
 class console_file : public special_file {
diff --git a/drivers/console.hh b/drivers/console.hh
index 32e50be45..ff7e67ffa 100644
--- a/drivers/console.hh
+++ b/drivers/console.hh
@@ -12,14 +12,6 @@
 
 namespace console {
 
-class Console {
-public:
-    virtual ~Console() {}
-    virtual void write(const char *str, size_t len) = 0;
-    virtual bool input_ready() = 0;
-    virtual char readch() = 0;
-};
-
 void write(const char *msg, size_t len);
 void write_ll(const char *msg, size_t len);
 void console_init(bool use_vga);
diff --git a/drivers/isa-serial.cc b/drivers/isa-serial.cc
index 7266ee337..41c583738 100644
--- a/drivers/isa-serial.cc
+++ b/drivers/isa-serial.cc
@@ -10,20 +10,10 @@
 
 namespace console {
 
-IsaSerialConsole::IsaSerialConsole(sched::thread* poll_thread, const termios *tio)
-    : _irq(4, [=] { poll_thread->wake(); }), _tio(tio)
-{
-	reset();
-}
-
 void IsaSerialConsole::write(const char *str, size_t len)
 {
-    while (len > 0) {
-        if ((*str == '\n') && (_tio->c_oflag & OPOST) && (_tio->c_oflag & ONLCR))
-            writeByte('\r');
+    while (len-- > 0)
         writeByte(*str++);
-        len--;
-    }
 }
 
 bool IsaSerialConsole::input_ready()
@@ -80,9 +70,9 @@ void IsaSerialConsole::reset() {
     pci::outb(MCR_AUX_OUTPUT_2, ioport + MCR_ADDRESS);
 }
 
-void IsaSerialConsole::early_write(const char *str, size_t len) {
-    while (len-- > 0)
-        writeByte(*str++);
+void IsaSerialConsole::dev_start() {
+    _irq = new gsi_edge_interrupt(4, [&] { _thread->wake(); });
+    reset();
 }
 
 }
diff --git a/drivers/isa-serial.hh b/drivers/isa-serial.hh
index 38f504b29..43211cf73 100644
--- a/drivers/isa-serial.hh
+++ b/drivers/isa-serial.hh
@@ -8,26 +8,23 @@
 #ifndef DRIVERS_ISA_SERIAL_HH
 #define DRIVERS_ISA_SERIAL_HH
 
-#include "console.hh"
+#include "console-driver.hh"
 #include "drivers/pci.hh"
 #include <osv/sched.hh>
 #include <osv/interrupt.hh>
-#include <termios.h>
 
 namespace console {
 
-class IsaSerialConsole : public Console {
+class IsaSerialConsole : public ConsoleDriver {
 public:
-    explicit IsaSerialConsole(sched::thread* consumer, const termios *tio);
     virtual void write(const char *str, size_t len);
+    virtual void flush() {}
     virtual bool input_ready() override;
     virtual char readch();
-    static void early_write(const char *str, size_t len);
 private:
-    gsi_edge_interrupt _irq;
+    gsi_edge_interrupt* _irq;
     static const u16 ioport = 0x3f8;
     u8 lcr;
-    const termios *_tio;
 
     enum IsaSerialValues {
         // UART registers, offsets to ioport:
@@ -61,8 +58,10 @@ private:
         MCR_LOOPBACK_MODE = 0x16,
     };
 
+    virtual void dev_start();
     void reset();
     static void writeByte(const char letter);
+    virtual const char *thread_name() { return "isa-serial-input"; }
 };
 
 }
diff --git a/drivers/line-discipline.cc b/drivers/line-discipline.cc
new file mode 100644
index 000000000..59db1f0f5
--- /dev/null
+++ b/drivers/line-discipline.cc
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+#include "line-discipline.hh"
+#include <osv/sched.hh>
+#include <signal.h>
+
+namespace console {
+
+LineDiscipline::LineDiscipline(const termios *tio)
+    : _tio(tio)
+{
+}
+
+void LineDiscipline::read(struct uio *uio, int ioflag) {
+    WITH_LOCK(_mutex) {
+        _readers.push_back(sched::thread::current());
+        sched::thread::wait_until(_mutex, [&] { return !_read_queue.empty(); });
+        _readers.remove(sched::thread::current());
+        while (uio->uio_resid && !_read_queue.empty()) {
+            struct iovec *iov = uio->uio_iov;
+            auto n = std::min(_read_queue.size(), iov->iov_len);
+            for (size_t i = 0; i < n; ++i) {
+                static_cast<char*>(iov->iov_base)[i] = _read_queue.front();
+                _read_queue.pop();
+            }
+
+            uio->uio_resid -= n;
+            uio->uio_offset += n;
+            if (n == iov->iov_len) {
+                uio->uio_iov++;
+                uio->uio_iovcnt--;
+            }
+        }
+    }
+}
+
+// Console line discipline thread.
+//
+// The "line discipline" is an intermediate layer between a physical device
+// (here a serial port) and a character-device interface (here console_read())
+// implementing features such as input echo, line editing, etc. In OSv, this
+// is implemented in a thread, which is also responsible for read-ahead (input
+// characters are read, echoed and buffered even if no-one is yet reading).
+//
+// The code below implements a fixed line discipline (actually two - canonical
+// and non-canonical). We resisted the temptation to make the line discipline
+// a stand-alone pluggable object: In the early 1980s, 8th Edition Research
+// Unix experimented with pluggable line disciplines, providing improved
+// editing features such as CRT erase (backspace outputs backspace-space-
+// backspace), word erase, etc. These pluggable line-disciplines led to the
+// development of Unix "STREAMS". However, today, these concepts are all but
+// considered obsolete: In the mid 80s it was realized that these editing
+// features can better be implemented in userspace code - Contemporary shells
+// introduced sophisticated command-line editing (tcsh and ksh were both
+// announced in 1983), and the line-editing libraries appeared (GNU Readline,
+// in 1989). Posix's standardization of termios(3) also more-or-less set in
+// stone the features that Posix-compliant line discipline should support.
+//
+// We currently support only a subset of the termios(3) features, which we
+// considered most useful. More of the features can be added as needed.
+
+static inline bool isctrl(char c) {
+    return ((c<' ' && c!='\t' && c!='\n') || c=='\177');
+}
+
+void LineDiscipline::read_poll(ConsoleDriver *driver)
+{
+    while (true) {
+        std::lock_guard<mutex> lock(_mutex);
+        sched::thread::wait_until(_mutex, [&] { return driver->input_ready(); });
+        char c = driver->readch();
+        if (c == 0)
+            continue;
+
+        if (c == '\r' && _tio->c_iflag & ICRNL) {
+            c = '\n';
+        }
+        if (_tio->c_lflag & ISIG) {
+            // Currently, INTR and QUIT signal OSv's only process, process 0.
+            if (c == _tio->c_cc[VINTR]) {
+                kill(0, SIGINT);
+                continue;
+            } else if (c == _tio->c_cc[VQUIT]) {
+                kill(0, SIGQUIT);
+                continue;
+            }
+        }
+
+        if (_tio->c_lflag & ICANON) {
+            // canonical ("cooked") mode, where input is only made
+            // available to the reader after a newline (until then, the
+            // user can edit it with backspace, etc.).
+            if (c == '\n') {
+                if (_tio->c_lflag && ECHO)
+                    driver->write(&c, 1);
+                _line_buffer.push_back('\n');
+                while (!_line_buffer.empty()) {
+                    _read_queue.push(_line_buffer.front()); _line_buffer.pop_front();
+                }
+                for (auto t : _readers) {
+                    t->wake();
+                }
+                continue; // already echoed
+            } else if (c == _tio->c_cc[VERASE]) {
+                if (_line_buffer.empty()) {
+                    continue; // do nothing, and echo nothing
+                }
+                char e = _line_buffer.back();
+                _line_buffer.pop_back();
+                if (_tio->c_lflag && ECHO) {
+                    static const char eraser[] = {'\b',' ','\b','\b',' ','\b'};
+                    if (_tio->c_lflag && ECHOE) {
+                        if (isctrl(e)) { // Erase the two characters ^X
+                            driver->write(eraser, 6);
+                        } else {
+                            driver->write(eraser, 3);
+                        }
+                    } else {
+                        if (isctrl(e)) {
+                            driver->write(eraser+2, 2);
+                        } else {
+                            driver->write(eraser, 1);
+                        }
+                    }
+                    continue; // already echoed
+                }
+            } else {
+                _line_buffer.push_back(c);
+            }
+        } else {
+            // non-canonical ("cbreak") mode, where characters are made
+            // available for reading as soon as they are typed.
+            _read_queue.push(c);
+            for (auto t : _readers) {
+                t->wake();
+            }
+        }
+        if (_tio->c_lflag & ECHO) {
+            if (isctrl(c) && (_tio->c_lflag & ECHOCTL)) {
+                char out[2];
+                out[0] = '^';
+                out[1] = c^'@';
+                driver->write(out, 2);
+            } else {
+                driver->write(&c, 1);
+            }
+        }
+    }
+}
+void LineDiscipline::write(const char *str, size_t len,
+    std::function<void(const char *str, size_t len)> writer)
+{
+    while (len-- > 0) {
+        if ((*str == '\n') &&
+            (_tio->c_oflag & OPOST) && (_tio->c_oflag & ONLCR))
+                writer("\r", 1);
+            writer(str++, 1);
+    }
+}
+
+}
diff --git a/drivers/line-discipline.hh b/drivers/line-discipline.hh
new file mode 100644
index 000000000..84e96f037
--- /dev/null
+++ b/drivers/line-discipline.hh
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#ifndef DRIVERS_LINE_DISCIPLINE_HH
+#define DRIVERS_LINE_DISCIPLINE_HH
+#include <termios.h>
+#include <queue>
+#include <list>
+#include <functional>
+#include <osv/uio.h>
+#include <osv/mutex.h>
+#include "console-driver.hh"
+
+namespace console {
+
+class LineDiscipline {
+public:
+    explicit LineDiscipline(const termios *tio);
+    void read(struct uio *uio, int ioflag);
+    void read_poll(ConsoleDriver *driver);
+    int read_queue_size() { return _read_queue.size(); }
+    void write(const char *str, size_t len,
+        std::function<void(const char *str, size_t len)> writer);
+private:
+    mutex _mutex;
+    const termios *_tio;
+    // characters available to be returned on read() from the console
+    std::queue<char> _read_queue;
+    // who to wake when characters are added to _read_queue
+    std::list<sched::thread*> _readers;
+    // inputed characters not yet made available to read() in ICANON mode
+    std::deque<char> _line_buffer;
+};
+
+};
+
+#endif
diff --git a/drivers/vga.cc b/drivers/vga.cc
index fb2cc6cfb..099da8e13 100644
--- a/drivers/vga.cc
+++ b/drivers/vga.cc
@@ -59,10 +59,8 @@ static int tsm_scroll_cb(struct tsm_screen *screen, int scroll_count, void *data
     return 0;
 }
 
-VGAConsole::VGAConsole(sched::thread* poll_thread, const termios *tio)
-    : _tio(tio)
-    , _kbd(poll_thread)
-    , _offset_dirty(false)
+VGAConsole::VGAConsole()
+    : _offset_dirty(false)
 {
     tsm_screen_new(&_tsm_screen, tsm_log_cb, this);
     tsm_screen_resize(_tsm_screen, NCOLS, NROWS);
@@ -123,12 +121,11 @@ void VGAConsole::apply_offset()
 
 void VGAConsole::write(const char *str, size_t len)
 {
-    while (len > 0) {
-        if ((*str == '\n') && (_tio->c_oflag & OPOST) && (_tio->c_oflag & ONLCR))
-            tsm_vte_input(_tsm_vte, "\r", 1);
-        tsm_vte_input(_tsm_vte, str++, 1);
-        len--;
-    }
+    tsm_vte_input(_tsm_vte, str, len);
+}
+
+void VGAConsole::flush()
+{
     tsm_screen_draw(_tsm_screen, tsm_draw_cb, tsm_cursor_cb, tsm_scroll_cb, this);
     if (_offset_dirty)
         apply_offset();
@@ -136,7 +133,7 @@ void VGAConsole::write(const char *str, size_t len)
 
 bool VGAConsole::input_ready()
 {
-    return !_read_queue.empty() || _kbd.input_ready();
+    return !_read_queue.empty() || _kbd->input_ready();
 }
 
 char VGAConsole::readch()
@@ -152,18 +149,18 @@ char VGAConsole::readch()
             return c;
         }
 
-        key = _kbd.readkey();
+        key = _kbd->readkey();
         if (!key) {
             if (_read_queue.empty())
                 return 0;
             continue;
         }
 
-        if (_kbd.shift & MOD_SHIFT)
+        if (_kbd->shift & MOD_SHIFT)
             mods |= TSM_SHIFT_MASK;
-        if (_kbd.shift & MOD_CTL)
+        if (_kbd->shift & MOD_CTL)
             mods |= TSM_CONTROL_MASK;
-        if (_kbd.shift & MOD_ALT)
+        if (_kbd->shift & MOD_ALT)
             mods |= TSM_ALT_MASK;
 
         switch (key) {
@@ -192,4 +189,9 @@ char VGAConsole::readch()
     }
 }
 
+void VGAConsole::dev_start()
+{
+    _kbd = new Keyboard(_thread);
+}
+
 }
diff --git a/drivers/vga.hh b/drivers/vga.hh
index aba358ae4..f3203731b 100644
--- a/drivers/vga.hh
+++ b/drivers/vga.hh
@@ -8,19 +8,19 @@
 #ifndef DRIVERS_VGA_HH
 #define DRIVERS_VGA_HH
 
-#include "console.hh"
+#include "console-driver.hh"
 #include <osv/sched.hh>
-#include <termios.h>
 #include "kbd.hh"
 #include "libtsm/libtsm.hh"
 #include <queue>
 
 namespace console {
 
-class VGAConsole : public Console {
+class VGAConsole : public ConsoleDriver {
 public:
-    explicit VGAConsole(sched::thread* consumer, const termios *tio);
+    explicit VGAConsole();
     virtual void write(const char *str, size_t len);
+    virtual void flush();
     virtual bool input_ready();
     virtual char readch();
     void draw(const uint32_t c, const struct tsm_screen_attr *attr, unsigned int x, unsigned int y);
@@ -43,14 +43,15 @@ private:
     };
     unsigned _col = 0;
     static volatile unsigned short * const _buffer;
-    const termios *_tio;
     struct tsm_screen *_tsm_screen;
     struct tsm_vte *_tsm_vte;
-    Keyboard _kbd;
+    Keyboard *_kbd;
     std::queue<char> _read_queue;
     unsigned short _history[BUFFER_SIZE];
     unsigned _offset;
     bool _offset_dirty;
+    virtual void dev_start();
+    virtual const char *thread_name() { return "kbd-input"; }
 };
 
 }
-- 
GitLab