3.5 KiB
3.5 KiB
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)
}
Детали конфигурации
- IER = 0: отключаем прерывания UART (TODO: включить для RX).
- DLAB = 1: разрешаем программирование делителя бода.
- Divisor = 3: при тактовой 1.8432 MHz → 115200 / 3 = 38400 бод.
- LCR = 0x03: 8N1 — 8 бит данных, нет чётности, 1 стоп-бит.
- FCR = 0xC7: enable FIFO, clear both FIFOs, trigger at 14 bytes.
- 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
}