summaryrefslogtreecommitdiff
path: root/x86-bare-metal/mbr-com-serial/com.zig
diff options
context:
space:
mode:
Diffstat (limited to 'x86-bare-metal/mbr-com-serial/com.zig')
-rw-r--r--x86-bare-metal/mbr-com-serial/com.zig106
1 files changed, 106 insertions, 0 deletions
diff --git a/x86-bare-metal/mbr-com-serial/com.zig b/x86-bare-metal/mbr-com-serial/com.zig
new file mode 100644
index 0000000..d6137ea
--- /dev/null
+++ b/x86-bare-metal/mbr-com-serial/com.zig
@@ -0,0 +1,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;
+}