Add serial tests
This commit is contained in:
parent
586d46332c
commit
35b76d5f1c
3 changed files with 176 additions and 38 deletions
|
@ -9,7 +9,7 @@ pub const Level = enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn logCallback(context: void, str: []const u8) anyerror!void {
|
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 {
|
pub fn log(comptime level: Level, comptime format: []const u8, args: ...) void {
|
||||||
|
|
|
@ -1,20 +1,9 @@
|
||||||
const arch = @import("arch.zig").internals;
|
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
|
/// The I/O port numbers associated with each serial port
|
||||||
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};
|
|
||||||
|
|
||||||
pub const Port = enum(u16) {
|
pub const Port = enum(u16) {
|
||||||
COM1 = 0x3F8,
|
COM1 = 0x3F8,
|
||||||
COM2 = 0x2F8,
|
COM2 = 0x2F8,
|
||||||
|
@ -22,25 +11,119 @@ pub const Port = enum(u16) {
|
||||||
COM4 = 0x2E8,
|
COM4 = 0x2E8,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute a value that encodes the serial properties
|
/// Errors thrown by serial functions
|
||||||
// This is fed into the LCR (line control register)
|
const SerialError = error{
|
||||||
fn lcrValue(char_len: u8, comptime stop_bit: bool, comptime parity_bit: bool, msb: u8) u8 {
|
/// The given baudrate is outside of the allowed range
|
||||||
var val = char_len & 0x3;
|
InvalidBaudRate,
|
||||||
val |= (if (stop_bit) 0 else 1) << 2;
|
|
||||||
val |= (if (parity_bit) 1 else 0) << 3;
|
/// The given char len is outside the allowed range.
|
||||||
val |= (msb & 0x1) << 7;
|
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;
|
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 {
|
fn baudDivisor(baud: u32) SerialError!u16 {
|
||||||
if (baud > BAUD_MAX or baud <= 0) return SerialError.InvalidBaud;
|
if (baud > BAUD_MAX or baud == 0)
|
||||||
return @intCast(u16, BAUD_MAX / baud);
|
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 {
|
fn transmitIsEmpty(port: Port) bool {
|
||||||
return arch.inb(@enumToInt(port) + 5) & 0x20 > 0;
|
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
|
/// 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 baud: u32 - The baudrate to use. Cannot be more than MAX_BAUDRATE
|
||||||
/// IN port: Port - The port to initialise
|
/// 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 {
|
pub fn init(baud: u32, port: Port) SerialError!void {
|
||||||
// The baudrate is sent as a divisor of the max baud rate
|
// The baudrate is sent as a divisor of the max baud rate
|
||||||
const divisor: u16 = try baudDivisor(baud);
|
const divisor: u16 = try baudDivisor(baud);
|
||||||
const port_int = @enumToInt(port);
|
const port_int = @enumToInt(port);
|
||||||
// Send a byte to start setting the baudrate
|
// 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
|
// Send the divisor's lsb
|
||||||
arch.outb(port_int, @truncate(u8, divisor));
|
arch.outb(port_int, @truncate(u8, divisor));
|
||||||
// Send the divisor's msb
|
// Send the divisor's msb
|
||||||
arch.outb(port_int + 1, @truncate(u8, divisor >> 8));
|
arch.outb(port_int + 1, @truncate(u8, divisor >> 8));
|
||||||
// Send the properties to use
|
// 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
|
// Stop initialisation
|
||||||
arch.outb(port_int + 1, 0);
|
arch.outb(port_int + 1, 0);
|
||||||
|
|
||||||
|
if (options.rt_test)
|
||||||
|
runtimeTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(char: u8, port: Port) void {
|
test "lcrValue computes the correct value" {
|
||||||
while (!transmitIsEmpty(port)) {
|
// Check valid combinations
|
||||||
arch.halt();
|
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 {
|
test "baudDivisor" {
|
||||||
for (str) |char| {
|
// Check invalid baudrates
|
||||||
write(char, port);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -13,9 +13,10 @@ msg_queue = queue.Queue(-1)
|
||||||
proc = None
|
proc = None
|
||||||
|
|
||||||
class TestCase:
|
class TestCase:
|
||||||
def __init__(self, name, expected):
|
def __init__(self, name, expected, prefix=r"\[INFO\] "):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.expected = expected
|
self.expected = expected
|
||||||
|
self.prefix = prefix
|
||||||
|
|
||||||
def failure(msg):
|
def failure(msg):
|
||||||
print("FAILURE: %s" %(msg))
|
print("FAILURE: %s" %(msg))
|
||||||
|
@ -29,7 +30,8 @@ def test_pass(case, exp, expected_idx, found):
|
||||||
|
|
||||||
def get_pre_archinit_cases():
|
def get_pre_archinit_cases():
|
||||||
return [
|
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():
|
def get_post_archinit_cases():
|
||||||
|
@ -76,7 +78,7 @@ if __name__ == "__main__":
|
||||||
expected_idx = 0
|
expected_idx = 0
|
||||||
# Go through the expected log messages
|
# Go through the expected log messages
|
||||||
while expected_idx < len(case.expected):
|
while expected_idx < len(case.expected):
|
||||||
e = r"\[INFO\] " + case.expected[expected_idx]
|
e = case.prefix + case.expected[expected_idx]
|
||||||
try:
|
try:
|
||||||
line = msg_queue.get(block=True, timeout=5)
|
line = msg_queue.get(block=True, timeout=5)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
|
Loading…
Reference in a new issue