dev: Added new LupIO-TTY device

This device is notfied when data is available from the terminal
(e.g. from keyboard input) in order to receive characters. It also
transmits characters to the terminal to be displayed.

The following are the specifications regarding the LupIO-TTY:
https://gitlab.com/luplab/lupio/lupio-specs/-/blob/main/lupio-tty.md

Change-Id: Icc8294984989cfa422d8ed227da39debfa49ab36
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/53031
Maintainer: Bobby Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
diff --git a/src/dev/lupio/LupioTTY.py b/src/dev/lupio/LupioTTY.py
new file mode 100644
index 0000000..972311f
--- /dev/null
+++ b/src/dev/lupio/LupioTTY.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2021 The Regents of the University of California
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from m5.params import Param
+from m5.proxy import Parent
+
+from m5.objects.Device import BasicPioDevice
+
+class LupioTTY(BasicPioDevice):
+    type = 'LupioTTY'
+    cxx_class = 'gem5::LupioTTY'
+    cxx_header = "dev/lupio/lupio_tty.hh"
+    terminal = Param.SerialDevice(Parent.any, "The terminal")
+    pio_size = Param.Addr(0x1000, "PIO size")
+    platform = Param.Platform(Parent.any,
+                              "Platform this device is part of.")
+    int_id = Param.Int("Interrupt ID for the PIC to use")
diff --git a/src/dev/lupio/SConscript b/src/dev/lupio/SConscript
index 775412c..c5d6b8f 100644
--- a/src/dev/lupio/SConscript
+++ b/src/dev/lupio/SConscript
@@ -26,11 +26,14 @@
 
 Import('*')
 
-SimObject('LupioRTC.py', tags='riscv isa')
 SimObject('LupioRNG.py', tags='riscv isa')
+SimObject('LupioRTC.py', tags='riscv isa')
+SimObject('LupioTTY.py', tags='riscv isa')
 
 DebugFlag('LupioRNG')
 DebugFlag('LupioRTC')
+DebugFlag('LupioTTY')
 
-Source('lupio_rtc.cc', tags='riscv isa')
 Source('lupio_rng.cc', tags='riscv isa')
+Source('lupio_rtc.cc', tags='riscv isa')
+Source('lupio_tty.cc', tags='riscv isa')
diff --git a/src/dev/lupio/lupio_tty.cc b/src/dev/lupio/lupio_tty.cc
new file mode 100644
index 0000000..0989947
--- /dev/null
+++ b/src/dev/lupio/lupio_tty.cc
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2021 The Regents of the University of California
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dev/lupio/lupio_tty.hh"
+
+#include "base/trace.hh"
+#include "debug/LupioTTY.hh"
+#include "dev/platform.hh"
+#include "mem/packet_access.hh"
+#include "params/LupioTTY.hh"
+
+#define LUPIO_TTY_INVAL    0x80000000
+
+/* Same fields for CTRL and STAT registers */
+#define LUPIO_TTY_WBIT  (1 << 0)
+#define LUPIO_TTY_RBIT  (1 << 1)
+
+namespace gem5
+{
+
+LupioTTY::LupioTTY(const Params &params)
+    : BasicPioDevice(params, params.pio_size),
+    writChar(-1), readChar(-1), writIntrEn(false), readIntrEn(false),
+    terminal(params.terminal), platform(params.platform)
+{
+    // setup serial device callbacks
+    terminal->regInterfaceCallback([this]() { dataAvailable(); });
+}
+
+void
+LupioTTY::lupioTTYUpdateIRQ()
+{
+    unsigned int irq;
+
+    irq = (writIntrEn && writChar != -1)
+        || (readIntrEn && readChar != -1);
+
+    if (irq) {
+        DPRINTF(LupioTTY, "LupioTTY InterEvent, interrupting\n");
+        platform->postConsoleInt();
+    } else {
+        DPRINTF(LupioTTY, "LupioTTY InterEvent, not interrupting\n");
+        platform->clearConsoleInt();
+    }
+}
+
+void
+LupioTTY::dataAvailable()
+{
+    gem5_assert(terminal->dataAvailable());
+
+    // prevent from overwritting an unread character
+    if (readChar != -1) {
+        fprintf(stderr, "Dropping characters\n");
+    } else {
+        // read data from the terminal
+        readChar = terminal->readData();
+        lupioTTYUpdateIRQ();
+    }
+}
+
+uint64_t
+LupioTTY::lupioTTYRead(uint8_t addr)
+{
+    uint32_t ret = LUPIO_TTY_INVAL;
+
+    switch (addr >> 2) {
+        case LUPIO_TTY_WRIT:
+            DPRINTF(LupioTTY, "Accessing LUPIO_TTY_WRIT\n");
+            if (writChar != -1) {
+                ret = writChar;
+                writChar = -1;
+                lupioTTYUpdateIRQ();
+            }
+            break;
+        case LUPIO_TTY_READ:
+            DPRINTF(LupioTTY, "Accessing LUPIO_TTY_READ\n");
+            if (readChar != -1) {
+                // return new character
+                ret = readChar;
+                readChar = -1;
+                lupioTTYUpdateIRQ();
+            }
+            break;
+        case LUPIO_TTY_CTRL:
+            DPRINTF(LupioTTY, "Accessing LUPIO_TTY_CTRL\n");
+            ret = 0;
+            if (writIntrEn) {
+                ret |= LUPIO_TTY_WBIT;
+            }
+            if (readIntrEn) {
+                ret |= LUPIO_TTY_RBIT;
+            }
+            break;
+        case LUPIO_TTY_STAT:
+            DPRINTF(LupioTTY, "Accessing LUPIO_TTY_STAT\n");
+            // always ready to write
+            ret = LUPIO_TTY_WBIT;
+            // ready to read if unread character available
+            if (readChar != -1) {
+                ret |= LUPIO_TTY_RBIT;
+            }
+            break;
+        default:
+            panic("Unexpected read to the LupioTTY device at address %d!",
+                    addr);
+            break;
+    }
+
+    return ret;
+}
+
+void
+LupioTTY::lupioTTYWrite(uint8_t addr, uint64_t val64)
+{
+    uint32_t val = val64;
+    uint8_t c = val;
+
+    switch (addr >> 2) {
+        case LUPIO_TTY_WRIT:
+            DPRINTF(LupioTTY, "Accessing Write: LUPIO_TTY_WRIT: %d\n", c);
+            // write data to terminal
+            terminal->writeData(c);
+            writChar = c;
+            lupioTTYUpdateIRQ();
+            break;
+        case LUPIO_TTY_CTRL:
+            // set interrupt enable bits
+            DPRINTF(LupioTTY, "Accessing LUPIO_TTY_CTRL\n");
+            writIntrEn = val & LUPIO_TTY_WBIT;
+            readIntrEn = val & LUPIO_TTY_RBIT;
+            lupioTTYUpdateIRQ();
+            break;
+        default:
+            panic("Unexpected write to the LupioTTY device at address %d!",
+                    addr);
+            break;
+    }
+}
+
+Tick
+LupioTTY::read(PacketPtr pkt)
+{
+    Addr tty_addr = pkt->getAddr() - pioAddr;
+
+    DPRINTF(LupioTTY,
+        "Read request - addr: %#x, size: %#x\n", tty_addr, pkt->getSize());
+
+    uint64_t val = lupioTTYRead(tty_addr);
+    pkt->setUintX(val, byteOrder);
+
+    pkt->makeResponse();
+
+    return pioDelay;
+}
+
+Tick
+LupioTTY::write(PacketPtr pkt)
+{
+    Addr tty_addr = pkt->getAddr() - pioAddr;
+    DPRINTF(LupioTTY,
+        "Write request - addr: %#x pktAddr: %#x value: %c\n", tty_addr,
+         pkt->getAddr(), pkt->getUintX(byteOrder));
+
+    lupioTTYWrite(tty_addr, pkt->getUintX(byteOrder));
+
+    pkt->makeResponse();
+
+    return pioDelay;
+}
+
+} // namespace gem5
diff --git a/src/dev/lupio/lupio_tty.hh b/src/dev/lupio/lupio_tty.hh
new file mode 100644
index 0000000..1124193
--- /dev/null
+++ b/src/dev/lupio/lupio_tty.hh
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2021 The Regents of the University of California
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LUPIO_TTY_HH__
+#define __LUPIO_TTY_HH__
+
+#include "dev/io_device.hh"
+#include "dev/platform.hh"
+#include "dev/serial/serial.hh"
+#include "params/LupioTTY.hh"
+
+namespace gem5
+{
+
+class Terminal;
+/**
+ * LupioTTY:
+ * The LupioTTY is a virtual terminal device that can both transmit characters
+ * to a screen, as well as receive characters input from a keyboard.
+ */
+class LupioTTY : public BasicPioDevice
+{
+  private:
+    // Register map
+    enum
+    {
+        LUPIO_TTY_WRIT,
+        LUPIO_TTY_READ,
+        LUPIO_TTY_CTRL,
+        LUPIO_TTY_STAT,
+
+        // Max offset
+        LUPIO_TTY_MAX,
+    };
+
+    // Internal registers
+    int8_t writChar;
+    int8_t readChar;
+    bool writIntrEn;
+    bool readIntrEn;
+
+    uint64_t lupioTTYRead(const uint8_t addr);
+    void lupioTTYWrite(const uint8_t addr, uint64_t c);
+   /**
+    * IRQ management
+    */
+    void lupioTTYUpdateIRQ();
+
+    SerialDevice *terminal;
+    const ByteOrder byteOrder = ByteOrder::little;
+    Platform *platform;
+
+  public:
+    PARAMS(LupioTTY);
+    LupioTTY(const Params &p);
+
+    /**
+     * Inform the LupIO-TTY there is data available
+     */
+    void dataAvailable();
+
+    /**
+     * Implement BasicPioDevice virtual functions
+     */
+    Tick read(PacketPtr pkt) override;
+    Tick write(PacketPtr pkt) override;
+};
+
+} //namespace gem5
+
+#endif // __LUPIO_TTY_HH__