diff --git a/acpi/src/address.rs b/acpi/src/address.rs index 0a403b5..e527218 100644 --- a/acpi/src/address.rs +++ b/acpi/src/address.rs @@ -45,6 +45,7 @@ pub enum AddressSpace { Ipmi, GeneralIo, GenericSerialBus, + /// Describes a subspace in the Platform Communications Channel Table ([PCCT](crate::pcct::Pcct)). PlatformCommunicationsChannel, FunctionalFixedHardware, OemDefined(u8), diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs index 0b254df..2ff0254 100644 --- a/acpi/src/lib.rs +++ b/acpi/src/lib.rs @@ -70,6 +70,7 @@ pub mod handler; pub mod hpet; pub mod madt; pub mod mcfg; +pub mod pcct; pub mod rsdp; pub mod sdt; pub mod spcr; @@ -139,6 +140,10 @@ pub enum AcpiError { InvalidMadt(MadtError), InvalidGenericAddress, + /// The signature for a PCC subspace shared memory region did not + /// match. + PccInvalidShmemSignature(u32), + AllocError, } diff --git a/acpi/src/pcct/extended.rs b/acpi/src/pcct/extended.rs new file mode 100644 index 0000000..e739b93 --- /dev/null +++ b/acpi/src/pcct/extended.rs @@ -0,0 +1,220 @@ +use super::{PccCmdCompleteCheck, PccPlatformInterruptFlags, PccShmemHdr, PccSubspaceHeader, RawGenericAddress}; +use crate::{address::GenericAddress, AcpiResult}; +use bitflags::bitflags; + +/// Extended PCC communication subspace. +/// +/// The subspace might be a master or slave subspace, depending on +/// its type (3 and 4 respectively). The type can be inspected in the +/// subspace header or via this type's methods +/// ([`is_master()`](Self::is_master) and [`is_slave()`](Self::is_slave)). +/// +/// * Master subspaces are used by the OSPM to communicate with the +/// platform. +/// * Slave subspaces are used by the platform to send asynchronous +/// notifications to the OSPM. +/// +/// See section "14.1.6. Extended PCC subspaces (types 3 and 4)" of +/// the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PccExtendedSubspace { + /// PCC subspace header. + pub header: PccSubspaceHeader, + /// GSIV of the interrupt used for the PCC platform interrupt for + /// this subspace. + pub plat_interrupt: u32, + /// Flags for the platform interrupt for this subspace. + pub plat_interrupt_flags: PccPlatformInterruptFlags, + _rsvd: u8, + pub(super) base_address: u64, + pub(super) mem_len: u32, + pub(super) doorbell_reg: RawGenericAddress, + pub(super) doorbell_preserve: u64, + pub(super) doorbell_write: u64, + /// Expected latency to process a command, in microseconds. + pub nominal_latency: u32, + /// The maximum number of periodic requests that the subspace + /// channel can support, reported in commands per minute. 0 + /// indicates no limitation. + pub max_periodic_access_rate: u32, + /// The minimum amount of time that OSPM must wait after + /// the completion of a command before issuing the next command, + /// in microseconds. + pub min_request_turnaround_time: u32, + plat_interrupt_ack_reg: RawGenericAddress, + plat_interrupt_ack_preserve: u64, + plat_interrupt_ack_write: u64, + _rsvd2: [u8; 8], + cmd_complete_check_reg: RawGenericAddress, + cmd_complete_check_mask: u64, + cmd_complete_update_reg: RawGenericAddress, + cmd_complete_update_preserve: u64, + cmd_complete_update_write: u64, + err_status_reg: RawGenericAddress, + err_status_mask: u64, +} + +impl PccExtendedSubspace { + /// Returns `true` if this is a master subspace. + pub fn is_master(&self) -> bool { + self.header.stype == 3 + } + + /// Returns `true` if this is a slave subspace. + pub fn is_slave(&self) -> bool { + self.header.stype == 4 + } + + /// Get information about the platform interrupt ACK mechanism. + pub fn platform_interrupt_ack(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.plat_interrupt_ack_reg)?; + Ok(PccPlatformInterruptAck { + addr, + preserve: self.plat_interrupt_ack_preserve, + write: self.plat_interrupt_ack_write, + }) + } + + /// Get information about the command complete check register. + pub fn cmd_complete_check(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.cmd_complete_check_reg)?; + Ok(PccCmdCompleteCheck { addr, mask: self.cmd_complete_check_mask }) + } + + /// Get information about the command complete update register. + pub fn cmd_complete_update(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.cmd_complete_update_reg)?; + Ok(PccCmdCompleteUpdate { + addr, + preserve: self.cmd_complete_update_preserve, + write: self.cmd_complete_update_write, + }) + } + + /// Get information about the error status register. + pub fn error_status(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.err_status_reg)?; + Ok(PccErrorStatus { addr, mask: self.err_status_mask }) + } +} + +/// Information about the command complete update register. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccCmdCompleteUpdate { + /// Location of the register. + pub addr: GenericAddress, + /// Mask of bits to preserve in the command complete update + /// register, when updating command complete in this subspace. + pub preserve: u64, + /// Mask of bits to set in the command complete update register, + /// when updating command complete in this subspace. For master + /// subspaces the mask must indicate how to clear the command + /// complete bit. For slave subspaces, the mask must indicate how + /// to set the command complete bit. + pub write: u64, +} + +/// Platform interrupt ACK information. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccPlatformInterruptAck { + /// Location of the register. + pub addr: GenericAddress, + /// Bits to preserve when writing the register. + pub preserve: u64, + /// Bits to set when writing the register. + pub write: u64, +} + +/// Information about the error status register. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccErrorStatus { + /// Location of the register. + pub addr: GenericAddress, + /// The mask contained here can be combined through a logical AND + /// with content of the Error status register to ascertain whether + /// an error occurred in the transmission of the command through + /// the subspace. The logical NOT of this mask is be used to clear + /// the error. The inverted mask is combined through a logical AND + /// with the content of the Error status register, and the result + /// is written back into said register. This field is ignored for + /// slave channels. + pub mask: u64, +} + +/// Shared memory region header for [`PccExtendedSubspace`]. +/// +/// See section "14.3. Extended PCC Subspace Shared Memory Region" of +/// the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccExtendedShmem { + /// The PCC signature. The signature of a subspace is computed by + /// a bitwise-or of the value `0x50434300` with the subspace ID. + /// For example, subspace 3 has the signature `0x50434303`. + pub signature: u32, + /// Flags for doorbell behavior on this shared region. + pub flags: PccExtendedShmemFlags, + /// Length of payload being transmitted including command field. + pub len: u32, + /// Command being sent over the subspace. + pub cmd: u32, +} + +impl PccShmemHdr for PccExtendedShmem { + fn signature(&self) -> u32 { + self.signature + } +} + +bitflags! { + /// Flags for [`PccExtendedShmem`]. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct PccExtendedShmemFlags: u32 { + /// **For master subspaces** this field indicates to the + /// platform that it must generate an interrupt when the + /// command has completed. - Setting this bit to 1 when + /// sending a command, requests that completion of the command + /// is signaled via the platform interrupt. - Setting it to 0 + /// when sending a command, requests that no interrupt is + /// asserted when the command is completed. + /// + /// **For slave subspaces**, if the doorbell field of the + /// slave subspace is non zero, and this flag is set, the + /// OSPM must access the doorbell once it has processed the + /// notification. This bit is ignored by the platform if the + /// [Platform Interrupt field](PcctFlags::INTERRUPT) of the + /// [PCC flags](Pcct::flags) is set to zero. + const NOTIFY_ON_COMPLETION = 1; + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem::offset_of; + + #[test] + fn pcc_extended_subspace_offsets() { + assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt), 2); + assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_flags), 6); + assert_eq!(offset_of!(PccExtendedSubspace, base_address), 8); + assert_eq!(offset_of!(PccExtendedSubspace, mem_len), 16); + assert_eq!(offset_of!(PccExtendedSubspace, doorbell_reg), 20); + assert_eq!(offset_of!(PccExtendedSubspace, doorbell_preserve), 32); + assert_eq!(offset_of!(PccExtendedSubspace, doorbell_write), 40); + assert_eq!(offset_of!(PccExtendedSubspace, nominal_latency), 48); + assert_eq!(offset_of!(PccExtendedSubspace, max_periodic_access_rate), 52); + assert_eq!(offset_of!(PccExtendedSubspace, min_request_turnaround_time), 56); + assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_reg), 60); + assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_preserve), 72); + assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_write), 80); + assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_check_reg), 96); + assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_check_mask), 108); + assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_reg), 116); + assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_preserve), 128); + assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_write), 136); + assert_eq!(offset_of!(PccExtendedSubspace, err_status_reg), 144); + assert_eq!(offset_of!(PccExtendedSubspace, err_status_mask), 156); + } +} diff --git a/acpi/src/pcct/generic.rs b/acpi/src/pcct/generic.rs new file mode 100644 index 0000000..a9fdca3 --- /dev/null +++ b/acpi/src/pcct/generic.rs @@ -0,0 +1,131 @@ +use super::{PccShmemHdr, PccSubspaceHeader, RawGenericAddress}; +use bitflags::bitflags; + +/// Generic PCC communication subspace. +/// +/// See section "14.1.3. Generic Communications Subspace Structure +/// (type 0)" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PccGenericSubspace { + /// PCC subspace header. + pub header: PccSubspaceHeader, + _rsvd: [u8; 6], + pub(super) base_address: u64, + pub(super) mem_len: u64, + pub(super) doorbell_reg: RawGenericAddress, + pub(super) doorbell_preserve: u64, + pub(super) doorbell_write: u64, + /// Expected latency to process a command, in microseconds. + pub nominal_latency: u32, + /// The maximum number of periodic requests that the subspace + /// channel can support, reported in commands per minute. 0 + /// indicates no limitation. + pub max_periodic_access_rate: u32, + /// The minimum amount of time that OSPM must wait after + /// the completion of a command before issuing the next command, + /// in microseconds. + pub min_request_turnaround_time: u16, +} + +/// Shared memory region header for [`PccGenericSubspace`]. +/// +/// See section "14.2. Generic Communications Channel Shared Memory +/// Region" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccGenericShmem { + /// The PCC signature. The signature of a subspace is computed b + /// a bitwise-or of the value `0x50434300` with the subspace ID. + /// For example, subspace 3 has the signature `0x50434303`. + pub signature: u32, + /// Commands for the platform to perform. + pub cmd: PccGenericShmemCmd, + /// Command processing status. + pub status: PccGenericShmemStatus, +} + +impl PccShmemHdr for PccGenericShmem { + fn signature(&self) -> u32 { + self.signature + } +} + +/// Command field for [`PccGenericShmem`]. +/// +/// For [`PccGenericSubspace`], +/// [`PccHwReducedSubspace1`](super::PccHwReducedSubspace1) and +/// [`PccHwReducedSubspace2`](super::PccHwReducedSubspace2), this +/// 16-bit field is used to select one of the defined commands for +/// the platform to perform. OSPM is esponsible for populating this +/// field before each command invocation. +/// +/// See section "14.2.1. Generic Communications Channel Command +/// Field" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccGenericShmemCmd { + /// Command code to execute. Command codes are application + /// specific and defined by the consumer of this interface. + pub cmd: u8, + /// Additional bitfields for the command field. + pub flags: PccShmemCmdFlags, +} + +bitflags! { + /// Flags for [`PccGenericShmemCmd`]. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct PccShmemCmdFlags: u8 { + /// If set, the platform should generate a Doorbell interrupt + /// at the completion of this command. The interrupt is an + /// SCI for a [`PccGenericSubspace`], or as described by + /// the [Doorbell Interrupt field](PccHwReducedSubspace1::plat_interrupt) + /// for [`PccHwReducedSubspace1`] and + /// [`PccHwReducedSubspace2`]. If the + /// [Doorbell bit](PcctFlags::PLATFORM_INTERRUPT) is not set + /// in the [PCC global flags](Pcct::flags), this bit must be + /// cleared. + const NOTIFY_ON_COMPLETION = 1 << 7; + } +} + +bitflags! { + /// Status flags for [`PccGenericShmem`]. + /// + /// See section "14.2.2. Generic Communications Channel Status + /// Field" of the ACPI spec for more information. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct PccGenericShmemStatus: u16 { + /// If set, the platform has completed processing the last + /// command. + const CMD_COMPLETE = 0b1; + /// If set, the platform has issued a Platform Interrupt to + /// this subspace. OSPM must check the + /// [`CMD_COMPLETE`](Self::CMD_COMPLETE) and + /// [`PLATFORM_NOTIFICATION`](Self::PLATFORM_NOTIFICATION) + /// fields to determine the cause of the Interrupt. + const PLATFORM_INTERRUPT = 1 << 1; + /// If set, an error occurred executing the last command. + const ERROR = 1 << 2; + /// If set, indicates the platform is issuing an asynchronous + /// notification to OSPM. + const PLATFORM_NOTIFICATION = 1 << 3; + } +} +#[cfg(test)] +mod test { + use super::*; + use core::mem::offset_of; + + #[test] + fn pcc_generic_subspace_offsets() { + assert_eq!(offset_of!(PccGenericSubspace, base_address), 8); + assert_eq!(offset_of!(PccGenericSubspace, mem_len), 16); + assert_eq!(offset_of!(PccGenericSubspace, doorbell_reg), 24); + assert_eq!(offset_of!(PccGenericSubspace, doorbell_preserve), 36); + assert_eq!(offset_of!(PccGenericSubspace, doorbell_write), 44); + assert_eq!(offset_of!(PccGenericSubspace, nominal_latency), 52); + assert_eq!(offset_of!(PccGenericSubspace, max_periodic_access_rate), 56); + assert_eq!(offset_of!(PccGenericSubspace, min_request_turnaround_time), 60); + } +} diff --git a/acpi/src/pcct/mod.rs b/acpi/src/pcct/mod.rs new file mode 100644 index 0000000..b4ffb1f --- /dev/null +++ b/acpi/src/pcct/mod.rs @@ -0,0 +1,321 @@ +use crate::{ + address::{AddressSpace, GenericAddress, RawGenericAddress}, + sdt::{SdtHeader, Signature}, + AcpiError, + AcpiHandler, + AcpiResult, + AcpiTable, + PhysicalMapping, +}; +use bitflags::bitflags; +use core::{marker::PhantomData, ptr::NonNull}; + +/// Generic subspace structures (type 0). +pub mod generic; +use generic::*; + +/// HW-reduced subspace structures (types 1 and 2). +pub mod reduced; +use reduced::*; + +/// Extended PCC subspace structures (types 3 and 4). +pub mod extended; +use extended::*; + +/// HW-registers-based subspace structures (type 5). +pub mod register_based; +use register_based::*; + +bitflags! { + /// Global flags for the [PCCT](Pcct). + /// + /// See section "14.1.1. Platform Communications Channel Global + /// Flags" of the ACPI spec for more information. + #[derive(Clone, Copy, Debug)] + pub struct PcctFlags: u32 { + /// If set, the platform is capable of generating an + /// interrupt to indicate completion of a command. + const PLATFORM_INTERRUPT = 0b1; + } +} + +/// Platform Communications Channel Table (PCCT). +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Pcct { + header: SdtHeader, + /// Global flags for the PCCT. + pub flags: PcctFlags, + _rsvd: [u8; 8], +} + +unsafe impl AcpiTable for Pcct { + const SIGNATURE: Signature = Signature::PCCT; + + fn header(&self) -> &SdtHeader { + &self.header + } +} + +impl Pcct { + /// Returns an iterator over the PCC subspaces. + pub const fn entries(&self) -> PcctEntryIter { + // SAFETY: this is the layout specified by the ACPI spec. + // (14.1. Platform Communications Channel Table) + let ptr = unsafe { core::ptr::from_ref(self).add(1).cast() }; + let len = (self.header.length as usize).saturating_sub(size_of::()); + PcctEntryIter { ptr, num: 0, len, _phantom: PhantomData } + } + + /// Gets the appropriate subspace for the given address and its + /// position in the table. Returns an error if the address does + /// not belong to the PCC address space. + pub fn get_subspace(&self, addr: GenericAddress) -> AcpiResult<(u8, PcctEntry)> { + if addr.address_space != AddressSpace::PlatformCommunicationsChannel { + return Err(AcpiError::InvalidGenericAddress); + } + // This should never fail as we index with an `u8`. + let idx = addr.access_size; + let entry = self.entries().nth(idx as usize).ok_or(AcpiError::InvalidGenericAddress)?; + Ok((idx, entry)) + } +} + +/// An iterator over the PCC subspaces in the [PCCT](Pcct). +#[derive(Clone, Debug)] +pub struct PcctEntryIter<'a> { + ptr: *const u8, + len: usize, + num: usize, + _phantom: PhantomData<&'a ()>, +} + +impl<'a> Iterator for PcctEntryIter<'a> { + type Item = PcctEntry<'a>; + + fn next(&mut self) -> Option { + // Stop after we exceed the total length or we iterate over + // the maximum number of entries. + if self.len == 0 || self.num >= 256 { + return None; + } + + // SAFETY: we keep track of each entry we parse, so the + // pointer must be valid. + let entry = unsafe { Self::Item::from_ptr(self.ptr)? }; + let entry_len = entry.header().len as usize; + + self.num += 1; + self.ptr = self.ptr.wrapping_add(entry_len); + self.len = self.len.saturating_sub(entry_len); + Some(entry) + } +} + +/// Common header for all PCC subspaces. +/// +/// See section "14.1.2. Platform Communications Channel Subspace +/// Structures" of the ACPI spec for more information. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(C, packed)] +pub struct PccSubspaceHeader { + pub stype: u8, + pub len: u8, +} + +/// Information about the command complete check register for a PCC +/// subspace. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccCmdCompleteCheck { + /// Location of the register. + pub addr: GenericAddress, + /// Mask to determine whether a command is complete, using the + /// command complete check register. A command is complete if the + /// value of the register when combined through a logical AND with + /// this mask, yields a non-zero value + pub mask: u64, +} + +/// Information about the Doorbell register a PCC subspace. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccDoorbell { + /// The address of the doorbell + pub addr: GenericAddress, + /// Mask of bits to preserve when writing the doorbell register. + pub preserve: u64, + /// Mask of bits to set when writing the doorbell register. + pub write: u64, +} + +/// A generic shared memory region for a PCC subspace. +/// +/// The region is formed by a header (`T`) and a variable-length area +/// [`PccShmem::shmem`]. +#[derive(Debug)] +pub struct PccShmem { + /// Mapping for the shared memory region header. + pub header: PhysicalMapping, + /// Communication space. The pointer is only valid while the + /// header mapping is alive. + pub shmem: NonNull<[u8]>, +} + +impl PccShmem { + /// Verify that the signature for the shared memory region matches. + pub fn verify_signature(&self, subspace_num: u8) -> AcpiResult<()> { + if self.header.signature() != 0x50434300 | (subspace_num as u32) { + return Err(AcpiError::PccInvalidShmemSignature(self.header.signature())); + } + Ok(()) + } +} + +/// Trait implemented by all PCC subspaces. It allows access to the +/// subspace's doorbell and shared memory region. +pub trait PccSubspace { + /// The header for this PCC subspace's shared memory region. + type ShmemHdr: PccShmemHdr; + + /// Map the shared memory region for this subspace. + /// + /// Each PCC subspace has an address and length that points to a + /// shared memory region for communications ([`PccShmem`]). This + /// region is formed by a header (whose format is determined by + /// the type of the subspace - [`Self::ShmemHdr`]) and a + /// variable-length region (whose format is determined by the + /// subsystem using this subspace). + fn map_shmem(&self, h: &H) -> Option>; + + /// Get information about the doorbell for this subspace, if set. + fn doorbell(&self) -> Option>; +} + +/// Trait implemented by all shared memory region headers. +pub trait PccShmemHdr { + /// The signature for this header. + fn signature(&self) -> u32; +} + +/// Prepare an enum with all possible PCC subspace entries, plus some +/// catch-all implementations. +macro_rules! pcct_subentries { + ([ + $( + ($ty:ty, [$($id:expr),+], $variant:ident, $shhdr:ty) + ),+ $(,)? + ]) => { + /// An entry in the PCCT, corresponding to a subspace of a + /// specific type. + #[derive(Clone, Copy, Debug)] + pub enum PcctEntry<'a> { + $( + $variant(&'a $ty), + )+ + } + + impl PcctEntry<'_> { + /// The PCC subspace header for this entry. + pub const fn header(&self) -> &PccSubspaceHeader { + match self { + $( + Self::$variant(v) => &v.header, + )+ + } + } + + /// Information about the doorbell for this subspace, if + /// set. + pub fn doorbell(&self) -> Option> { + match self { + $( + Self::$variant(v) => v.doorbell(), + )+ + } + } + + /// Read a PCC subspace entry based on its type. Returns + /// [`None`] if an unknown entry type is found. + /// + /// # Safety + /// + /// The caller must provide a pointer to the beginning + /// of a PCC subspace structure. + const unsafe fn from_ptr(ptr: *const u8) -> Option { + // SAFETY: the caller must ensure we are given a + // valid pointer. The header is packed, so it has no + // alignment requirements. + let header = unsafe { &*ptr.cast::() }; + match header.stype { + $( + $( + // SAFETY: we checked the entry type above. + $id => Some(Self::$variant(unsafe { &*ptr.cast() })), + )+ + )+ + _ => None, + } + } + } + + $( + impl PccSubspace for $ty { + type ShmemHdr = $shhdr; + + fn doorbell(&self) -> Option> { + if self.doorbell_reg.is_empty() { + return None; + } + Some(GenericAddress::from_raw(self.doorbell_reg).map(|addr| PccDoorbell { + addr, + write: self.doorbell_write, + preserve: self.doorbell_preserve, + })) + } + + fn map_shmem( + &self, + h: &H, + ) -> Option> { + if self.mem_len == 0 { + return None; + } + + // SAFETY: we trust the base address and length + // provided by the PCCT. + let header = unsafe { + h.map_physical_region::<$shhdr>( + self.base_address as usize, + self.mem_len as usize, + ) + }; + + // SAFETY: this is the layout specified in the + // ACPI spec. The area right after the header is + // the communication space. + // The length takes into account the size of the + // header itself. + let shmem_raw = unsafe { + header.virtual_start().add(1).cast::() + }; + let shmem_len = self.mem_len as usize - size_of::<$shhdr>(); + let shmem = NonNull::slice_from_raw_parts(shmem_raw, shmem_len); + Some(PccShmem { header, shmem }) + } + } + )* + }; +} + +pcct_subentries!([ + (PccGenericSubspace, [0], Generic, PccGenericShmem), + (PccHwReducedSubspace1, [1], HwReduced1, PccGenericShmem), + (PccHwReducedSubspace2, [2], HwReduced2, PccGenericShmem), + (PccExtendedSubspace, [3, 4], Extended, PccExtendedShmem), + (PccHwRegisterBasedSubspace, [5], HwRegisterBased, PccReducedShmem), +]); + +#[cfg(test)] +mod test { + use super::*; + use core::mem::offset_of; +} diff --git a/acpi/src/pcct/reduced.rs b/acpi/src/pcct/reduced.rs new file mode 100644 index 0000000..b167521 --- /dev/null +++ b/acpi/src/pcct/reduced.rs @@ -0,0 +1,133 @@ +use super::{PccPlatformInterruptAck, PccSubspaceHeader, RawGenericAddress}; +use crate::{address::GenericAddress, AcpiResult}; +use bitflags::bitflags; + +/// HW-reduced PCC communications subspace (type 1). +/// +/// See section "14.1.4. HW-Reduced Communications Subspace Structure +/// (type 1)" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PccHwReducedSubspace1 { + /// PCC subspace header. + pub header: PccSubspaceHeader, + /// GSIV of the interrupt used for the PCC platform interrupt for + /// this subspace. + pub plat_interrupt: u32, + /// Flags for the platform interrupt for this subspace. + pub plat_interrupt_flags: PccPlatformInterruptFlags, + _rsvd: u8, + pub(super) base_address: u64, + pub(super) mem_len: u64, + pub(super) doorbell_reg: RawGenericAddress, + pub(super) doorbell_preserve: u64, + pub(super) doorbell_write: u64, + /// Expected latency to process a command, in microseconds. + pub nominal_latency: u32, + /// The maximum number of periodic requests that the subspace + /// channel can support, reported in commands per minute. 0 + /// indicates no limitation. + pub max_periodic_access_rate: u32, + /// The minimum amount of time that OSPM must wait after + /// the completion of a command before issuing the next command, + /// in microseconds. + pub min_request_turnaround_time: u16, +} + +/// HW-reduced PCC communications subspace (type 2). +/// +/// See section "14.1.5. HW-Reduced Communications Subspace Structure +/// (type 2)" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PccHwReducedSubspace2 { + /// PCC subspace header. + pub header: PccSubspaceHeader, + /// GSIV of the interrupt used for the PCC platform interrupt for + /// this subspace. + pub plat_interrupt: u32, + /// Flags for the platform interrupt for this subspace. + pub plat_interrupt_flags: PccPlatformInterruptFlags, + _rsvd: u8, + pub(super) base_address: u64, + pub(super) mem_len: u64, + pub(super) doorbell_reg: RawGenericAddress, + pub(super) doorbell_preserve: u64, + pub(super) doorbell_write: u64, + /// Expected latency to process a command, in microseconds. + pub nominal_latency: u32, + /// The maximum number of periodic requests that the subspace + /// channel can support, reported in commands per minute. 0 + /// indicates no limitation. + pub max_periodic_access_rate: u32, + /// The minimum amount of time that OSPM must wait after + /// the completion of a command before issuing the next command, + /// in microseconds. + pub min_request_turnaround_time: u16, + pub(super) plat_interrupt_ack_reg: RawGenericAddress, + pub(super) plat_interrupt_ack_preserve: u64, + pub(super) plat_interrupt_ack_write: u64, +} + +impl PccHwReducedSubspace2 { + /// Get information about the platform interrupt ACK mechanism. + pub fn platform_interrupt_ack(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.plat_interrupt_ack_reg)?; + Ok(PccPlatformInterruptAck { + addr, + preserve: self.plat_interrupt_ack_preserve, + write: self.plat_interrupt_ack_write, + }) + } +} + +bitflags! { + /// Interrupt flags for [`PccHwReducedSubspace1`] and + /// [`PccHwReducedSubspace2`]. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PccPlatformInterruptFlags: u8 { + /// * 1: Interrupt is Active low. + /// * 0: Interrupt is Active high. + const INTERRUPT_POLARITY = 1 << 0; + /// * 1: Interrupt is Edge triggered. + /// * 0: Interrupt is Level triggered. + const INTERRUPT_MODE = 1 << 1; + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem::offset_of; + + #[test] + fn pcc_hw_reduced_subspace_1_offsets() { + assert_eq!(offset_of!(PccHwReducedSubspace1, plat_interrupt), 2); + assert_eq!(offset_of!(PccHwReducedSubspace1, plat_interrupt_flags), 6); + assert_eq!(offset_of!(PccHwReducedSubspace1, base_address), 8); + assert_eq!(offset_of!(PccHwReducedSubspace1, mem_len), 16); + assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_reg), 24); + assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_preserve), 36); + assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_write), 44); + assert_eq!(offset_of!(PccHwReducedSubspace1, nominal_latency), 52); + assert_eq!(offset_of!(PccHwReducedSubspace1, max_periodic_access_rate), 56); + assert_eq!(offset_of!(PccHwReducedSubspace1, min_request_turnaround_time), 60); + } + + #[test] + fn pcc_hw_reduced_subspace_2_offsets() { + assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt), 2); + assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_flags), 6); + assert_eq!(offset_of!(PccHwReducedSubspace2, base_address), 8); + assert_eq!(offset_of!(PccHwReducedSubspace2, mem_len), 16); + assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_reg), 24); + assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_preserve), 36); + assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_write), 44); + assert_eq!(offset_of!(PccHwReducedSubspace2, nominal_latency), 52); + assert_eq!(offset_of!(PccHwReducedSubspace2, max_periodic_access_rate), 56); + assert_eq!(offset_of!(PccHwReducedSubspace2, min_request_turnaround_time), 60); + assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_reg), 62); + assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_preserve), 74); + assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_write), 82); + } +} diff --git a/acpi/src/pcct/register_based.rs b/acpi/src/pcct/register_based.rs new file mode 100644 index 0000000..4649f71 --- /dev/null +++ b/acpi/src/pcct/register_based.rs @@ -0,0 +1,83 @@ +use crate::{address::GenericAddress, AcpiResult}; + +use super::{PccCmdCompleteCheck, PccErrorStatus, PccShmemHdr, PccSubspaceHeader, RawGenericAddress}; + +/// See section "14.1.7. HW Registers based Communications Subspace +/// Structure (Type 5)" of the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PccHwRegisterBasedSubspace { + /// PCC subspace header. + pub header: PccSubspaceHeader, + /// Must be 0x0001 (Version 1 of this PCC definition). + pub version: u16, + pub(super) base_address: u64, + pub(super) mem_len: u64, + pub(super) doorbell_reg: RawGenericAddress, + pub(super) doorbell_preserve: u64, + pub(super) doorbell_write: u64, + cmd_complete_check_reg: RawGenericAddress, + cmd_complete_check_mask: u64, + err_status_reg: RawGenericAddress, + err_status_mask: u64, + /// Expected latency to process a command, in microseconds. + pub nominal_latency: u32, + /// The minimum amount of time that OSPM must wait after + /// the completion of a command before issuing the next command, + /// in microseconds. + pub min_request_turnaround_time: u16, +} + +impl PccHwRegisterBasedSubspace { + /// Get information about the command complete check register. + pub fn cmd_complete_check(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.cmd_complete_check_reg)?; + Ok(PccCmdCompleteCheck { addr, mask: self.cmd_complete_check_mask }) + } + + /// Get information about the error status register. + pub fn error_status(&self) -> AcpiResult { + let addr = GenericAddress::from_raw(self.err_status_reg)?; + Ok(PccErrorStatus { addr, mask: self.err_status_mask }) + } +} + +/// Shared memory region header for [`PccHwRegisterBasedSubspace`]. +/// +/// See section "14.4. Reduced PCC Subspace Shared Memory Region" of +/// the ACPI spec for more information. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PccReducedShmem { + /// The PCC signature. The signature of a subspace is computed b + /// a bitwise-or of the value `0x50434300` with the subspace ID. + /// For example, subspace 3 has the signature `0x50434303`. + pub signature: u32, +} + +impl PccShmemHdr for PccReducedShmem { + fn signature(&self) -> u32 { + self.signature + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem::offset_of; + + #[test] + fn pcc_hw_register_based_subspace_offsets() { + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, base_address), 4); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, mem_len), 12); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_reg), 20); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_preserve), 32); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_write), 40); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, cmd_complete_check_reg), 48); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, cmd_complete_check_mask), 60); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, err_status_reg), 68); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, err_status_mask), 80); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, nominal_latency), 88); + assert_eq!(offset_of!(PccHwRegisterBasedSubspace, min_request_turnaround_time), 92); + } +}