aboutsummaryrefslogtreecommitdiff
path: root/devices/uart
diff options
context:
space:
mode:
Diffstat (limited to 'devices/uart')
-rw-r--r--devices/uart/sys_io.hh34
-rw-r--r--devices/uart/uart_16550.c78
-rw-r--r--devices/uart/uart_16550.h49
-rw-r--r--devices/uart/unittest_uart_16550.cc116
4 files changed, 277 insertions, 0 deletions
diff --git a/devices/uart/sys_io.hh b/devices/uart/sys_io.hh
new file mode 100644
index 0000000..142c9c1
--- /dev/null
+++ b/devices/uart/sys_io.hh
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <gmock/gmock.h>
+
+class IPort {
+public:
+ virtual unsigned char inb(unsigned short) const = 0;
+ virtual void outb(unsigned char, unsigned short) const = 0;
+};
+
+class MockPort : public IPort {
+public:
+ MOCK_METHOD(unsigned char, inb, (unsigned short), (const, override));
+ MOCK_METHOD(void, outb, (unsigned char, unsigned short), (const, override));
+};
+
+static std::unique_ptr<MockPort> mockPort;
+
+// mock free functions
+extern "C" {
+unsigned char
+inb(unsigned short v)
+{
+ EXPECT_TRUE(mockPort) << "MockPort was not set up by the test fixture";
+ return mockPort->inb(v);
+}
+
+void
+outb(unsigned char p, unsigned short v)
+{
+ EXPECT_TRUE(mockPort) << "MockPort was not set up by the test fixture";
+ return mockPort->outb(p, v);
+}
+}
diff --git a/devices/uart/uart_16550.c b/devices/uart/uart_16550.c
new file mode 100644
index 0000000..4697cf3
--- /dev/null
+++ b/devices/uart/uart_16550.c
@@ -0,0 +1,78 @@
+#include "uart_16550.h"
+#include <stddef.h>
+
+int
+uart_thre(unsigned short port)
+{
+ return inb(port + LineStatus) & THRE;
+}
+
+void
+uart_putc(const FILE *self, char a)
+{
+ const unsigned short port = (unsigned short)self->id;
+
+ while (uart_thre(port) == 0) {}
+ outb((unsigned char)a, port);
+
+ if (a == '\n') {
+ while (uart_thre(port) == 0) {}
+ outb('\r', port);
+ }
+}
+
+int
+uart_puts(const FILE *self, const char *string, int length)
+{
+ int i;
+ int written = 0;
+
+ if (length == -1)
+ while (*string != '\0') {
+ uart_putc(self, *string);
+ ++string;
+ ++written;
+ }
+
+ else
+ for (i = 0; i < length; ++i) {
+ uart_putc(self, string[i]);
+ ++written;
+ }
+
+ return written;
+}
+
+void
+uart_flush(__attribute__((unused)) const FILE *self)
+{
+}
+
+FILE uart_stream;
+
+FILE *
+uart_init(unsigned short port)
+{
+ outb(0x00, port + 1); /* Disable all interrupts */
+ outb(0x80, port + 3); /* Enable DLAB (set baud rate divisor) */
+ outb(0x03, port + 0); /* Set divisor to 3 (lo byte) 38400 baud */
+ outb(0x00, port + 1); /* (hi byte) */
+ outb(0x03, port + 3); /* 8 bits, no parity, one stop bit */
+ outb(0xc7, port + 2); /* Enable FIFO, clear them, with 14-byte threshold */
+ outb(0x0b, port + 4); /* IRQs enabled, RTS/DSR set */
+ outb(0x1e, port + 4); /* Set in loopback mode, test the serial chip */
+ outb(0xae, port + 0); /* Test serial chip (send byte 0xAE and check if serial */
+ /* returns same byte) */
+
+ /* Check if serial is faulty (i.e: not same byte as sent) */
+ if (inb(port + 0) != 0xae) { return NULL; }
+
+ /* If serial is not faulty set it in normal operation mode */
+ /* (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled) */
+ outb(0x0f, port + 4);
+ uart_stream.id = port;
+ uart_stream.putc = &uart_putc;
+ uart_stream.puts = &uart_puts;
+ uart_stream.flush = &uart_flush;
+ return &uart_stream;
+}
diff --git a/devices/uart/uart_16550.h b/devices/uart/uart_16550.h
new file mode 100644
index 0000000..bbeb9b7
--- /dev/null
+++ b/devices/uart/uart_16550.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "uart.h"
+
+int uart_thre(unsigned short port);
+void uart_putc(const FILE *self, char a);
+int uart_puts(const FILE *self, const char *string, int length);
+void uart_flush(__attribute__((unused)) const FILE *self);
+
+enum uart_16550_offset {
+ Data = 0, /* read from receive buffer / write to transmit buffer | BaudDiv_l */
+ InterruptControl = 1, /* interrupt enable | BaudDiv_h */
+ FifoControl = 2, /* interrupt ID and FIFO control */
+ LineControl = 3, /* most significant bit is the DLAB */
+ ModemControl = 4,
+ LineStatus = 5,
+ ModemStatus = 6,
+ Scratch = 7
+};
+
+/* Line Control
+ * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * |dla| | parity | s | data | */
+enum LineControl {
+ d5bit = 0x00, /* 0000 0000 data bits */
+ d6bit = 0x01, /* 0000 0001 */
+ d7bit = 0x02, /* 0000 0010 */
+ d8bit = 0x03, /* 0000 0011 */
+ /* none = 0b00000000, // parity bits */
+ odd = 0x08, /* 0000 1000 */
+ even = 0x18, /* 0001 1000 */
+ mark = 0x28, /* 0010 1000 */
+ space = 0x38, /* 0011 1000 */
+ /* s1bit = 0b00000000, // stop bits */
+ s2bit = 0x04, /* 0000 0100 1.5 for 5bit data; 2 otherwise */
+ dlab = 0x80 /* 1000 0000 divisor latch access bit */
+};
+
+/* Line Status Register */
+enum LineStatus {
+ DR = (1 << 0), /* data ready: see if there is data to read */
+ OE = (1 << 1), /* overrun error: see if there has been data lost */
+ PE = (1 << 2), /* parity error: see if there was error in transmission */
+ FE = (1 << 3), /* framing error: see if a stop bit was missing */
+ BI = (1 << 4), /* break indicator: see if there is a break in data input */
+ THRE = (1 << 5), /* transmitter holding register empty: see if transmission buffer is empty */
+ TEMT = (1 << 6), /* transmitter empty: see if transmitter is not doing anything */
+ ERRO = (1 << 7) /* impending error: see if there is an error with a word in the input buffer */
+};
diff --git a/devices/uart/unittest_uart_16550.cc b/devices/uart/unittest_uart_16550.cc
new file mode 100644
index 0000000..f8124bb
--- /dev/null
+++ b/devices/uart/unittest_uart_16550.cc
@@ -0,0 +1,116 @@
+#include "sys_io.hh"
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::MockFunction;
+using ::testing::Return;
+
+namespace k {
+extern "C" {
+#include "uart.h"
+#include "uart_16550.h"
+}
+} // namespace k
+
+class Uart16550 : public ::testing::Test {
+protected:
+ void
+ SetUp()
+ {
+ ASSERT_FALSE(mockPort);
+ mockPort.reset(new MockPort);
+ }
+ void
+ TearDown()
+ {
+ ASSERT_TRUE(mockPort);
+ mockPort.reset();
+ }
+};
+
+TEST_F(Uart16550, uart_thre)
+{
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(1).WillOnce(Return(k::THRE));
+
+ const auto result = uart_thre(k::COM1);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(Uart16550, uart_putc)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(1).WillRepeatedly(Return(k::THRE));
+ EXPECT_CALL(*mockPort, outb('a', k::COM1)).Times(1);
+
+ uart_putc(&f, 'a');
+}
+
+TEST_F(Uart16550, uart_putc_newline)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(2).WillRepeatedly(Return(k::THRE));
+ EXPECT_CALL(*mockPort, outb('\n', k::COM1)).Times(1);
+ EXPECT_CALL(*mockPort, outb('\r', k::COM1)).Times(1);
+
+ uart_putc(&f, '\n');
+}
+
+TEST_F(Uart16550, uart_puts)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+ const char *string{"This is a test string to write over uart"};
+ const int length = (int)strlen(string);
+
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(length).WillRepeatedly(Return(k::THRE));
+ EXPECT_CALL(*mockPort, outb(_, k::COM1)).Times(length);
+
+ const auto result = uart_puts(&f, string, length);
+ ASSERT_EQ(result, length);
+}
+
+TEST_F(Uart16550, uart_puts_WithUnknownLength)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+ const char *string{"This is a test string to write over uart"};
+ const int length = (int)strlen(string);
+
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(length).WillRepeatedly(Return(k::THRE));
+ EXPECT_CALL(*mockPort, outb(_, k::COM1)).Times(length);
+
+ const auto result = uart_puts(&f, string, -1);
+ ASSERT_EQ(result, length);
+}
+
+TEST_F(Uart16550, uart_puts_WithPartialLength)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+ const char *string{"This is a test string to write over uart"};
+ const int length = (int)strlen(string);
+ const int partial = 10;
+
+ ASSERT_LT(partial, length);
+
+ // set up expectations
+ EXPECT_CALL(*mockPort, inb(k::COM1 + k::LineStatus)).Times(partial).WillRepeatedly(Return(k::THRE));
+ EXPECT_CALL(*mockPort, outb(_, k::COM1)).Times(partial);
+
+ const auto result = uart_puts(&f, string, partial);
+ ASSERT_EQ(result, partial);
+}
+
+TEST_F(Uart16550, uart_flush)
+{
+ k::FILE f{k::COM1, nullptr, nullptr, nullptr};
+
+ // set up expectations
+ // no mock calls are expected
+
+ uart_flush(&f);
+}