Skip to main content

ostd/arch/x86/
serial.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! The console I/O.
4
5use spin::Once;
6use x86_64::instructions::port::ReadWriteAccess;
7
8use crate::{
9    boot::EarlyCmdline,
10    console::uart_ns16650a::{Ns16550aAccess, Ns16550aRegister, Ns16550aUart},
11    io::{IoPort, reserve_io_port_range},
12    sync::{LocalIrqDisabled, SpinLock},
13};
14
15/// The primary serial port, which serves as an early console.
16pub static SERIAL_PORT: Once<SpinLock<Ns16550aUart<SerialAccess>, LocalIrqDisabled>> = Once::new();
17
18/// Access to serial registers via I/O ports in x86.
19#[derive(Debug)]
20pub struct SerialAccess {
21    data: IoPort<u8, ReadWriteAccess>,
22    int_en: IoPort<u8, ReadWriteAccess>,
23    fifo_ctrl: IoPort<u8, ReadWriteAccess>,
24    line_ctrl: IoPort<u8, ReadWriteAccess>,
25    modem_ctrl: IoPort<u8, ReadWriteAccess>,
26    line_stat: IoPort<u8, ReadWriteAccess>,
27    modem_stat: IoPort<u8, ReadWriteAccess>,
28}
29
30impl SerialAccess {
31    /// # Safety
32    ///
33    /// The caller must ensure that the base port is a valid serial base port and that it has
34    /// exclusive ownership of the serial registers.
35    const unsafe fn new(port: u16) -> Self {
36        // SAFETY: The safety is upheld by the caller.
37        unsafe {
38            Self {
39                data: IoPort::new(port),
40                int_en: IoPort::new(port + 1),
41                fifo_ctrl: IoPort::new(port + 2),
42                line_ctrl: IoPort::new(port + 3),
43                modem_ctrl: IoPort::new(port + 4),
44                line_stat: IoPort::new(port + 5),
45                modem_stat: IoPort::new(port + 6),
46            }
47        }
48    }
49}
50
51impl Ns16550aAccess for SerialAccess {
52    fn read(&self, reg: Ns16550aRegister) -> u8 {
53        match reg {
54            Ns16550aRegister::DataOrDivisorLo => self.data.read(),
55            Ns16550aRegister::IntEnOrDivisorHi => self.int_en.read(),
56            Ns16550aRegister::FifoCtrl => self.fifo_ctrl.read(),
57            Ns16550aRegister::LineCtrl => self.line_ctrl.read(),
58            Ns16550aRegister::ModemCtrl => self.modem_ctrl.read(),
59            Ns16550aRegister::LineStat => self.line_stat.read(),
60            Ns16550aRegister::ModemStat => self.modem_stat.read(),
61        }
62    }
63
64    fn write(&mut self, reg: Ns16550aRegister, val: u8) {
65        match reg {
66            Ns16550aRegister::DataOrDivisorLo => self.data.write(val),
67            Ns16550aRegister::IntEnOrDivisorHi => self.int_en.write(val),
68            Ns16550aRegister::FifoCtrl => self.fifo_ctrl.write(val),
69            Ns16550aRegister::LineCtrl => self.line_ctrl.write(val),
70            Ns16550aRegister::ModemCtrl => self.modem_ctrl.write(val),
71            Ns16550aRegister::LineStat => self.line_stat.write(val),
72            Ns16550aRegister::ModemStat => self.modem_stat.write(val),
73        }
74    }
75}
76
77/// Initializes the serial port.
78pub(crate) fn init(early_cmdline: &EarlyCmdline) {
79    if !early_cmdline.has_early_console {
80        return;
81    }
82
83    // TODO: Add existence check for COM1.
84    SERIAL_PORT.call_once(|| {
85        // SAFETY:
86        // 1. The legacy COM1 serial port at 0x3F8 can be disabled via the command line. If it is
87        //    enabled, it is assumed to exist and be accessible via the I/O registers.
88        //    (FIXME: This needs to be confirmed by checking the ACPI table or using more specific
89        //    kernel parameters to obtain early information for building the early console.)
90        // 2. `reserve_io_port_range` guarantees exclusive ownership of the I/O registers.
91        let access = unsafe { SerialAccess::new(0x3F8) };
92        let mut serial = Ns16550aUart::new(access);
93        serial.init();
94        SpinLock::new(serial)
95    });
96}
97reserve_io_port_range!(0x3F8..0x400);