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
}