Files
Elyz/kernel/docs/debug/serial.md
2026-07-07 16:40:41 +03:00

3.5 KiB
Raw Blame History

Serial Port драйвер: serial.rs

Назначение

Драйвер последовательного порта (UART 16550, COM1) для отладочного вывода. Позволяет видеть сообщения ядра через QEMU serial console, minicom, screen и т.д.

Аппаратная модель

COM1 расположен по портам ввода-вывода 0x3F8-0x3FF:

Порт Регистр Назначение
0x3F8 DATA Чтение/запись данных
0x3F9 IER Interrupt Enable
0x3FA IIR/FCR Interrupt ID / FIFO Control
0x3FB LCR Line Control
0x3FC MCR Modem Control
0x3FD LSR Line Status
0x3FE MSR Modem Status

Инициализация

pub unsafe fn init() -> Self {
    let port = Self::COM1; // 0x3F8
    outb(port + 1, 0x00);  // IER = 0 (disable interrupts)
    outb(port + 3, 0x80);  // LCR DLAB=1 (enable baud rate programming)
    outb(port + 0, 0x03);  // Divisor LSB = 3 (38400 baud)
    outb(port + 1, 0x00);  // Divisor MSB = 0
    outb(port + 3, 0x03);  // LCR = 8N1 (8 bits, No parity, 1 stop)
    outb(port + 2, 0xC7);  // FCR = enable FIFO, clear, 14-byte threshold
    outb(port + 4, 0x0B);  // MCR = DTR+RTS+OUT2 (enable IRQ + handshake)
    SerialPort(port)
}

Детали конфигурации

  1. IER = 0: отключаем прерывания UART (TODO: включить для RX).
  2. DLAB = 1: разрешаем программирование делителя бода.
  3. Divisor = 3: при тактовой 1.8432 MHz → 115200 / 3 = 38400 бод.
  4. LCR = 0x03: 8N1 — 8 бит данных, нет чётности, 1 стоп-бит.
  5. FCR = 0xC7: enable FIFO, clear both FIFOs, trigger at 14 bytes.
  6. MCR = 0x0B: DTR=1, RTS=1, OUT2=1 (необходимо для IRQ на ISA шине).

Отправка байта

fn is_transmit_empty(&self) -> bool {
    unsafe { (inb(self.0 + 5) & 0x20) != 0 }  // LSR bit 5 = Transmitter Holding Register Empty
}

pub fn send(&self, data: u8) {
    while !self.is_transmit_empty() {}   // Ждём, пока UART готов
    unsafe { outb(self.0, data); }
}

fmt::Write реализация

impl core::fmt::Write for SerialPort {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        for byte in s.bytes() { self.send(byte); }
        Ok(())
    }
}

Глобальный экземпляр

static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);

pub fn init_global() {
    let mut guard = SERIAL_PORT.lock();
    *guard = Some(unsafe { SerialPort::init() });
}

pub fn write_global(args: core::fmt::Arguments) {
    let mut guard = SERIAL_PORT.lock();
    if let Some(ref mut sp) = *guard {
        let _ = sp.write_fmt(args);
    }
}

init_global() вызывается из kmain() после инициализации TTY. write_global() используется:

  • В log!() макросе (через debug.rs).
  • В rust_panic() обработчике.
  • В обработчиках исключений (GPF, Double Fault, Early Exception).

Низкоуровневый I/O

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

unsafe fn inb(port: u16) -> u8 {
    let res: u8;
    asm!("in al, dx", out("al") res, in("dx") port,
         options(nomem, nostack, preserves_flags));
    res
}