1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
// Support for COM serial ports [1].
//
// COM ports are accessed via IO ports. Each port exposes a set of 1 byte
// registers relative to the ports base address.
// 0 Receive buffer (inb).
// 0 Transmit buffer (outb).
// 1 Interrupt enable.
// 2 Interrupt identification.
// 3 Line control.
// 4 Modem control.
// 5 Line status.
// 6 Modem status.
// 7 Scratch register.
//
// If DLAB bit in the line control register is '1', then offset 0/1 are
// remapped, to setup a 2 byte divisor value.
// 0 Low byte of the divisor.
// 1 High byte of the divisor.
// The COM port has an internal clock at 115200 Hz.
//
// The bios data area (DBA) may provide the base address of COM1-COM4 ports [2].
// The first two ports have fairly standardized base addresses
// - COM1 0x3f8
// - COM2 0x2f8
//
// [1] https://wiki.osdev.org/Serial_Ports
// [2] https://wiki.osdev.org/Memory_Map_(x86)
// COM port register offsets relative to COM port base address.
const REG_DATA: u16 = 0;
const REG_INTR_EN: u16 = 1;
const REG_LINE_CTRL: u16 = 3;
const REG_LINE_STAT: u16 = 5;
// Remapped iff LINE_CTRL.DLAB == 1.
const REG_DIVLO: u16 = 0;
const REG_DIVHI: u16 = 1;
// Bit masks for LINE_CTRL register.
const BIT_DLAB: u8 = (1 << 7);
// Bit masks for LINE_STAT register.
const BIT_RX_READY: u8 = (1 << 0);
const BIT_TX_READY: u8 = (1 << 5);
/// Type to interact with x86 serial COM ports [1].
///
/// [1] https://wiki.osdev.org/Serial_Ports
pub const Com = struct {
// Widelysed standardized address for COM1 port (eg also in qemu).
pub const COM1: u16 = 0x3f8;
base: u16,
/// Initialize COM port at `base` address, with following configuration
/// - all interrupts disabled (polling)
/// - divisor = 1 (-> baud rate 115200)
/// - 8 data, 1 stop, no parity bits (8N1)
pub fn init(base: u16) Com {
// Disable all COM port interrupts.
outb(base + REG_INTR_EN, 0);
// Set DLAB bit, to enable access to divisor registers.
outb(base + REG_LINE_CTRL, BIT_DLAB);
// Set divisor=1, configuring a baud rate of 115200.
outb(base + REG_DIVLO, 1);
outb(base + REG_DIVHI, 0);
// Unset DLAB bit, no parity, 1 stop bit, 8 data bits (8n1).
outb(base + REG_LINE_CTRL, 0b11);
return .{ .base = base };
}
/// Write out `msg` to COM port.
pub fn puts(self: Com, msg: []const u8) void {
for (msg) |ch| {
self.putc(ch);
}
}
/// Write out `char` to COM port.
pub fn putc(self: Com, char: u8) void {
while ((inb(self.base + REG_LINE_STAT) & BIT_TX_READY) == 0) {}
outb(self.base, char);
}
};
// -- UTILS ---------------------------------------------------------------------
inline fn outb(port: u16, val: u8) void {
_ = asm volatile ("outb %[al], %[dx]"
: // No outputs.
: [al] "{al}" (val),
[dx] "{dx}" (port),
);
}
inline fn inb(port: u16) u8 {
var val: u8 = 0;
_ = asm volatile ("inb %[dx], %[al]"
: [al] "={al}" (val),
: [dx] "{dx}" (port),
);
return val;
}
|