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 2f93806
Show file tree
Hide file tree
Showing 4 changed files with 217 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
205 changes: 205 additions & 0 deletions acpi/src/spcr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use crate::{
address::{GenericAddress, RawGenericAddress},
AcpiResult,
AcpiTable,
SdtHeader,
Signature,
};
use core::{
num::{NonZeroU8, NonZeroU32},
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,
/// The type of the register interface
pub 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 {
/// 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;
}
}

0 comments on commit 2f93806

Please sign in to comment.