diff options
author | aqua <aqua@iserlohn-fortress.net> | 2023-03-12 14:53:27 +0200 |
---|---|---|
committer | aqua <aqua@iserlohn-fortress.net> | 2023-03-12 14:53:27 +0200 |
commit | 92e4b6d5522e53e6868b9b0c52b8e54d10bbf606 (patch) | |
tree | a23bd7054b6d0fdd9703e69035cd303d6b448e35 | |
parent | Move all tests next to the code they're testing (diff) | |
download | kernel-92e4b6d5522e53e6868b9b0c52b8e54d10bbf606.tar.xz |
Add unit tests for C drivers
-rw-r--r-- | Makefile.config | 4 | ||||
-rw-r--r-- | devices/Makefile | 5 | ||||
-rw-r--r-- | devices/uart.h | 21 | ||||
-rw-r--r-- | devices/uart/sys_io.hh | 34 | ||||
-rw-r--r-- | devices/uart/uart_16550.c | 82 | ||||
-rw-r--r-- | devices/uart/uart_16550.h | 49 | ||||
-rw-r--r-- | devices/uart/unittest_uart_16550.cc | 116 | ||||
-rw-r--r-- | i686/toolchain.mk | 8 | ||||
-rw-r--r-- | rules.mk | 10 |
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 @@ -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} |