Make serial arch-dependent

This commit is contained in:
Sam Tebbs 2020-06-04 14:39:37 +01:00
parent e7770d0051
commit ecc3de413c
6 changed files with 264 additions and 202 deletions

View file

@ -8,6 +8,7 @@ const irq = @import("irq.zig");
const isr = @import("isr.zig");
const pit = @import("pit.zig");
const rtc = @import("rtc.zig");
const serial = @import("serial.zig");
const paging = @import("paging.zig");
const syscalls = @import("syscalls.zig");
const mem = @import("../../mem.zig");
@ -16,6 +17,8 @@ const pmm = @import("pmm.zig");
const vmm = @import("../../vmm.zig");
const log = @import("../../log.zig");
const tty = @import("../../tty.zig");
const Serial = @import("../../serial.zig").Serial;
const panic = @import("../../panic.zig").panic;
const MemProfile = mem.MemProfile;
/// The virtual end of the kernel code
@ -251,6 +254,31 @@ pub fn haltNoInterrupts() noreturn {
}
}
///
/// Write a byte to serial port com1. Used by the serial initialiser
///
/// Arguments:
/// IN byte: u8 - The byte to write
///
fn writeSerialCom1(byte: u8) void {
serial.write(byte, serial.Port.COM1);
}
///
/// Initialise serial communication using port COM1 and construct a Serial instance
///
/// Return: serial.Serial
/// The Serial instance constructed with the function used to write bytes
///
pub fn initSerial() Serial {
serial.init(serial.DEFAULT_BAUDRATE, serial.Port.COM1) catch |e| {
panic(@errorReturnTrace(), "Failed to initialise serial: {}", .{e});
};
return Serial{
.write = writeSerialCom1,
};
}
///
/// Initialise the system's memory. Populates a memory profile with boot modules from grub, the amount of available memory, the reserved regions of virtual and physical memory as well as the start and end of the kernel code
///

View file

@ -0,0 +1,178 @@
const arch = @import("arch.zig");
const panic = @import("../../panic.zig").panic;
const testing = @import("std").testing;
/// The I/O port numbers associated with each serial port
pub const Port = enum(u16) {
COM1 = 0x3F8,
COM2 = 0x2F8,
COM3 = 0x3E8,
COM4 = 0x2E8,
};
/// Errors thrown by serial functions
pub 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.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);
}
///
/// Initialise a serial port to a certain baudrate
///
/// Arguments
/// IN baud: u32 - The baudrate to use. Cannot be more than MAX_BAUDRATE
/// IN port: Port - The port to initialise
///
/// 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) 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) catch |e| {
panic(@errorReturnTrace(), "Failed to setup serial properties: {}", .{e});
});
// Stop initialisation
arch.outb(port_int + 1, 0);
}
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);
}
}
}
}
// Check invalid char lengths
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(4, false, false, 0));
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(9, false, false, 0));
}
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);
}
}

View file

@ -7,12 +7,11 @@ const arch = @import("arch.zig").internals;
const tty = @import("tty.zig");
const vga = @import("vga.zig");
const log = @import("log.zig");
const serial = @import("serial.zig");
const pmm = @import("pmm.zig");
const serial = @import("serial.zig");
const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig");
const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig");
const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig");
const options = @import("build_options");
const heap = @import("heap.zig");
comptime {
@ -38,11 +37,9 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn
}
export fn kmain(boot_payload: arch.BootPayload) void {
serial.init(serial.DEFAULT_BAUDRATE, serial.Port.COM1) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise serial: {}", .{e});
};
const serial_stream = serial.init();
if (build_options.rt_test) log.runtimeTests();
log.init(serial_stream);
const mem_profile = arch.initMem(boot_payload) catch |e| panic_root.panic(@errorReturnTrace(), "Failed to initialise memory profile: {}", .{e});
var fixed_allocator = mem_profile.fixed_allocator;
@ -74,5 +71,5 @@ export fn kmain(boot_payload: arch.BootPayload) void {
tty.print("Hello Pluto from kernel :)\n", .{});
// The panic runtime tests must run last as they never return
if (options.rt_test) panic_root.runtimeTests();
if (build_options.rt_test) panic_root.runtimeTests();
}

View file

@ -1,5 +1,6 @@
const serial = @import("serial.zig");
const build_options = @import("build_options");
const std = @import("std");
const Serial = @import("serial.zig").Serial;
const fmt = std.fmt;
/// The errors that can occur when logging
@ -15,8 +16,10 @@ pub const Level = enum {
ERROR,
};
var serial: Serial = undefined;
fn logCallback(context: void, str: []const u8) LoggingError!usize {
serial.writeBytes(str, serial.Port.COM1);
serial.writeBytes(str);
return str.len;
}
@ -76,6 +79,18 @@ pub fn logError(comptime format: []const u8, args: var) void {
log(Level.ERROR, format, args);
}
///
/// Initialise the logging stream using the given Serial instance.
///
/// Arguments:
/// IN ser: Serial - The serial instance to use when logging
///
pub fn init(ser: Serial) void {
serial = ser;
if (build_options.rt_test) runtimeTests();
}
pub fn runtimeTests() void {
inline for (@typeInfo(Level).Enum.fields) |field| {
const level = @field(Level, field.name);

View file

@ -1,217 +1,56 @@
const arch = @import("arch.zig").internals;
const panic = @import("panic.zig").panic;
const testing = @import("std").testing;
const build_options = @import("build_options");
/// The I/O port numbers associated with each serial port
pub const Port = enum(u16) {
COM1 = 0x3F8,
COM2 = 0x2F8,
COM3 = 0x3E8,
COM4 = 0x2E8,
};
pub const Serial = struct {
/// Function that writes a single byte to the serial stream
pub const Write = fn (byte: u8) void;
/// Errors thrown by serial functions
const SerialError = error{
/// The given baudrate is outside of the allowed range
InvalidBaudRate,
write: Write,
/// 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.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
///
/// Arguments
/// IN baud: u32 - The baudrate to use. Cannot be more than MAX_BAUDRATE
/// IN port: Port - The port to initialise
///
/// 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) 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) catch |e| {
panic(@errorReturnTrace(), "Failed to setup serial properties: {}", .{e});
});
// Stop initialisation
arch.outb(port_int + 1, 0);
if (build_options.rt_test) runtimeTests();
}
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);
}
}
///
/// Write a slice of bytes to the serial stream.
///
/// Arguments:
/// str: []const u8 - The bytes to send.
///
pub fn writeBytes(self: *const @This(), bytes: []const u8) void {
for (bytes) |byte| {
self.write(byte);
}
}
};
// Check invalid char lengths
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(4, false, false, 0));
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(9, false, false, 0));
}
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);
}
///
/// Initialise the serial interface. The details of how this is done depends on the architecture.
///
/// Return: Serial
/// The serial interface constructed by the architecture
///
pub fn init() Serial {
const serial = arch.initSerial();
if (build_options.rt_test) runtimeTests(serial);
return serial;
}
///
/// Run all the runtime tests
///
fn runtimeTests() void {
rt_writeByte();
rt_writeBytes();
pub fn runtimeTests(serial: Serial) void {
rt_writeByte(serial);
rt_writeBytes(serial);
}
///
/// Test writing a byte and a new line separately
///
fn rt_writeByte() void {
write('c', Port.COM1);
write('\n', Port.COM1);
fn rt_writeByte(serial: Serial) void {
serial.write('c');
serial.write('\n');
}
///
/// Test writing a series of bytes
///
fn rt_writeBytes() void {
writeBytes(&[_]u8{ '1', '2', '3', '\n' }, Port.COM1);
fn rt_writeBytes(serial: Serial) void {
serial.writeBytes(&[_]u8{ '1', '2', '3', '\n' });
}

View file

@ -6,6 +6,7 @@ const gdt = @import("gdt_mock.zig");
const idt = @import("idt_mock.zig");
const vmm = @import("vmm_mock.zig");
const paging = @import("paging_mock.zig");
const Serial = @import("../../../src/kernel/serial.zig").Serial;
const mock_framework = @import("mock_framework.zig");
pub const initTest = mock_framework.initTest;
@ -101,6 +102,10 @@ pub fn haltNoInterrupts() noreturn {
while (true) {}
}
pub fn initSerial() Serial {
return .{ .write = undefined };
}
pub fn initMem(payload: BootPayload) std.mem.Allocator.Error!mem.MemProfile {
return MemProfile{
.vaddr_end = @ptrCast([*]u8, &KERNEL_VADDR_END),