Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b8b10b0

Browse files
committedNov 25, 2024·
acpi: add support for PCC table
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.
1 parent 5d37bb3 commit b8b10b0

File tree

7 files changed

+893
-0
lines changed

7 files changed

+893
-0
lines changed
 

‎acpi/src/address.rs

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub enum AddressSpace {
4545
Ipmi,
4646
GeneralIo,
4747
GenericSerialBus,
48+
/// Describes a subspace in the Platform Communications Channel Table ([PCCT](crate::pcct::Pcct)).
4849
PlatformCommunicationsChannel,
4950
FunctionalFixedHardware,
5051
OemDefined(u8),

‎acpi/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub mod handler;
7070
pub mod hpet;
7171
pub mod madt;
7272
pub mod mcfg;
73+
pub mod pcct;
7374
pub mod rsdp;
7475
pub mod sdt;
7576
pub mod spcr;
@@ -139,6 +140,10 @@ pub enum AcpiError {
139140
InvalidMadt(MadtError),
140141
InvalidGenericAddress,
141142

143+
/// The signature for a PCC subspace shared memory region did not
144+
/// match.
145+
PccInvalidShmemSignature(u32),
146+
142147
AllocError,
143148
}
144149

‎acpi/src/pcct/extended.rs

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use super::{PccCmdCompleteCheck, PccPlatformInterruptFlags, PccShmemHdr, PccSubspaceHeader, RawGenericAddress};
2+
use crate::{address::GenericAddress, AcpiResult};
3+
use bitflags::bitflags;
4+
5+
/// Extended PCC communication subspace.
6+
///
7+
/// The subspace might be a master or slave subspace, depending on
8+
/// its type (3 and 4 respectively). The type can be inspected in the
9+
/// subspace header or via this type's methods
10+
/// ([`is_master()`](Self::is_master) and [`is_slave()`](Self::is_slave)).
11+
///
12+
/// * Master subspaces are used by the OSPM to communicate with the
13+
/// platform.
14+
/// * Slave subspaces are used by the platform to send asynchronous
15+
/// notifications to the OSPM.
16+
///
17+
/// See section "14.1.6. Extended PCC subspaces (types 3 and 4)" of
18+
/// the ACPI spec for more information.
19+
#[repr(C, packed)]
20+
#[derive(Clone, Copy, Debug)]
21+
pub struct PccExtendedSubspace {
22+
/// PCC subspace header.
23+
pub header: PccSubspaceHeader,
24+
/// GSIV of the interrupt used for the PCC platform interrupt for
25+
/// this subspace.
26+
pub plat_interrupt: u32,
27+
/// Flags for the platform interrupt for this subspace.
28+
pub plat_interrupt_flags: PccPlatformInterruptFlags,
29+
_rsvd: u8,
30+
pub(super) base_address: u64,
31+
pub(super) mem_len: u32,
32+
pub(super) doorbell_reg: RawGenericAddress,
33+
pub(super) doorbell_preserve: u64,
34+
pub(super) doorbell_write: u64,
35+
/// Expected latency to process a command, in microseconds.
36+
pub nominal_latency: u32,
37+
/// The maximum number of periodic requests that the subspace
38+
/// channel can support, reported in commands per minute. 0
39+
/// indicates no limitation.
40+
pub max_periodic_access_rate: u32,
41+
/// The minimum amount of time that OSPM must wait after
42+
/// the completion of a command before issuing the next command,
43+
/// in microseconds.
44+
pub min_request_turnaround_time: u32,
45+
plat_interrupt_ack_reg: RawGenericAddress,
46+
plat_interrupt_ack_preserve: u64,
47+
plat_interrupt_ack_write: u64,
48+
_rsvd2: [u8; 8],
49+
cmd_complete_check_reg: RawGenericAddress,
50+
cmd_complete_check_mask: u64,
51+
cmd_complete_update_reg: RawGenericAddress,
52+
cmd_complete_update_preserve: u64,
53+
cmd_complete_update_write: u64,
54+
err_status_reg: RawGenericAddress,
55+
err_status_mask: u64,
56+
}
57+
58+
impl PccExtendedSubspace {
59+
/// Returns `true` if this is a master subspace.
60+
pub fn is_master(&self) -> bool {
61+
self.header.stype == 3
62+
}
63+
64+
/// Returns `true` if this is a slave subspace.
65+
pub fn is_slave(&self) -> bool {
66+
self.header.stype == 4
67+
}
68+
69+
/// Get information about the platform interrupt ACK mechanism.
70+
pub fn platform_interrupt_ack(&self) -> AcpiResult<PccPlatformInterruptAck> {
71+
let addr = GenericAddress::from_raw(self.plat_interrupt_ack_reg)?;
72+
Ok(PccPlatformInterruptAck {
73+
addr,
74+
preserve: self.plat_interrupt_ack_preserve,
75+
write: self.plat_interrupt_ack_write,
76+
})
77+
}
78+
79+
/// Get information about the command complete check register.
80+
pub fn cmd_complete_check(&self) -> AcpiResult<PccCmdCompleteCheck> {
81+
let addr = GenericAddress::from_raw(self.cmd_complete_check_reg)?;
82+
Ok(PccCmdCompleteCheck { addr, mask: self.cmd_complete_check_mask })
83+
}
84+
85+
/// Get information about the command complete update register.
86+
pub fn cmd_complete_update(&self) -> AcpiResult<PccCmdCompleteUpdate> {
87+
let addr = GenericAddress::from_raw(self.cmd_complete_update_reg)?;
88+
Ok(PccCmdCompleteUpdate {
89+
addr,
90+
preserve: self.cmd_complete_update_preserve,
91+
write: self.cmd_complete_update_write,
92+
})
93+
}
94+
95+
/// Get information about the error status register.
96+
pub fn error_status(&self) -> AcpiResult<PccErrorStatus> {
97+
let addr = GenericAddress::from_raw(self.err_status_reg)?;
98+
Ok(PccErrorStatus { addr, mask: self.err_status_mask })
99+
}
100+
}
101+
102+
/// Information about the command complete update register.
103+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104+
pub struct PccCmdCompleteUpdate {
105+
/// Location of the register.
106+
pub addr: GenericAddress,
107+
/// Mask of bits to preserve in the command complete update
108+
/// register, when updating command complete in this subspace.
109+
pub preserve: u64,
110+
/// Mask of bits to set in the command complete update register,
111+
/// when updating command complete in this subspace. For master
112+
/// subspaces the mask must indicate how to clear the command
113+
/// complete bit. For slave subspaces, the mask must indicate how
114+
/// to set the command complete bit.
115+
pub write: u64,
116+
}
117+
118+
/// Platform interrupt ACK information.
119+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120+
pub struct PccPlatformInterruptAck {
121+
/// Location of the register.
122+
pub addr: GenericAddress,
123+
/// Bits to preserve when writing the register.
124+
pub preserve: u64,
125+
/// Bits to set when writing the register.
126+
pub write: u64,
127+
}
128+
129+
/// Information about the error status register.
130+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131+
pub struct PccErrorStatus {
132+
/// Location of the register.
133+
pub addr: GenericAddress,
134+
/// The mask contained here can be combined through a logical AND
135+
/// with content of the Error status register to ascertain whether
136+
/// an error occurred in the transmission of the command through
137+
/// the subspace. The logical NOT of this mask is be used to clear
138+
/// the error. The inverted mask is combined through a logical AND
139+
/// with the content of the Error status register, and the result
140+
/// is written back into said register. This field is ignored for
141+
/// slave channels.
142+
pub mask: u64,
143+
}
144+
145+
/// Shared memory region header for [`PccExtendedSubspace`].
146+
///
147+
/// See section "14.3. Extended PCC Subspace Shared Memory Region" of
148+
/// the ACPI spec for more information.
149+
#[repr(C, packed)]
150+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151+
pub struct PccExtendedShmem {
152+
/// The PCC signature. The signature of a subspace is computed by
153+
/// a bitwise-or of the value `0x50434300` with the subspace ID.
154+
/// For example, subspace 3 has the signature `0x50434303`.
155+
pub signature: u32,
156+
/// Flags for doorbell behavior on this shared region.
157+
pub flags: PccExtendedShmemFlags,
158+
/// Length of payload being transmitted including command field.
159+
pub len: u32,
160+
/// Command being sent over the subspace.
161+
pub cmd: u32,
162+
}
163+
164+
impl PccShmemHdr for PccExtendedShmem {
165+
fn signature(&self) -> u32 {
166+
self.signature
167+
}
168+
}
169+
170+
bitflags! {
171+
/// Flags for [`PccExtendedShmem`].
172+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173+
pub struct PccExtendedShmemFlags: u32 {
174+
/// **For master subspaces** this field indicates to the
175+
/// platform that it must generate an interrupt when the
176+
/// command has completed. - Setting this bit to 1 when
177+
/// sending a command, requests that completion of the command
178+
/// is signaled via the platform interrupt. - Setting it to 0
179+
/// when sending a command, requests that no interrupt is
180+
/// asserted when the command is completed.
181+
///
182+
/// **For slave subspaces**, if the doorbell field of the
183+
/// slave subspace is non zero, and this flag is set, the
184+
/// OSPM must access the doorbell once it has processed the
185+
/// notification. This bit is ignored by the platform if the
186+
/// [Platform Interrupt field](PcctFlags::INTERRUPT) of the
187+
/// [PCC flags](Pcct::flags) is set to zero.
188+
const NOTIFY_ON_COMPLETION = 1;
189+
}
190+
}
191+
192+
#[cfg(test)]
193+
mod test {
194+
use super::*;
195+
use core::mem::offset_of;
196+
197+
#[test]
198+
fn pcc_extended_subspace_offsets() {
199+
assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt), 2);
200+
assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_flags), 6);
201+
assert_eq!(offset_of!(PccExtendedSubspace, base_address), 8);
202+
assert_eq!(offset_of!(PccExtendedSubspace, mem_len), 16);
203+
assert_eq!(offset_of!(PccExtendedSubspace, doorbell_reg), 20);
204+
assert_eq!(offset_of!(PccExtendedSubspace, doorbell_preserve), 32);
205+
assert_eq!(offset_of!(PccExtendedSubspace, doorbell_write), 40);
206+
assert_eq!(offset_of!(PccExtendedSubspace, nominal_latency), 48);
207+
assert_eq!(offset_of!(PccExtendedSubspace, max_periodic_access_rate), 52);
208+
assert_eq!(offset_of!(PccExtendedSubspace, min_request_turnaround_time), 56);
209+
assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_reg), 60);
210+
assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_preserve), 72);
211+
assert_eq!(offset_of!(PccExtendedSubspace, plat_interrupt_ack_write), 80);
212+
assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_check_reg), 96);
213+
assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_check_mask), 108);
214+
assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_reg), 116);
215+
assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_preserve), 128);
216+
assert_eq!(offset_of!(PccExtendedSubspace, cmd_complete_update_write), 136);
217+
assert_eq!(offset_of!(PccExtendedSubspace, err_status_reg), 144);
218+
assert_eq!(offset_of!(PccExtendedSubspace, err_status_mask), 156);
219+
}
220+
}

‎acpi/src/pcct/generic.rs

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use super::{PccShmemHdr, PccSubspaceHeader, RawGenericAddress};
2+
use bitflags::bitflags;
3+
4+
/// Generic PCC communication subspace.
5+
///
6+
/// See section "14.1.3. Generic Communications Subspace Structure
7+
/// (type 0)" of the ACPI spec for more information.
8+
#[repr(C, packed)]
9+
#[derive(Clone, Copy, Debug)]
10+
pub struct PccGenericSubspace {
11+
/// PCC subspace header.
12+
pub header: PccSubspaceHeader,
13+
_rsvd: [u8; 6],
14+
pub(super) base_address: u64,
15+
pub(super) mem_len: u64,
16+
pub(super) doorbell_reg: RawGenericAddress,
17+
pub(super) doorbell_preserve: u64,
18+
pub(super) doorbell_write: u64,
19+
/// Expected latency to process a command, in microseconds.
20+
pub nominal_latency: u32,
21+
/// The maximum number of periodic requests that the subspace
22+
/// channel can support, reported in commands per minute. 0
23+
/// indicates no limitation.
24+
pub max_periodic_access_rate: u32,
25+
/// The minimum amount of time that OSPM must wait after
26+
/// the completion of a command before issuing the next command,
27+
/// in microseconds.
28+
pub min_request_turnaround_time: u16,
29+
}
30+
31+
/// Shared memory region header for [`PccGenericSubspace`].
32+
///
33+
/// See section "14.2. Generic Communications Channel Shared Memory
34+
/// Region" of the ACPI spec for more information.
35+
#[repr(C, packed)]
36+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37+
pub struct PccGenericShmem {
38+
/// The PCC signature. The signature of a subspace is computed b
39+
/// a bitwise-or of the value `0x50434300` with the subspace ID.
40+
/// For example, subspace 3 has the signature `0x50434303`.
41+
pub signature: u32,
42+
/// Commands for the platform to perform.
43+
pub cmd: PccGenericShmemCmd,
44+
/// Command processing status.
45+
pub status: PccGenericShmemStatus,
46+
}
47+
48+
impl PccShmemHdr for PccGenericShmem {
49+
fn signature(&self) -> u32 {
50+
self.signature
51+
}
52+
}
53+
54+
/// Command field for [`PccGenericShmem`].
55+
///
56+
/// For [`PccGenericSubspace`],
57+
/// [`PccHwReducedSubspace1`](super::PccHwReducedSubspace1) and
58+
/// [`PccHwReducedSubspace2`](super::PccHwReducedSubspace2), this
59+
/// 16-bit field is used to select one of the defined commands for
60+
/// the platform to perform. OSPM is esponsible for populating this
61+
/// field before each command invocation.
62+
///
63+
/// See section "14.2.1. Generic Communications Channel Command
64+
/// Field" of the ACPI spec for more information.
65+
#[repr(C, packed)]
66+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67+
pub struct PccGenericShmemCmd {
68+
/// Command code to execute. Command codes are application
69+
/// specific and defined by the consumer of this interface.
70+
pub cmd: u8,
71+
/// Additional bitfields for the command field.
72+
pub flags: PccShmemCmdFlags,
73+
}
74+
75+
bitflags! {
76+
/// Flags for [`PccGenericShmemCmd`].
77+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78+
pub struct PccShmemCmdFlags: u8 {
79+
/// If set, the platform should generate a Doorbell interrupt
80+
/// at the completion of this command. The interrupt is an
81+
/// SCI for a [`PccGenericSubspace`], or as described by
82+
/// the [Doorbell Interrupt field](PccHwReducedSubspace1::plat_interrupt)
83+
/// for [`PccHwReducedSubspace1`] and
84+
/// [`PccHwReducedSubspace2`]. If the
85+
/// [Doorbell bit](PcctFlags::PLATFORM_INTERRUPT) is not set
86+
/// in the [PCC global flags](Pcct::flags), this bit must be
87+
/// cleared.
88+
const NOTIFY_ON_COMPLETION = 1 << 7;
89+
}
90+
}
91+
92+
bitflags! {
93+
/// Status flags for [`PccGenericShmem`].
94+
///
95+
/// See section "14.2.2. Generic Communications Channel Status
96+
/// Field" of the ACPI spec for more information.
97+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98+
pub struct PccGenericShmemStatus: u16 {
99+
/// If set, the platform has completed processing the last
100+
/// command.
101+
const CMD_COMPLETE = 0b1;
102+
/// If set, the platform has issued a Platform Interrupt to
103+
/// this subspace. OSPM must check the
104+
/// [`CMD_COMPLETE`](Self::CMD_COMPLETE) and
105+
/// [`PLATFORM_NOTIFICATION`](Self::PLATFORM_NOTIFICATION)
106+
/// fields to determine the cause of the Interrupt.
107+
const PLATFORM_INTERRUPT = 1 << 1;
108+
/// If set, an error occurred executing the last command.
109+
const ERROR = 1 << 2;
110+
/// If set, indicates the platform is issuing an asynchronous
111+
/// notification to OSPM.
112+
const PLATFORM_NOTIFICATION = 1 << 3;
113+
}
114+
}
115+
#[cfg(test)]
116+
mod test {
117+
use super::*;
118+
use core::mem::offset_of;
119+
120+
#[test]
121+
fn pcc_generic_subspace_offsets() {
122+
assert_eq!(offset_of!(PccGenericSubspace, base_address), 8);
123+
assert_eq!(offset_of!(PccGenericSubspace, mem_len), 16);
124+
assert_eq!(offset_of!(PccGenericSubspace, doorbell_reg), 24);
125+
assert_eq!(offset_of!(PccGenericSubspace, doorbell_preserve), 36);
126+
assert_eq!(offset_of!(PccGenericSubspace, doorbell_write), 44);
127+
assert_eq!(offset_of!(PccGenericSubspace, nominal_latency), 52);
128+
assert_eq!(offset_of!(PccGenericSubspace, max_periodic_access_rate), 56);
129+
assert_eq!(offset_of!(PccGenericSubspace, min_request_turnaround_time), 60);
130+
}
131+
}

‎acpi/src/pcct/mod.rs

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
use crate::{
2+
address::{AddressSpace, GenericAddress, RawGenericAddress},
3+
sdt::{SdtHeader, Signature},
4+
AcpiError,
5+
AcpiHandler,
6+
AcpiResult,
7+
AcpiTable,
8+
PhysicalMapping,
9+
};
10+
use bitflags::bitflags;
11+
use core::{marker::PhantomData, ptr::NonNull};
12+
13+
/// Generic subspace structures (type 0).
14+
pub mod generic;
15+
use generic::*;
16+
17+
/// HW-reduced subspace structures (types 1 and 2).
18+
pub mod reduced;
19+
use reduced::*;
20+
21+
/// Extended PCC subspace structures (types 3 and 4).
22+
pub mod extended;
23+
use extended::*;
24+
25+
/// HW-registers-based subspace structures (type 5).
26+
pub mod register_based;
27+
use register_based::*;
28+
29+
bitflags! {
30+
/// Global flags for the [PCCT](Pcct).
31+
///
32+
/// See section "14.1.1. Platform Communications Channel Global
33+
/// Flags" of the ACPI spec for more information.
34+
#[derive(Clone, Copy, Debug)]
35+
pub struct PcctFlags: u32 {
36+
/// If set, the platform is capable of generating an
37+
/// interrupt to indicate completion of a command.
38+
const PLATFORM_INTERRUPT = 0b1;
39+
}
40+
}
41+
42+
/// Platform Communications Channel Table (PCCT).
43+
#[repr(C, packed)]
44+
#[derive(Clone, Copy, Debug)]
45+
pub struct Pcct {
46+
header: SdtHeader,
47+
/// Global flags for the PCCT.
48+
pub flags: PcctFlags,
49+
_rsvd: [u8; 8],
50+
}
51+
52+
unsafe impl AcpiTable for Pcct {
53+
const SIGNATURE: Signature = Signature::PCCT;
54+
55+
fn header(&self) -> &SdtHeader {
56+
&self.header
57+
}
58+
}
59+
60+
impl Pcct {
61+
/// Returns an iterator over the PCC subspaces.
62+
pub const fn entries(&self) -> PcctEntryIter {
63+
// SAFETY: this is the layout specified by the ACPI spec.
64+
// (14.1. Platform Communications Channel Table)
65+
let ptr = unsafe { core::ptr::from_ref(self).add(1).cast() };
66+
let len = (self.header.length as usize).saturating_sub(size_of::<Self>());
67+
PcctEntryIter { ptr, num: 0, len, _phantom: PhantomData }
68+
}
69+
70+
/// Gets the appropriate subspace for the given address and its
71+
/// position in the table. Returns an error if the address does
72+
/// not belong to the PCC address space.
73+
pub fn get_subspace(&self, addr: GenericAddress) -> AcpiResult<(u8, PcctEntry)> {
74+
if addr.address_space != AddressSpace::PlatformCommunicationsChannel {
75+
return Err(AcpiError::InvalidGenericAddress);
76+
}
77+
let idx = addr.access_size;
78+
let entry = self.entries().nth(idx as usize).ok_or(AcpiError::InvalidGenericAddress)?;
79+
Ok((idx, entry))
80+
}
81+
}
82+
83+
/// An iterator over the PCC subspaces in the [PCCT](Pcct).
84+
#[derive(Clone, Debug)]
85+
pub struct PcctEntryIter<'a> {
86+
ptr: *const u8,
87+
len: usize,
88+
num: usize,
89+
_phantom: PhantomData<&'a ()>,
90+
}
91+
92+
impl<'a> Iterator for PcctEntryIter<'a> {
93+
type Item = PcctEntry<'a>;
94+
95+
fn next(&mut self) -> Option<Self::Item> {
96+
// Stop after we exceed the total length or we iterate over
97+
// the maximum number of entries.
98+
if self.len == 0 || self.num >= 256 {
99+
return None;
100+
}
101+
102+
// SAFETY: we keep track of each entry we parse, so the
103+
// pointer must be valid.
104+
let entry = unsafe { Self::Item::from_ptr(self.ptr)? };
105+
let entry_len = entry.header().len as usize;
106+
107+
self.num += 1;
108+
self.ptr = self.ptr.wrapping_add(entry_len);
109+
self.len = self.len.saturating_sub(entry_len);
110+
Some(entry)
111+
}
112+
}
113+
114+
/// Common header for all PCC subspaces.
115+
///
116+
/// See section "14.1.2. Platform Communications Channel Subspace
117+
/// Structures" of the ACPI spec for more information.
118+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119+
#[repr(C, packed)]
120+
pub struct PccSubspaceHeader {
121+
pub stype: u8,
122+
pub len: u8,
123+
}
124+
125+
/// Information about the command complete check register for a PCC
126+
/// subspace.
127+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128+
pub struct PccCmdCompleteCheck {
129+
/// Location of the register.
130+
pub addr: GenericAddress,
131+
/// Mask to determine whether a command is complete, using the
132+
/// command complete check register. A command is complete if the
133+
/// value of the register when combined through a logical AND with
134+
/// this mask, yields a non-zero value
135+
pub mask: u64,
136+
}
137+
138+
/// Information about the Doorbell register a PCC subspace.
139+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140+
pub struct PccDoorbell {
141+
/// The address of the doorbell
142+
pub addr: GenericAddress,
143+
/// Mask of bits to preserve when writing the doorbell register.
144+
pub preserve: u64,
145+
/// Mask of bits to set when writing the doorbell register.
146+
pub write: u64,
147+
}
148+
149+
/// A generic shared memory region for a PCC subspace.
150+
///
151+
/// The region is formed by a header (`T`) and a variable-length area
152+
/// [`PccShmem::shmem`].
153+
#[derive(Debug)]
154+
pub struct PccShmem<H: AcpiHandler, T: PccShmemHdr> {
155+
/// Mapping for the shared memory region header.
156+
pub header: PhysicalMapping<H, T>,
157+
/// Communication space. The pointer is only valid while the
158+
/// header mapping is alive.
159+
pub shmem: NonNull<[u8]>,
160+
}
161+
162+
impl<H: AcpiHandler, T: PccShmemHdr> PccShmem<H, T> {
163+
/// Verify that the signature for the shared memory region matches.
164+
pub fn verify_signature(&self, subspace_num: u8) -> AcpiResult<()> {
165+
if self.header.signature() != 0x50434300 | (subspace_num as u32) {
166+
return Err(AcpiError::PccInvalidShmemSignature(self.header.signature()));
167+
}
168+
Ok(())
169+
}
170+
}
171+
172+
/// Trait implemented by all PCC subspaces. It allows access to the
173+
/// subspace's doorbell and shared memory region.
174+
pub trait PccSubspace {
175+
/// The header for this PCC subspace's shared memory region.
176+
type ShmemHdr: PccShmemHdr;
177+
178+
/// Map the shared memory region for this subspace.
179+
///
180+
/// Each PCC subspace has an address and length that points to a
181+
/// shared memory region for communications ([`PccShmem`]). This
182+
/// region is formed by a header (whose format is determined by
183+
/// the type of the subspace - [`Self::ShmemHdr`]) and a
184+
/// variable-length region (whose format is determined by the
185+
/// subsystem using this subspace).
186+
fn map_shmem<H: AcpiHandler>(&self, h: &H) -> Option<PccShmem<H, Self::ShmemHdr>>;
187+
188+
/// Get information about the doorbell for this subspace, if set.
189+
fn doorbell(&self) -> Option<AcpiResult<PccDoorbell>>;
190+
}
191+
192+
/// Trait implemented by all shared memory region headers.
193+
pub trait PccShmemHdr {
194+
/// The signature for this header.
195+
fn signature(&self) -> u32;
196+
}
197+
198+
/// Prepare an enum with all possible PCC subspace entries, plus some
199+
/// catch-all implementations.
200+
macro_rules! pcct_subentries {
201+
([
202+
$(
203+
($ty:ty, [$($id:expr),+], $variant:ident, $shhdr:ty)
204+
),+ $(,)?
205+
]) => {
206+
/// An entry in the PCCT, corresponding to a subspace of a
207+
/// specific type.
208+
#[derive(Clone, Copy, Debug)]
209+
pub enum PcctEntry<'a> {
210+
$(
211+
$variant(&'a $ty),
212+
)+
213+
}
214+
215+
impl PcctEntry<'_> {
216+
/// The PCC subspace header for this entry.
217+
pub const fn header(&self) -> &PccSubspaceHeader {
218+
match self {
219+
$(
220+
Self::$variant(v) => &v.header,
221+
)+
222+
}
223+
}
224+
225+
/// Information about the doorbell for this subspace, if
226+
/// set.
227+
pub fn doorbell(&self) -> Option<AcpiResult<PccDoorbell>> {
228+
match self {
229+
$(
230+
Self::$variant(v) => v.doorbell(),
231+
)+
232+
}
233+
}
234+
235+
/// Read a PCC subspace entry based on its type. Returns
236+
/// [`None`] if an unknown entry type is found.
237+
///
238+
/// # Safety
239+
///
240+
/// The caller must provide a pointer to the beginning
241+
/// of a PCC subspace structure.
242+
const unsafe fn from_ptr(ptr: *const u8) -> Option<Self> {
243+
// SAFETY: the caller must ensure we are given a
244+
// valid pointer. The header is packed, so it has no
245+
// alignment requirements.
246+
let header = unsafe { &*ptr.cast::<PccSubspaceHeader>() };
247+
match header.stype {
248+
$(
249+
$(
250+
// SAFETY: we checked the entry type above.
251+
$id => Some(Self::$variant(unsafe { &*ptr.cast() })),
252+
)+
253+
)+
254+
_ => None,
255+
}
256+
}
257+
}
258+
259+
$(
260+
impl PccSubspace for $ty {
261+
type ShmemHdr = $shhdr;
262+
263+
fn doorbell(&self) -> Option<AcpiResult<PccDoorbell>> {
264+
if self.doorbell_reg.is_empty() {
265+
return None;
266+
}
267+
Some(GenericAddress::from_raw(self.doorbell_reg).map(|addr| PccDoorbell {
268+
addr,
269+
write: self.doorbell_write,
270+
preserve: self.doorbell_preserve,
271+
}))
272+
}
273+
274+
fn map_shmem<H: AcpiHandler>(
275+
&self,
276+
h: &H,
277+
) -> Option<PccShmem<H, Self::ShmemHdr>> {
278+
if self.mem_len == 0 {
279+
return None;
280+
}
281+
282+
// SAFETY: we trust the base address and length
283+
// provided by the PCCT.
284+
let header = unsafe {
285+
h.map_physical_region::<$shhdr>(
286+
self.base_address as usize,
287+
self.mem_len as usize,
288+
)
289+
};
290+
291+
// SAFETY: this is the layout specified in the
292+
// ACPI spec. The area right after the header is
293+
// the communication space.
294+
// The length takes into account the size of the
295+
// header itself.
296+
let shmem_raw = unsafe {
297+
header.virtual_start().add(1).cast::<u8>()
298+
};
299+
let shmem_len = self.mem_len as usize - size_of::<$shhdr>();
300+
let shmem = NonNull::slice_from_raw_parts(shmem_raw, shmem_len);
301+
Some(PccShmem { header, shmem })
302+
}
303+
}
304+
)*
305+
};
306+
}
307+
308+
pcct_subentries!([
309+
(PccGenericSubspace, [0], Generic, PccGenericShmem),
310+
(PccHwReducedSubspace1, [1], HwReduced1, PccGenericShmem),
311+
(PccHwReducedSubspace2, [2], HwReduced2, PccGenericShmem),
312+
(PccExtendedSubspace, [3, 4], Extended, PccExtendedShmem),
313+
(PccHwRegisterBasedSubspace, [5], HwRegisterBased, PccReducedShmem),
314+
]);
315+
316+
#[cfg(test)]
317+
mod test {
318+
use super::*;
319+
use core::mem::offset_of;
320+
}

‎acpi/src/pcct/reduced.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use super::{PccPlatformInterruptAck, PccSubspaceHeader, RawGenericAddress};
2+
use crate::{address::GenericAddress, AcpiResult};
3+
use bitflags::bitflags;
4+
5+
/// HW-reduced PCC communications subspace (type 1).
6+
///
7+
/// See section "14.1.4. HW-Reduced Communications Subspace Structure
8+
/// (type 1)" of the ACPI spec for more information.
9+
#[repr(C, packed)]
10+
#[derive(Clone, Copy, Debug)]
11+
pub struct PccHwReducedSubspace1 {
12+
/// PCC subspace header.
13+
pub header: PccSubspaceHeader,
14+
/// GSIV of the interrupt used for the PCC platform interrupt for
15+
/// this subspace.
16+
pub plat_interrupt: u32,
17+
/// Flags for the platform interrupt for this subspace.
18+
pub plat_interrupt_flags: PccPlatformInterruptFlags,
19+
_rsvd: u8,
20+
pub(super) base_address: u64,
21+
pub(super) mem_len: u64,
22+
pub(super) doorbell_reg: RawGenericAddress,
23+
pub(super) doorbell_preserve: u64,
24+
pub(super) doorbell_write: u64,
25+
/// Expected latency to process a command, in microseconds.
26+
pub nominal_latency: u32,
27+
/// The maximum number of periodic requests that the subspace
28+
/// channel can support, reported in commands per minute. 0
29+
/// indicates no limitation.
30+
pub max_periodic_access_rate: u32,
31+
/// The minimum amount of time that OSPM must wait after
32+
/// the completion of a command before issuing the next command,
33+
/// in microseconds.
34+
pub min_request_turnaround_time: u16,
35+
}
36+
37+
/// HW-reduced PCC communications subspace (type 2).
38+
///
39+
/// See section "14.1.5. HW-Reduced Communications Subspace Structure
40+
/// (type 2)" of the ACPI spec for more information.
41+
#[repr(C, packed)]
42+
#[derive(Clone, Copy, Debug)]
43+
pub struct PccHwReducedSubspace2 {
44+
/// PCC subspace header.
45+
pub header: PccSubspaceHeader,
46+
/// GSIV of the interrupt used for the PCC platform interrupt for
47+
/// this subspace.
48+
pub plat_interrupt: u32,
49+
/// Flags for the platform interrupt for this subspace.
50+
pub plat_interrupt_flags: PccPlatformInterruptFlags,
51+
_rsvd: u8,
52+
pub(super) base_address: u64,
53+
pub(super) mem_len: u64,
54+
pub(super) doorbell_reg: RawGenericAddress,
55+
pub(super) doorbell_preserve: u64,
56+
pub(super) doorbell_write: u64,
57+
/// Expected latency to process a command, in microseconds.
58+
pub nominal_latency: u32,
59+
/// The maximum number of periodic requests that the subspace
60+
/// channel can support, reported in commands per minute. 0
61+
/// indicates no limitation.
62+
pub max_periodic_access_rate: u32,
63+
/// The minimum amount of time that OSPM must wait after
64+
/// the completion of a command before issuing the next command,
65+
/// in microseconds.
66+
pub min_request_turnaround_time: u16,
67+
pub(super) plat_interrupt_ack_reg: RawGenericAddress,
68+
pub(super) plat_interrupt_ack_preserve: u64,
69+
pub(super) plat_interrupt_ack_write: u64,
70+
}
71+
72+
impl PccHwReducedSubspace2 {
73+
/// Get information about the platform interrupt ACK mechanism.
74+
pub fn platform_interrupt_ack(&self) -> AcpiResult<PccPlatformInterruptAck> {
75+
let addr = GenericAddress::from_raw(self.plat_interrupt_ack_reg)?;
76+
Ok(PccPlatformInterruptAck {
77+
addr,
78+
preserve: self.plat_interrupt_ack_preserve,
79+
write: self.plat_interrupt_ack_write,
80+
})
81+
}
82+
}
83+
84+
bitflags! {
85+
/// Interrupt flags for [`PccHwReducedSubspace1`] and
86+
/// [`PccHwReducedSubspace2`].
87+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88+
pub struct PccPlatformInterruptFlags: u8 {
89+
/// * 1: Interrupt is Active low.
90+
/// * 0: Interrupt is Active high.
91+
const INTERRUPT_POLARITY = 1 << 0;
92+
/// * 1: Interrupt is Edge triggered.
93+
/// * 0: Interrupt is Level triggered.
94+
const INTERRUPT_MODE = 1 << 1;
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod test {
100+
use super::*;
101+
use core::mem::offset_of;
102+
103+
#[test]
104+
fn pcc_hw_reduced_subspace_1_offsets() {
105+
assert_eq!(offset_of!(PccHwReducedSubspace1, plat_interrupt), 2);
106+
assert_eq!(offset_of!(PccHwReducedSubspace1, plat_interrupt_flags), 6);
107+
assert_eq!(offset_of!(PccHwReducedSubspace1, base_address), 8);
108+
assert_eq!(offset_of!(PccHwReducedSubspace1, mem_len), 16);
109+
assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_reg), 24);
110+
assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_preserve), 36);
111+
assert_eq!(offset_of!(PccHwReducedSubspace1, doorbell_write), 44);
112+
assert_eq!(offset_of!(PccHwReducedSubspace1, nominal_latency), 52);
113+
assert_eq!(offset_of!(PccHwReducedSubspace1, max_periodic_access_rate), 56);
114+
assert_eq!(offset_of!(PccHwReducedSubspace1, min_request_turnaround_time), 60);
115+
}
116+
117+
#[test]
118+
fn pcc_hw_reduced_subspace_2_offsets() {
119+
assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt), 2);
120+
assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_flags), 6);
121+
assert_eq!(offset_of!(PccHwReducedSubspace2, base_address), 8);
122+
assert_eq!(offset_of!(PccHwReducedSubspace2, mem_len), 16);
123+
assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_reg), 24);
124+
assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_preserve), 36);
125+
assert_eq!(offset_of!(PccHwReducedSubspace2, doorbell_write), 44);
126+
assert_eq!(offset_of!(PccHwReducedSubspace2, nominal_latency), 52);
127+
assert_eq!(offset_of!(PccHwReducedSubspace2, max_periodic_access_rate), 56);
128+
assert_eq!(offset_of!(PccHwReducedSubspace2, min_request_turnaround_time), 60);
129+
assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_reg), 62);
130+
assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_preserve), 74);
131+
assert_eq!(offset_of!(PccHwReducedSubspace2, plat_interrupt_ack_write), 82);
132+
}
133+
}

‎acpi/src/pcct/register_based.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use crate::{address::GenericAddress, AcpiResult};
2+
3+
use super::{PccCmdCompleteCheck, PccErrorStatus, PccShmemHdr, PccSubspaceHeader, RawGenericAddress};
4+
5+
/// See section "14.1.7. HW Registers based Communications Subspace
6+
/// Structure (Type 5)" of the ACPI spec for more information.
7+
#[repr(C, packed)]
8+
#[derive(Clone, Copy, Debug)]
9+
pub struct PccHwRegisterBasedSubspace {
10+
/// PCC subspace header.
11+
pub header: PccSubspaceHeader,
12+
/// Must be 0x0001 (Version 1 of this PCC definition).
13+
pub version: u16,
14+
pub(super) base_address: u64,
15+
pub(super) mem_len: u64,
16+
pub(super) doorbell_reg: RawGenericAddress,
17+
pub(super) doorbell_preserve: u64,
18+
pub(super) doorbell_write: u64,
19+
cmd_complete_check_reg: RawGenericAddress,
20+
cmd_complete_check_mask: u64,
21+
err_status_reg: RawGenericAddress,
22+
err_status_mask: u64,
23+
/// Expected latency to process a command, in microseconds.
24+
pub nominal_latency: u32,
25+
/// The minimum amount of time that OSPM must wait after
26+
/// the completion of a command before issuing the next command,
27+
/// in microseconds.
28+
pub min_request_turnaround_time: u16,
29+
}
30+
31+
impl PccHwRegisterBasedSubspace {
32+
/// Get information about the command complete check register.
33+
pub fn cmd_complete_check(&self) -> AcpiResult<PccCmdCompleteCheck> {
34+
let addr = GenericAddress::from_raw(self.cmd_complete_check_reg)?;
35+
Ok(PccCmdCompleteCheck { addr, mask: self.cmd_complete_check_mask })
36+
}
37+
38+
/// Get information about the error status register.
39+
pub fn error_status(&self) -> AcpiResult<PccErrorStatus> {
40+
let addr = GenericAddress::from_raw(self.err_status_reg)?;
41+
Ok(PccErrorStatus { addr, mask: self.err_status_mask })
42+
}
43+
}
44+
45+
/// Shared memory region header for [`PccHwRegisterBasedSubspace`].
46+
///
47+
/// See section "14.4. Reduced PCC Subspace Shared Memory Region" of
48+
/// the ACPI spec for more information.
49+
#[repr(C, packed)]
50+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51+
pub struct PccReducedShmem {
52+
/// The PCC signature. The signature of a subspace is computed b
53+
/// a bitwise-or of the value `0x50434300` with the subspace ID.
54+
/// For example, subspace 3 has the signature `0x50434303`.
55+
pub signature: u32,
56+
}
57+
58+
impl PccShmemHdr for PccReducedShmem {
59+
fn signature(&self) -> u32 {
60+
self.signature
61+
}
62+
}
63+
64+
#[cfg(test)]
65+
mod test {
66+
use super::*;
67+
use core::mem::offset_of;
68+
69+
#[test]
70+
fn pcc_hw_register_based_subspace_offsets() {
71+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, base_address), 4);
72+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, mem_len), 12);
73+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_reg), 20);
74+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_preserve), 32);
75+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, doorbell_write), 40);
76+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, cmd_complete_check_reg), 48);
77+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, cmd_complete_check_mask), 60);
78+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, err_status_reg), 68);
79+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, err_status_mask), 80);
80+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, nominal_latency), 88);
81+
assert_eq!(offset_of!(PccHwRegisterBasedSubspace, min_request_turnaround_time), 92);
82+
}
83+
}

0 commit comments

Comments
 (0)
Please sign in to comment.