pluto/src/kernel/serial.zig

217 lines
6.4 KiB
Zig
Raw Normal View History

2019-06-16 23:48:32 +01:00
const arch = @import("arch.zig").internals;
2019-10-08 00:11:50 +01:00
const panic = @import("panic.zig").panic;
const testing = @import("std").testing;
const options = @import("build_options");
2019-06-16 23:48:32 +01:00
2019-10-08 00:11:50 +01:00
/// The I/O port numbers associated with each serial port
2019-06-16 23:48:32 +01:00
pub const Port = enum(u16) {
COM1 = 0x3F8,
COM2 = 0x2F8,
COM3 = 0x3E8,
2019-10-06 19:03:02 +01:00
COM4 = 0x2E8,
2019-06-16 23:48:32 +01:00
};
2019-10-08 00:11:50 +01:00
/// 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;
2019-06-16 23:48:32 +01:00
return val;
}
2019-10-08 00:11:50 +01:00
///
/// 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.
///
2019-06-16 23:48:32 +01:00
fn baudDivisor(baud: u32) SerialError!u16 {
2019-10-08 00:11:50 +01:00
if (baud > BAUD_MAX or baud == 0)
return SerialError.InvalidBaudRate;
return @truncate(u16, BAUD_MAX / baud);
2019-06-16 23:48:32 +01:00
}
2019-10-08 00:11:50 +01:00
///
/// 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.
///
2019-06-16 23:48:32 +01:00
fn transmitIsEmpty(port: Port) bool {
return arch.inb(@enumToInt(port) + 5) & 0x20 > 0;
}
2019-10-08 00:11:50 +01:00
///
/// 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);
}
}
2019-06-16 23:48:32 +01:00
///
/// 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
///
2019-10-08 00:11:50 +01:00
/// Error: SerialError
/// InvalidBaudRate - The baudrate is 0 or greater than BAUD_MAX.
2019-06-16 23:48:32 +01:00
///
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
2019-10-08 00:11:50 +01:00
arch.outb(port_int + LCR, lcrValue(0, false, false, 1) catch |e| {
2020-01-01 19:12:36 +00:00
panic(@errorReturnTrace(), "Failed to initialise serial output setup: {}", .{e});
2019-10-08 00:11:50 +01:00
});
2019-06-16 23:48:32 +01:00
// 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
2020-01-01 19:12:36 +00:00
arch.outb(port_int + LCR, lcrValue(CHAR_LEN, SINGLE_STOP_BIT, PARITY_BIT, 0) catch |e| panic(@errorReturnTrace(), "Failed to setup serial properties: {}", .{e}));
2019-06-16 23:48:32 +01:00
// Stop initialisation
arch.outb(port_int + 1, 0);
2019-10-08 00:11:50 +01:00
if (options.rt_test)
runtimeTests();
2019-06-16 23:48:32 +01:00
}
2019-10-08 00:11:50 +01:00
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);
}
}
}
2019-10-06 19:03:02 +01:00
}
2019-10-08 00:11:50 +01:00
// Check invalid char lengths
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(4, false, false, 0));
testing.expectError(SerialError.InvalidCharacterLength, lcrValue(9, false, false, 0));
2019-06-16 23:48:32 +01:00
}
2019-10-08 00:11:50 +01:00
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);
2019-06-16 23:48:32 +01:00
}
}
2019-10-08 00:11:50 +01:00
///
/// 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 {
2020-01-01 19:12:36 +00:00
writeBytes(&[_]u8{ '1', '2', '3', '\n' }, Port.COM1);
2019-10-08 00:11:50 +01:00
}