Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/kernel/arch/x86/arch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
Expand Down Expand Up @@ -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
///
Expand Down
178 changes: 178 additions & 0 deletions src/kernel/arch/x86/serial.zig
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 4 additions & 7 deletions src/kernel/kmain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
19 changes: 17 additions & 2 deletions src/kernel/log.zig
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
Loading