111 lines
3.5 KiB
Markdown
111 lines
3.5 KiB
Markdown
# 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 |
|
||
|
||
## Инициализация
|
||
|
||
```rust
|
||
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 шине).
|
||
|
||
## Отправка байта
|
||
|
||
```rust
|
||
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 реализация
|
||
|
||
```rust
|
||
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(())
|
||
}
|
||
}
|
||
```
|
||
|
||
## Глобальный экземпляр
|
||
|
||
```rust
|
||
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
|
||
|
||
```rust
|
||
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
|
||
}
|
||
```
|