summaryrefslogtreecommitdiffstats
path: root/src/serial.rs
blob: 03fa570d68dfd5af9cd0674514874b0042304de0 (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
107
108
109
110
111
112
113
114
115
116
117
118
/// UART 16550 serial driver — port-mapped I/O on COM1 (0x3F8)
use core::fmt;

const COM1: u16 = 0x3F8;

const DATA_REG: u16 = COM1;
const INTR_ENABLE: u16 = COM1 + 1;
const FIFO_CONTROL: u16 = COM1 + 2;
const LINE_CONTROL: u16 = COM1 + 3;
const MODEM_CONTROL: u16 = COM1 + 4;
const LINE_STATUS: u16 = COM1 + 5;

const LSR_TX_EMPTY: u8 = 0x20;
const LSR_DATA_READY: u8 = 0x01;

/// Initialise the UART with 115200 baud, 8N1.
pub fn init() {
    unsafe {
        // Disable interrupts
        port_out(INTR_ENABLE, 0x00);
        // Set DLAB=1 (baud rate divisor)
        port_out(LINE_CONTROL, 0x80);
        // Divisor = 1 → 115200 baud (with 1.8432 MHz clock)
        port_out(DATA_REG, 0x01);
        port_out(INTR_ENABLE, 0x00);
        // 8-bit, 1 stop, no parity, DLAB=0
        port_out(LINE_CONTROL, 0x03);
        // Enable FIFO, clear, 14-byte threshold
        port_out(FIFO_CONTROL, 0xC7);
        // RTS/DSR set
        port_out(MODEM_CONTROL, 0x0B);
    }
}

/// Write a single byte to the serial port (blocking poll).
pub fn put_byte(b: u8) {
    unsafe {
        // Wait for transmitter holding register empty
        while port_in(LINE_STATUS) & LSR_TX_EMPTY == 0 {}
        port_out(DATA_REG, b);
    }
}

/// Read a single byte from the serial port (blocking poll).
pub fn get_byte() -> u8 {
    unsafe {
        while port_in(LINE_STATUS) & LSR_DATA_READY == 0 {}
        port_in(DATA_REG)
    }
}

/// Non-blocking read: returns Some(byte) if data ready, None otherwise.
pub fn try_get_byte() -> Option<u8> {
    unsafe {
        if port_in(LINE_STATUS) & LSR_DATA_READY != 0 {
            Some(port_in(DATA_REG))
        } else {
            None
        }
    }
}

/// Write a string to serial.
pub fn write_str(s: &str) {
    for &b in s.as_bytes() {
        if b == b'\n' {
            put_byte(b'\r');
        }
        put_byte(b);
    }
}

// ---------------------------------------------------------------------------
// fmt::Write implementation
// ---------------------------------------------------------------------------
pub struct SerialWriter;

impl fmt::Write for SerialWriter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        write_str(s);
        Ok(())
    }
}

/// Helper macro — prints to serial via format_args!.
#[macro_export]
macro_rules! serial_print {
    ($($arg:tt)*) => {{
        use core::fmt::Write;
        let _ = write!(&mut $crate::serial::SerialWriter, $($arg)*);
    }};
}

#[macro_export]
macro_rules! serial_println {
    () => { $crate::serial::write_str("\r\n"); };
    ($($arg:tt)*) => {{
        $crate::serial_print!($($arg)*);
        $crate::serial::write_str("\r\n");
    }};
}

// ---------------------------------------------------------------------------
// Port I/O helpers
// ---------------------------------------------------------------------------
unsafe fn port_in(port: u16) -> u8 {
    let val: u8;
    unsafe {
        core::arch::asm!("in al, dx", out("al") val, in("dx") port, options(nostack, preserves_flags));
    }
    val
}

unsafe fn port_out(port: u16, val: u8) {
    unsafe {
        core::arch::asm!("out dx, al", in("al") val, in("dx") port, options(nostack, preserves_flags));
    }
}