aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraqua <aqua@iserlohn-fortress.net>2023-03-12 14:53:27 +0200
committeraqua <aqua@iserlohn-fortress.net>2023-03-12 14:53:27 +0200
commit92e4b6d5522e53e6868b9b0c52b8e54d10bbf606 (patch)
treea23bd7054b6d0fdd9703e69035cd303d6b448e35
parentMove all tests next to the code they're testing (diff)
downloadkernel-92e4b6d5522e53e6868b9b0c52b8e54d10bbf606.tar.xz
Add unit tests for C drivers
-rw-r--r--Makefile.config4
-rw-r--r--devices/Makefile5
-rw-r--r--devices/uart.h21
-rw-r--r--devices/uart/sys_io.hh34
-rw-r--r--devices/uart/uart_16550.c82
-rw-r--r--devices/uart/uart_16550.h49
-rw-r--r--devices/uart/unittest_uart_16550.cc116
-rw-r--r--i686/toolchain.mk8
-rw-r--r--rules.mk10
9 files changed, 322 insertions, 7 deletions
diff --git a/Makefile.config b/Makefile.config
index c771677..836c094 100644
--- a/Makefile.config
+++ b/Makefile.config
@@ -64,9 +64,11 @@ HOST_CFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion \
${CFLAGS}
HOST_CXX := g++
HOST_CXXFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion -g -Og \
- $(shell pkg-config --cflags --libs gtest gtest_main gmock) \
+ $(shell pkg-config --cflags gtest gtest_main gmock) \
${CXXFLAGS}
+HOST_LDFLAGS := $(shell pkg-config --libs gtest gtest_main gmock)
+
# emulator name and flags
QEMU := qemu-system-i386 -accel kvm -machine pc
diff --git a/devices/Makefile b/devices/Makefile
index 3c61f6d..e081f68 100644
--- a/devices/Makefile
+++ b/devices/Makefile
@@ -7,5 +7,10 @@ ${ARCH}_CXXFLAGS += ${INCLUDES}
TARGETLIB += devs
devs.SRCS = pic_8259.c uart_16550.cpp vga.cpp i8042.c pckbd.c mouse.c
+HOSTTARGETBIN += uart/test_uart_16550
+uart/test_uart_16550.SRCS = uart/uart_16550.c uart/unittest_uart_16550.cc
+
+#TESTS += uart/test_uart_16550
+
include ../rules.mk
diff --git a/devices/uart.h b/devices/uart.h
new file mode 100644
index 0000000..8def000
--- /dev/null
+++ b/devices/uart.h
@@ -0,0 +1,21 @@
+#pragma once
+
+// #include <stdio.h>
+// #include <sys/io.h>
+
+enum UART {
+ COM1 = 0x3f8,
+ COM2 = 0x2f8,
+ COM3 = 0x3e8,
+ COM4 = 0x2e8,
+ COM5 = 0x5f8,
+ COM6 = 0x4f8,
+ COM7 = 0x5e8,
+ COM8 = 0x4e8,
+};
+
+typedef struct {
+ unsigned id;
+} FILE;
+
+FILE *uart_init(enum UART port);
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..61c349f
--- /dev/null
+++ b/devices/uart/uart_16550.c
@@ -0,0 +1,82 @@
+#include "uart_16550.h"
+#include <stddef.h>
+
+#ifdef __ARCH__
+#include <sys/io.h>
+#else
+unsigned char inb(unsigned short);
+void outb(unsigned char, unsigned short);
+#endif
+
+int
+uart_thre(enum UART port)
+{
+ return inb(port + LineStatus) & THRE;
+}
+
+void
+uart_putc(const FILE *self, char a)
+{
+ while (uart_thre((enum UART)self->id) == 0) {}
+ outb(a, self->id);
+
+ if (a == '\n') {
+ while (uart_thre((enum UART)self->id) == 0) {}
+ outb('\r', self->id);
+ }
+}
+
+int
+uart_puts(const FILE *self, const char *string, int length)
+{
+ int written = 0;
+
+ if (length == -1)
+ while (*string != '\0') {
+ uart_putc(self, *string);
+ ++string;
+ ++written;
+ }
+
+ else
+ for (int 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(enum UART 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..f8e1931
--- /dev/null
+++ b/devices/uart/uart_16550.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "../uart.h"
+
+int uart_thre(enum UART 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..560c6c0
--- /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};
+
+ // 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};
+
+ // 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};
+ 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};
+ 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};
+ 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};
+
+ // set up expectations
+ // no mock calls are expected
+
+ uart_flush(&f);
+}
diff --git a/i686/toolchain.mk b/i686/toolchain.mk
index 791966b..1205c28 100644
--- a/i686/toolchain.mk
+++ b/i686/toolchain.mk
@@ -6,8 +6,8 @@ ${ARCH}_AS := i686-elf-as
${ARCH}_CC := i686-elf-gcc
${ARCH}_CCID := $(shell ${${ARCH}_CC} --version | head -n1)
-${ARCH}_CFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion -fanalyzer -ffreestanding -std=gnu11 \
- -mgeneral-regs-only \
+${ARCH}_CFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion -fanalyzer \
+ -D__ARCH__="${ARCH}" -ffreestanding -std=gnu11 -mgeneral-regs-only \
$(shell echo ${CONFIG_CFLAGS})
${ARCH}_CXX := i686-elf-g++
@@ -32,9 +32,11 @@ HOST_CFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion \
${CFLAGS}
HOST_CXX := g++
HOST_CXXFLAGS := -Wall -Wextra -Wpedantic -Werror=shadow -Wconversion -g -Og \
- $(shell pkg-config --cflags --libs gtest gtest_main gmock) \
+ $(shell pkg-config --cflags gtest gtest_main gmock) \
${CXXFLAGS}
+HOST_LDFLAGS := $(shell pkg-config --libs gtest gtest_main gmock)
+
# emulator name and flags
QEMU := qemu-system-i386 -accel kvm -machine pc
diff --git a/rules.mk b/rules.mk
index eedf27f..5d73051 100644
--- a/rules.mk
+++ b/rules.mk
@@ -37,7 +37,7 @@ $(foreach T,${HOSTTARGETBIN},\
$(eval $T.OBJS += $(call objects,$T.SRCS,) ) \
$(eval $T.DEPS += $(call depends,$T.SRCS,) ) \
$(eval include $($T.DEPS) ) \
- $(eval $T: ${$T.OBJS}; @echo ' LD HOST $T'; ${HOST_CC} ${HOST_LDFLAGS} -o $T ${$T.OBJS} ) \
+ $(eval $T: ${$T.OBJS}; @echo ' HOST LD $T'; ${HOST_CXX} ${HOST_LDFLAGS} -o $T ${$T.OBJS} ) \
$(eval DEFAULT_TARGETS += $T) \
)
@@ -113,13 +113,17 @@ ${ARCH}_CXXFLAGS += -I../lib/libk -Drestrict=__restrict__
# Host suffix rules
%.o: %.c
- @echo ' CC HOST $<'
+ @echo ' HOST CC $<'
@${HOST_CC} ${HOST_CFLAGS} -c -o $@ $<
+%.o: %.cc
+ @echo ' HOST CXX $<'
+ @${HOST_CXX} ${HOST_CXXFLAGS} -c -o $@ $<
+
# test rules
test_%: test_%.cc
@echo ' CXX TEST $@'
- @${HOST_CXX} ${HOST_CXXFLAGS} $< -o $@
+ @${HOST_CXX} ${HOST_CXXFLAGS} ${HOST_LDFLAGS} $< -o $@
.PHONY: test.base valgrind.base clean.base FORCE
test.base: ${TESTS}