Skip to content

Commit

Permalink
acpi: add support for PCC table
Browse files Browse the repository at this point in the history
The PCCT (Platform Communications Channel Table) is a generic
mechanism for OSPM to communicate with an entity in the platform (e.g.
a platform controller, or a Baseboard Management Controller (BMC)).

The table provides access to a new namespace, to which Generic Address
Structures (GAS) in other tables can refer to. Each entry in the PCCT
is an addressable subspace, which provides the address and length of a
shared memory region that can be used for communication with the
platform. Subspaces may also have a doorbell register to notify the
platform of certain events, as well as additional registers.
  • Loading branch information
00xc committed Nov 25, 2024
1 parent 5d37bb3 commit b217e2f
Show file tree
Hide file tree
Showing 7 changed files with 894 additions and 0 deletions.
1 change: 1 addition & 0 deletions acpi/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
5 changes: 5 additions & 0 deletions acpi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}

Expand Down
220 changes: 220 additions & 0 deletions acpi/src/pcct/extended.rs
Original file line number Diff line number Diff line change
@@ -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<PccPlatformInterruptAck> {
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<PccCmdCompleteCheck> {
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<PccCmdCompleteUpdate> {
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<PccErrorStatus> {
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);
}
}
131 changes: 131 additions & 0 deletions acpi/src/pcct/generic.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit b217e2f

Please sign in to comment.