diff --git a/acpi/Cargo.toml b/acpi/Cargo.toml index e80b462d..5c26f54e 100644 --- a/acpi/Cargo.toml +++ b/acpi/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [dependencies] bit_field = "0.10.2" +bitflags = "2.5.0" log = "0.4.20" [features] diff --git a/acpi/src/address.rs b/acpi/src/address.rs index 66817362..0d890b18 100644 --- a/acpi/src/address.rs +++ b/acpi/src/address.rs @@ -16,6 +16,16 @@ pub(crate) struct RawGenericAddress { pub address: u64, } +impl RawGenericAddress { + pub(crate) const fn is_empty(&self) -> bool { + self.address_space == 0 + && self.bit_width == 0 + && self.bit_offset == 0 + && self.access_size == 0 + && self.address == 0 + } +} + #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum AddressSpace { SystemMemory, diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index 1efee0f3..1bc1f78e 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -75,6 +75,7 @@ pub mod madt; pub mod mcfg; pub mod rsdp; pub mod sdt; +pub mod spcr; #[cfg(feature = "allocator_api")] mod managed_slice; diff --git a/acpi/src/spcr.rs b/acpi/src/spcr.rs new file mode 100644 index 00000000..d38e47da --- /dev/null +++ b/acpi/src/spcr.rs @@ -0,0 +1,286 @@ +use crate::{ + address::{GenericAddress, RawGenericAddress}, + AcpiResult, + AcpiTable, + SdtHeader, + Signature, +}; +use core::{ + num::{NonZeroU32, NonZeroU8}, + ptr, + slice, + str::{self, Utf8Error}, +}; + +/// Serial Port Console Redirection (SPCR) Table. +/// +/// The table provides information about the configuration and use of the +/// serial port or non-legacy UART interface. On a system where the BIOS or +/// system firmware uses the serial port for console input/output, this table +/// should be used to convey information about the settings. +/// +/// For more information, see [the official documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/serial-port-console-redirection-table). +#[repr(C, packed)] +#[derive(Debug)] +pub struct Spcr { + pub header: SdtHeader, + interface_type: u8, + _reserved: [u8; 3], + base_address: RawGenericAddress, + interrupt_type: u8, + irq: u8, + global_system_interrupt: u32, + /// The baud rate the BIOS used for redirection. + configured_baud_rate: u8, + pub parity: u8, + pub stop_bits: u8, + flow_control: u8, + terminal_type: u8, + /// Language which the BIOS was redirecting. Must be 0. + pub language: u8, + pci_device_id: u16, + pci_vendor_id: u16, + pci_bus_number: u8, + pci_device_number: u8, + pci_function_number: u8, + pub pci_flags: u32, + /// PCI segment number. systems with fewer than 255 PCI buses, this number + /// will be 0. + pub pci_segment: u8, + uart_clock_freq: u32, + precise_baud_rate: u32, + namespace_string_length: u16, + namespace_string_offset: u16, +} + +unsafe impl AcpiTable for Spcr { + const SIGNATURE: Signature = Signature::SPCR; + + fn header(&self) -> &SdtHeader { + &self.header + } +} + +impl Spcr { + /// Gets the type of the register interface. + pub fn interface_type(&self) -> SpcrInteraceType { + SpcrInteraceType::from(self.interface_type) + } + + /// The base address of the Serial Port register set, if if console + /// redirection is enabled. + pub fn base_address(&self) -> Option> { + (!self.base_address.is_empty()).then(|| GenericAddress::from_raw(self.base_address)) + } + + fn configured_baud_rate(&self) -> Option { + match self.configured_baud_rate { + 3 => unsafe { Some(NonZeroU32::new_unchecked(9600)) }, + 4 => unsafe { Some(NonZeroU32::new_unchecked(19200)) }, + 6 => unsafe { Some(NonZeroU32::new_unchecked(57600)) }, + 7 => unsafe { Some(NonZeroU32::new_unchecked(115200)) }, + _ => None, + } + } + + /// The baud rate the BIOS used for redirection, if configured. + pub fn baud_rate(&self) -> Option { + NonZeroU32::new(self.precise_baud_rate).or_else(|| self.configured_baud_rate()) + } + + /// Flow control flags for the UART. + pub fn flow_control(&self) -> SpcrFlowControl { + SpcrFlowControl::from_bits_truncate(self.flow_control) + } + + /// Interrupt type(s) used by the UART. + pub fn interrupt_type(&self) -> SpcrInterruptType { + SpcrInterruptType::from_bits_truncate(self.interrupt_type) + } + + /// The PC-AT-compatible IRQ used by the UART, if the UART supports it. + /// Support is indicated by the [`interrupt_type`](Self::interrupt_type). + pub fn irq(&self) -> Option { + self.interrupt_type().contains(SpcrInterruptType::DUAL_8259).then_some(self.irq) + } + + /// The Global System Interrupt (GSIV) used by the UART, if the UART + /// supports it. Support is indicated by the + /// [`interrupt_type`](Self::interrupt_type). + pub fn global_system_interrupt(&self) -> Option { + if self.interrupt_type().difference(SpcrInterruptType::DUAL_8259).is_empty() { + return None; + } + Some(self.global_system_interrupt) + } + + /// The terminal protocol the BIOS was using for console redirection. + pub fn terminal_type(&self) -> SpcrTerminalType { + SpcrTerminalType::from_bits_truncate(self.terminal_type) + } + + /// If the UART is a PCI device, returns its Device ID. + pub fn pci_device_id(&self) -> Option { + (self.pci_device_id != 0xffff).then_some(self.pci_device_id) + } + + /// If the UART is a PCI device, returns its Vendor ID. + pub fn pci_vendor_id(&self) -> Option { + (self.pci_vendor_id != 0xffff).then_some(self.pci_vendor_id) + } + + /// If the UART is a PCI device, returns its bus number. + pub fn pci_bus_number(&self) -> Option { + NonZeroU8::new(self.pci_bus_number) + } + + /// If the UART is a PCI device, returns its device number. + pub fn pci_device_number(&self) -> Option { + NonZeroU8::new(self.pci_device_number) + } + + /// If the UART is a PCI device, returns its function number. + pub fn pci_function_number(&self) -> Option { + NonZeroU8::new(self.pci_function_number) + } + + /// The UART clock frequency in Hz, if it can be determined. + pub const fn uart_clock_frequency(&self) -> Option { + if self.header.revision <= 2 { + return None; + } + NonZeroU32::new(self.uart_clock_freq) + } + + /// An ASCII string to uniquely identify this device. This string consists + /// of a fully qualified reference to the object that represents this + /// device in the ACPI namespace. If no namespace device exists, + /// the namespace string must only contain a single '.'. + pub fn namespace_string(&self) -> Result<&str, Utf8Error> { + let start = ptr::from_ref(self).cast::(); + let bytes = unsafe { + let str_start = start.add(self.namespace_string_offset as usize); + slice::from_raw_parts(str_start, self.namespace_string_length as usize) + }; + str::from_utf8(bytes) + } +} + +bitflags::bitflags! { + /// Interrupt type(s) used by an UART. + #[derive(Clone, Copy, Debug)] + pub struct SpcrInterruptType: u8 { + /// PC-AT-compatible dual-8259 IRQ interrupt. + const DUAL_8259 = 1 << 0; + /// I/O APIC interrupt (Global System Interrupt). + const IO_APIC = 1 << 1; + /// I/O SAPIC interrupt (Global System Interrupt). + const IO_SAPIC = 1 << 2; + /// ARMH GIC interrupt (Global System Interrupt). + const ARMH_GIC = 1 << 3; + /// RISC-V PLIC/APLIC interrupt (Global System Interrupt). + const RISCV_PLIC = 1 << 4; + } +} + +bitflags::bitflags! { + /// The terminal protocol the BIOS uses for console redirection. + #[derive(Clone, Copy, Debug)] + pub struct SpcrTerminalType: u8 { + const VT1000 = 1 << 0; + const EXTENDED_VT1000 = 1 << 1; + const VT_UTF8 = 1 << 2; + const ANSI = 1 << 3; + } +} + +bitflags::bitflags! { + /// Flow control flags for the UART. + #[derive(Clone, Copy, Debug)] + pub struct SpcrFlowControl: u8 { + /// DCD required for transmit + const DCD = 1 << 0; + /// RTS/CTS hardware flow control + const RTS_CTS = 1 << 1; + /// XON/XOFF software control + const XON_XOFF = 1 << 2; + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum SpcrInteraceType { + /// Full 16550 interface + Full16550, + /// Full 16450 interface (must also accept writing to the 16550 FCR register). + Full16450, + /// MAX311xE SPI UART + MAX311xE, + /// Arm PL011 UART + ArmPL011, + /// MSM8x60 (e.g. 8960) + MSM8x60, + /// Nvidia 16550 + Nvidia16550, + /// TI OMAP + TiOmap, + /// APM88xxxx + APM88xxxx, + /// MSM8974 + Msm8974, + /// SAM5250 + Sam5250, + /// Intel USIF + IntelUSIF, + /// i.MX 6 + Imx6, + /// (deprecated) Arm SBSA (2.x only) Generic UART supporting only 32-bit accesses + ArmSBSAGeneric32bit, + /// Arm SBSA Generic UART + ArmSBSAGeneric, + /// Arm DCC + ArmDCC, + /// VCM2835 + Bcm2835, + /// SDM845 with clock rate of 1.8432 MHz + Sdm845_18432, + /// 16550-compatible with parameters defined in Generic Address Structure + Generic16550, + /// SDM845 with clock rate of 7.372 MHz + Sdm845_7372, + /// Intel LPSS + IntelLPSS, + /// RISC-V SBI console (any supported SBI mechanism) + RiscVSbi, + /// Unknown interface + Unknown(u8), +} + +impl From for SpcrInteraceType { + fn from(val: u8) -> Self { + match val { + 0x00 => Self::Full16550, + 0x01 => Self::Full16450, + 0x02 => Self::MAX311xE, + 0x03 => Self::ArmPL011, + 0x04 => Self::MSM8x60, + 0x05 => Self::Nvidia16550, + 0x06 => Self::TiOmap, + 0x08 => Self::APM88xxxx, + 0x09 => Self::Msm8974, + 0x0A => Self::Sam5250, + 0x0B => Self::IntelUSIF, + 0x0C => Self::Imx6, + 0x0D => Self::ArmSBSAGeneric32bit, + 0x0E => Self::ArmSBSAGeneric, + 0x0F => Self::ArmDCC, + 0x10 => Self::Bcm2835, + 0x11 => Self::Sdm845_18432, + 0x12 => Self::Generic16550, + 0x13 => Self::Sdm845_7372, + 0x14 => Self::IntelLPSS, + 0x15 => Self::RiscVSbi, + _ => Self::Unknown(val), + } + } +}