diff --git a/src/kernel/log.zig b/src/kernel/log.zig index 8db6b7c..b0b7640 100644 --- a/src/kernel/log.zig +++ b/src/kernel/log.zig @@ -9,7 +9,7 @@ pub const Level = enum { }; fn logCallback(context: void, str: []const u8) anyerror!void { - serial.writeString(str, serial.Port.COM1); + serial.writeBytes(str, serial.Port.COM1); } pub fn log(comptime level: Level, comptime format: []const u8, args: ...) void { diff --git a/src/kernel/serial.zig b/src/kernel/serial.zig index 042e5e7..dfdfda8 100644 --- a/src/kernel/serial.zig +++ b/src/kernel/serial.zig @@ -1,20 +1,9 @@ const arch = @import("arch.zig").internals; +const panic = @import("panic.zig").panic; +const testing = @import("std").testing; +const options = @import("build_options"); -// The LCR is the line control register -const LCR = 3; -// Maximum baudrate -const BAUD_MAX = 115200; -// 8 bits per serial character -const CHAR_LEN = 8; -// One stop bit per transmission -const SINGLE_STOP_BIT = true; -// No parity bit -const PARITY_BIT = false; - -pub const DEFAULT_BAUDRATE = 38400; - -const SerialError = error{InvalidBaud}; - +/// The I/O port numbers associated with each serial port pub const Port = enum(u16) { COM1 = 0x3F8, COM2 = 0x2F8, @@ -22,25 +11,119 @@ pub const Port = enum(u16) { COM4 = 0x2E8, }; -// Compute a value that encodes the serial properties -// This is fed into the LCR (line control register) -fn lcrValue(char_len: u8, comptime stop_bit: bool, comptime parity_bit: bool, msb: u8) u8 { - var val = char_len & 0x3; - val |= (if (stop_bit) 0 else 1) << 2; - val |= (if (parity_bit) 1 else 0) << 3; - val |= (msb & 0x1) << 7; +/// Errors thrown by serial functions +const SerialError = error{ + /// The given baudrate is outside of the allowed range + InvalidBaudRate, + + /// The given char len is outside the allowed range. + InvalidCharacterLength, +}; + +/// The LCR is the line control register +const LCR: u16 = 3; + +/// Maximum baudrate +const BAUD_MAX: u32 = 115200; + +/// 8 bits per serial character +const CHAR_LEN: u8 = 8; + +/// One stop bit per transmission +const SINGLE_STOP_BIT: bool = true; + +/// No parity bit +const PARITY_BIT: bool = false; + +/// Default baudrate +pub const DEFAULT_BAUDRATE = 38400; + +/// +/// Compute a value that encodes the serial properties +/// Used by the line control register +/// +/// Arguments: +/// IN char_len: u8 - The number of bits in each individual byte. Must be 0 or between 5 and 8 (inclusive). +/// IN stop_bit: bool - If a stop bit should included in each transmission. +/// IN parity_bit: bool - If a parity bit should be included in each transmission. +/// IN msb: u1 - The most significant bit to use. +/// +/// Return: u8 +/// The computed lcr value. +/// +/// Error: SerialError +/// InvalidCharacterLength - If the char_len is less than 5 or greater than 8. +/// +fn lcrValue(char_len: u8, stop_bit: bool, parity_bit: bool, msb: u1) SerialError!u8 { + if (char_len != 0 and (char_len < 5 or char_len > 8)) + return SerialError.InvalidCharacterLength; + // Set the msb and OR in all arguments passed + const val = char_len & 0x3 | + @intCast(u8, @boolToInt(stop_bit)) << 2 | + @intCast(u8, @boolToInt(parity_bit)) << 3 | + @intCast(u8, msb) << 7; return val; } +/// +/// The serial controller accepts a divisor rather than a raw badrate, as that is more space efficient. +/// This function computes the divisor for a desired baudrate. Note that multiple baudrates can have the same divisor. +/// +/// Arguments: +/// baud: u32 - The desired baudrate. Must be greater than 0 and less than BAUD_MAX. +/// +/// Return: u16 +/// The computed divisor. +/// +/// Error: SerialError +/// InvalidBaudRate - If baudrate is 0 or greater than BAUD_MAX. +/// fn baudDivisor(baud: u32) SerialError!u16 { - if (baud > BAUD_MAX or baud <= 0) return SerialError.InvalidBaud; - return @intCast(u16, BAUD_MAX / baud); + if (baud > BAUD_MAX or baud == 0) + return SerialError.InvalidBaudRate; + return @truncate(u16, BAUD_MAX / baud); } +/// +/// Checks if the transmission buffer is empty, which means data can be sent. +/// +/// Arguments: +/// port: Port - The port to check. +/// +/// Return: bool +/// If the transmission buffer is empty. +/// fn transmitIsEmpty(port: Port) bool { return arch.inb(@enumToInt(port) + 5) & 0x20 > 0; } +/// +/// Write a byte to a serial port. Waits until the transmission queue is empty. +/// +/// Arguments: +/// char: u8 - The byte to send. +/// port: Port - The port to send the byte to. +/// +pub fn write(char: u8, port: Port) void { + while (!transmitIsEmpty(port)) { + arch.halt(); + } + arch.outb(@enumToInt(port), char); +} + +/// +/// Write a slice of bytes to a serial port. See write for more detailed information. +/// +/// Arguments: +/// str: []const u8 - The bytes to send. +/// port: Port - The port to send the bytes to. +/// +pub fn writeBytes(str: []const u8, port: Port) void { + for (str) |char| { + write(char, port); + } +} + /// /// Initialise a serial port to a certain baudrate /// @@ -48,33 +131,86 @@ fn transmitIsEmpty(port: Port) bool { /// IN baud: u32 - The baudrate to use. Cannot be more than MAX_BAUDRATE /// IN port: Port - The port to initialise /// -/// Throws a SerialError if the baudrate is invalid +/// Error: SerialError +/// InvalidBaudRate - The baudrate is 0 or greater than BAUD_MAX. /// pub fn init(baud: u32, port: Port) SerialError!void { // The baudrate is sent as a divisor of the max baud rate const divisor: u16 = try baudDivisor(baud); const port_int = @enumToInt(port); // Send a byte to start setting the baudrate - arch.outb(port_int + LCR, lcrValue(0, false, false, 1)); + arch.outb(port_int + LCR, lcrValue(0, false, false, 1) catch |e| { + panic(@errorReturnTrace(), "Failed to initialise serial output setup: {}", e); + }); // Send the divisor's lsb arch.outb(port_int, @truncate(u8, divisor)); // Send the divisor's msb arch.outb(port_int + 1, @truncate(u8, divisor >> 8)); // Send the properties to use - arch.outb(port_int + LCR, lcrValue(CHAR_LEN, SINGLE_STOP_BIT, PARITY_BIT, 0)); + arch.outb(port_int + LCR, lcrValue(CHAR_LEN, SINGLE_STOP_BIT, PARITY_BIT, 0) catch |e| panic(@errorReturnTrace(), "Failed to setup serial properties: {}", e)); // Stop initialisation arch.outb(port_int + 1, 0); + + if (options.rt_test) + runtimeTests(); } -pub fn write(char: u8, port: Port) void { - while (!transmitIsEmpty(port)) { - arch.halt(); +test "lcrValue computes the correct value" { + // Check valid combinations + inline for ([_]u8{ 0, 5, 6, 7, 8 }) |char_len| { + inline for ([_]bool{ true, false }) |stop_bit| { + inline for ([_]bool{ true, false }) |parity_bit| { + inline for ([_]u1{ 0, 1 }) |msb| { + const val = try lcrValue(char_len, stop_bit, parity_bit, msb); + const expected = char_len & 0x3 | + @boolToInt(stop_bit) << 2 | + @boolToInt(parity_bit) << 3 | + @intCast(u8, msb) << 7; + testing.expectEqual(val, expected); + } + } + } } - arch.outb(@enumToInt(port), char); + + // Check invalid char lengths + testing.expectError(SerialError.InvalidCharacterLength, lcrValue(4, false, false, 0)); + testing.expectError(SerialError.InvalidCharacterLength, lcrValue(9, false, false, 0)); } -pub fn writeString(str: []const u8, port: Port) void { - for (str) |char| { - write(char, port); +test "baudDivisor" { + // Check invalid baudrates + inline for ([_]u32{ 0, BAUD_MAX + 1 }) |baud| { + testing.expectError(SerialError.InvalidBaudRate, baudDivisor(baud)); + } + + // Check valid baudrates + var baud: u32 = 1; + while (baud <= BAUD_MAX) : (baud += 1) { + const val = try baudDivisor(baud); + const expected = @truncate(u16, BAUD_MAX / baud); + testing.expectEqual(val, expected); } } + +/// +/// Run all the runtime tests +/// +fn runtimeTests() void { + rt_writeByte(); + rt_writeBytes(); +} + +/// +/// Test writing a byte and a new line separately +/// +fn rt_writeByte() void { + write('c', Port.COM1); + write('\n', Port.COM1); +} + +/// +/// Test writing a series of bytes +/// +fn rt_writeBytes() void { + writeBytes([_]u8{ '1', '2', '3', '\n' }, Port.COM1); +} diff --git a/test/rt-test.py b/test/rt-test.py index dee4b84..83a1e7e 100644 --- a/test/rt-test.py +++ b/test/rt-test.py @@ -13,9 +13,10 @@ msg_queue = queue.Queue(-1) proc = None class TestCase: - def __init__(self, name, expected): + def __init__(self, name, expected, prefix=r"\[INFO\] "): self.name = name self.expected = expected + self.prefix = prefix def failure(msg): print("FAILURE: %s" %(msg)) @@ -29,7 +30,8 @@ def test_pass(case, exp, expected_idx, found): def get_pre_archinit_cases(): return [ - TestCase("Arch init starts", [r"Init arch \w+"]) + TestCase("Serial tests", [r"c", r"123"], ""), + TestCase("Arch init starts", [r"Init arch \w+"]), ] def get_post_archinit_cases(): @@ -76,7 +78,7 @@ if __name__ == "__main__": expected_idx = 0 # Go through the expected log messages while expected_idx < len(case.expected): - e = r"\[INFO\] " + case.expected[expected_idx] + e = case.prefix + case.expected[expected_idx] try: line = msg_queue.get(block=True, timeout=5) except queue.Empty: