Merge pull request #100 from SamTebbs33/feature/serial-tests

Add serial tests
This commit is contained in:
Sam Tebbs 2019-10-15 17:32:37 +01:00 committed by GitHub
commit 526d1cee93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 176 additions and 38 deletions

View file

@ -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 {

View file

@ -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);
}

View file

@ -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: