summaryrefslogtreecommitdiff
path: root/x86-bare-metal/mbr-com-serial/com.zig
blob: d6137ea567a128373ad3d68a2a821069f479246f (plain) (blame)
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;
}