Skip to content

Commit

Permalink
acpi: add support for SPCR table
Browse files Browse the repository at this point in the history
Add support for the Serial Port Console Redirection (SPCR). The table
provides information about the configuration and use of the serial
port or non-legacy UART interface.
  • Loading branch information
00xc committed Jun 16, 2024
1 parent 4390f04 commit 8fba408
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 0 deletions.
1 change: 1 addition & 0 deletions acpi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ edition = "2021"

[dependencies]
bit_field = "0.10.2"
bitflags = "2.5.0"
log = "0.4.20"

[features]
Expand Down
10 changes: 10 additions & 0 deletions acpi/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions acpi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
286 changes: 286 additions & 0 deletions acpi/src/spcr.rs
Original file line number Diff line number Diff line change
@@ -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<AcpiResult<GenericAddress>> {
(!self.base_address.is_empty()).then(|| GenericAddress::from_raw(self.base_address))
}

fn configured_baud_rate(&self) -> Option<NonZeroU32> {
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> {
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<u8> {
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<u32> {
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<u16> {
(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<u16> {
(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> {
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> {
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> {
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<NonZeroU32> {
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::<u8>();
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<u8> 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),
}
}
}

0 comments on commit 8fba408

Please sign in to comment.