Skip to content

Commit 57a415a

Browse files
authored
Merge pull request #55 from barkera/acpi_hpet
Add HPET ACPI struct.
2 parents ec202ff + d74490d commit 57a415a

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed

mythril_core/src/acpi/hpet.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use super::rsdt::SDT;
2+
use super::GenericAddressStructure;
3+
use crate::error::{Error, Result};
4+
use byteorder::{ByteOrder, NativeEndian};
5+
use core::fmt;
6+
use core::ops::Range;
7+
use derive_try_from_primitive::TryFromPrimitive;
8+
9+
mod offsets {
10+
use super::*;
11+
pub const EVENT_TIMER_BLOCK_ID: Range<usize> = 0..4;
12+
pub const BASE_ADDRESS: Range<usize> = 4..16;
13+
pub const HPET_NUMBER: usize = 16;
14+
pub const MIN_CLOCK_TICK: Range<usize> = 17..19;
15+
pub const PAGE_PROTECTION: usize = 19;
16+
}
17+
18+
/// Page Protection for HPET register access.
19+
///
20+
/// See Table 3 in the IA-PC HPET specification.
21+
#[repr(u8)]
22+
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
23+
pub enum PageProtection {
24+
/// No page protection
25+
NoProtection = 0x00,
26+
/// Register access is protected by a 4KB page
27+
Protected4KB = 0x01,
28+
/// Register access is protected by a 64KB page
29+
Protected64KB = 0x02,
30+
}
31+
32+
/// High Precision Event Timer ACPI entry.
33+
///
34+
/// See `IA-PC HPET § 1.0a`.
35+
pub struct HPET<'a> {
36+
/// System Descriptor Table Header for this structure.
37+
sdt: &'a SDT<'a>,
38+
/// The hardware revision ID.
39+
pub hardware_rev_id: u8,
40+
/// The number of comparators in the first timer block.
41+
pub comparator_count: u8,
42+
/// The size cap of the counter. If false, then only 32 bit mode is allowed.
43+
/// If true, then both 32 bit and 64 bit modes are supported.
44+
pub counter_cap: bool,
45+
/// If true, then the HPET is LegacyReplacement IRQ routing capable.
46+
pub legacy_replacement: bool,
47+
/// The PCI vendor ID of the first timer block.
48+
pub pci_vendor_id: u16,
49+
/// The address of the HPET register stored as an ACPI GAS.
50+
pub address: GenericAddressStructure,
51+
/// The HPET sequence number.
52+
pub hpet_number: u8,
53+
/// The minimum number of ticks that must be used by any counter programmed
54+
/// in periodic mode to avoid lost interrupts.
55+
pub minimum_tick: u16,
56+
/// The type of page protection for HPET register access.
57+
pub page_protection: PageProtection,
58+
}
59+
60+
impl<'a> HPET<'a> {
61+
/// Create a new HPET given a SDT.
62+
pub fn new(sdt: &'a SDT<'a>) -> Result<HPET<'a>> {
63+
let event_timer_block_id =
64+
NativeEndian::read_u32(&sdt.table[offsets::EVENT_TIMER_BLOCK_ID]);
65+
let address =
66+
GenericAddressStructure::new(&sdt.table[offsets::BASE_ADDRESS])?;
67+
let hpet_number = sdt.table[offsets::HPET_NUMBER];
68+
let minimum_tick =
69+
NativeEndian::read_u16(&sdt.table[offsets::MIN_CLOCK_TICK]);
70+
71+
let page_protection =
72+
PageProtection::try_from(sdt.table[offsets::PAGE_PROTECTION] & 0xF)
73+
.ok_or_else(|| {
74+
Error::InvalidValue(format!(
75+
"Invalid HPET Page Protection type: {}",
76+
sdt.table[offsets::PAGE_PROTECTION] & 0xF
77+
))
78+
})?;
79+
80+
let hardware_rev_id = (event_timer_block_id & 0xFF) as u8;
81+
let comparator_count = ((event_timer_block_id >> 8) & 0x1F) as u8;
82+
let counter_cap = ((event_timer_block_id >> 13) & 0x1) as u8;
83+
let legacy_replacement = ((event_timer_block_id >> 15) & 0x1) as u8;
84+
let pci_vendor_id = ((event_timer_block_id >> 16) & 0xFFFF) as u16;
85+
86+
let counter_cap = counter_cap != 0;
87+
let legacy_replacement = legacy_replacement != 0;
88+
89+
Ok(Self {
90+
sdt,
91+
hardware_rev_id,
92+
comparator_count,
93+
counter_cap,
94+
legacy_replacement,
95+
pci_vendor_id,
96+
address,
97+
hpet_number,
98+
minimum_tick,
99+
page_protection,
100+
})
101+
}
102+
}
103+
104+
impl<'a> fmt::Debug for HPET<'a> {
105+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106+
write!(f, "{:?}", self.sdt)?;
107+
write!(f, " HPET address=0x{:x}", self.address.address)
108+
}
109+
}
110+
111+
#[cfg(test)]
112+
mod test {
113+
use super::*;
114+
use crate::acpi::{AccessSize, AddressSpaceID};
115+
116+
#[test]
117+
fn test_hpet_parse() {
118+
// sample HPET ACPI entry
119+
let buf = [
120+
0x48, 0x50, 0x45, 0x54, 0x38, 0x00, 0x00, 0x00, 0x01, 0xb6, 0x41,
121+
0x4c, 0x41, 0x53, 0x4b, 0x41, 0x41, 0x20, 0x4d, 0x20, 0x49, 0x00,
122+
0x00, 0x00, 0x09, 0x20, 0x07, 0x01, 0x41, 0x4d, 0x49, 0x2e, 0x05,
123+
0x00, 0x00, 0x00, 0x01, 0xa7, 0x86, 0x80, 0x00, 0x40, 0x00, 0x00,
124+
0x00, 0x00, 0xd0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xee, 0x37,
125+
0x00,
126+
];
127+
128+
let hpet_sdt = unsafe { SDT::new(buf.as_ptr()).unwrap() };
129+
let hpet = HPET::new(&hpet_sdt).unwrap();
130+
131+
assert_eq!(hpet.hardware_rev_id, 1);
132+
assert_eq!(hpet.comparator_count, 7);
133+
assert_eq!(hpet.counter_cap, true);
134+
assert_eq!(hpet.legacy_replacement, true);
135+
assert_eq!(hpet.pci_vendor_id, 0x8086);
136+
assert_eq!(hpet.address.address_space, AddressSpaceID::SystemMemory);
137+
assert_eq!(hpet.address.bit_width, 64);
138+
assert_eq!(hpet.address.bit_offset, 0);
139+
assert_eq!(hpet.address.access_size, AccessSize::Undefined);
140+
assert_eq!(hpet.address.address, 0xfed00000);
141+
assert_eq!(hpet.hpet_number, 0);
142+
assert_eq!(hpet.minimum_tick, 0x37ee);
143+
assert_eq!(hpet.page_protection, PageProtection::NoProtection);
144+
}
145+
}

mythril_core/src/acpi/mod.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,29 @@
99
//! [ACPI 6.3]: https://uefi.org/sites/default/files/resources/ACPI_6_3_May16.pdf
1010
1111
use crate::error::{Error, Result};
12+
use byteorder::{ByteOrder, NativeEndian};
13+
use derive_try_from_primitive::TryFromPrimitive;
14+
use raw_cpuid::CpuId;
1215

16+
/// Support for the High Precision Event Timer (HPET)
17+
pub mod hpet;
1318
/// Support for the Multiple APIC Descriptor Table (MADT).
1419
pub mod madt;
1520
/// Support for the Root System Descriptor Pointer (RSDP).
1621
pub mod rsdp;
1722
/// Support for the Root System Descriptor Table (RSDT).
1823
pub mod rsdt;
1924

25+
mod offsets {
26+
use core::ops::Range;
27+
28+
pub const GAS_ADDRESS_SPACE: usize = 0;
29+
pub const GAS_BIT_WIDTH: usize = 1;
30+
pub const GAS_BIT_OFFSET: usize = 2;
31+
pub const GAS_ACCESS_SIZE: usize = 3;
32+
pub const GAS_ADDRESS: Range<usize> = 4..12;
33+
}
34+
2035
/// Verify a one byte checksum for a given slice and length.
2136
pub(self) fn verify_checksum(bytes: &[u8], cksum_idx: usize) -> Result<()> {
2237
// Sum up the bytes in the buffer.
@@ -34,3 +49,136 @@ pub(self) fn verify_checksum(bytes: &[u8], cksum_idx: usize) -> Result<()> {
3449
)))
3550
}
3651
}
52+
53+
/// The size of a Generic Address Structure in bytes.
54+
pub const GAS_SIZE: usize = 12;
55+
56+
/// Generic Address Structure (GAS) used by ACPI for position of registers.
57+
///
58+
/// See Table 5-25 in ACPI specification.
59+
#[derive(Debug)]
60+
pub struct GenericAddressStructure {
61+
/// The address space where the associated address exists.
62+
pub address_space: AddressSpaceID,
63+
/// The size in bits of the given register.
64+
pub bit_width: u8,
65+
/// The bit offset of the given register at the given address.
66+
pub bit_offset: u8,
67+
/// The size of the memory access for the given address.
68+
pub access_size: AccessSize,
69+
/// The 64-bit address of the register or data structure.
70+
pub address: u64,
71+
}
72+
73+
/// Where a given address pointed to by a GAS resides.
74+
///
75+
/// See Table 5-25 of the ACPI specification.
76+
#[repr(u8)]
77+
#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
78+
pub enum AddressSpaceID {
79+
/// The associated address exists in the System Memory space.
80+
SystemMemory = 0x00,
81+
/// The associated address exists in the System I/O space.
82+
SystemIO = 0x01,
83+
/// The associated address exists in the PCI Configuration space.
84+
PCIConfiguration = 0x02,
85+
/// The associated address exists in an Embedded Controller.
86+
EmbeddedController = 0x03,
87+
/// The associated address exists in the SMBus.
88+
SMBus = 0x04,
89+
/// The associated address exists in the SystemCMOS.
90+
SystemCMOS = 0x05,
91+
/// The associated address exists in a PCI Bar Target.
92+
PciBarTarget = 0x06,
93+
/// The associated address exists in an IPMI.
94+
IPMI = 0x07,
95+
/// The associated address exists in General Purpose I/O.
96+
GPIO = 0x08,
97+
/// The associated address exists in a Generic Serial Bus.
98+
GenericSerialBus = 0x09,
99+
/// The associated address exists in the Platform Communications Channel (PCC).
100+
PlatformCommunicationsChannel = 0x0A,
101+
/// The associated address exists in Functional Fixed Hardware.
102+
FunctionalFixedHardware = 0x7F,
103+
}
104+
105+
/// Specifies access size of an address in a GAS.
106+
///
107+
/// See Table 5-25 in the ACPI specification.
108+
#[repr(u8)]
109+
#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
110+
pub enum AccessSize {
111+
/// Undefined (legacy reasons).
112+
Undefined = 0x00,
113+
/// Byte access.
114+
Byte = 0x01,
115+
/// Word access.
116+
Word = 0x02,
117+
/// DWord access.
118+
DWord = 0x03,
119+
/// QWord access.
120+
QWord = 0x04,
121+
}
122+
123+
impl GenericAddressStructure {
124+
/// Create a new GAS from a slice of bytes.
125+
pub fn new(bytes: &[u8]) -> Result<GenericAddressStructure> {
126+
if bytes.len() != GAS_SIZE {
127+
return Err(Error::InvalidValue(format!(
128+
"Invalid number of bytes for GAS: {} != {}",
129+
bytes.len(),
130+
GAS_SIZE
131+
)));
132+
}
133+
134+
let address_space =
135+
AddressSpaceID::try_from(bytes[offsets::GAS_ADDRESS_SPACE])
136+
.ok_or_else(|| {
137+
Error::InvalidValue(format!(
138+
"Invalid Address Space ID: {}",
139+
bytes[offsets::GAS_ADDRESS_SPACE]
140+
))
141+
})?;
142+
143+
let bit_width = bytes[offsets::GAS_BIT_WIDTH];
144+
let bit_offset = bytes[offsets::GAS_BIT_OFFSET];
145+
146+
let access_size = AccessSize::try_from(bytes[offsets::GAS_ACCESS_SIZE])
147+
.ok_or_else(|| {
148+
Error::InvalidValue(format!(
149+
"Invalid Access Size: {}",
150+
bytes[offsets::GAS_ACCESS_SIZE]
151+
))
152+
})?;
153+
154+
let address = NativeEndian::read_u64(&bytes[offsets::GAS_ADDRESS]);
155+
156+
if address_space == AddressSpaceID::SystemMemory
157+
|| address_space == AddressSpaceID::SystemIO
158+
{
159+
// call CPUID to determine if we need to verify the address. If the
160+
// call to CPUID fails, the check is not performed.
161+
let cpuid = CpuId::new();
162+
let is_64bit = cpuid
163+
.get_extended_function_info()
164+
.and_then(|x| Some(x.has_64bit_mode()))
165+
.ok_or_else(|| Error::NotSupported)?;
166+
167+
// verify that the address is only 32 bits for 32-bit platforms.
168+
if !is_64bit && ((address >> 32) & 0xFFFFFFFF) != 0 {
169+
return Err(Error::InvalidValue(format!(
170+
"Invalid address for a 32-bit system: {:x}",
171+
address
172+
)));
173+
}
174+
}
175+
176+
Ok(Self {
177+
address_space,
178+
bit_width,
179+
bit_offset,
180+
access_size,
181+
address,
182+
})
183+
}
184+
}

mythril_multiboot2/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ pub extern "C" fn kmain(multiboot_info_addr: usize) -> ! {
266266
let madt_sdt = rsdt.find_entry(b"APIC").expect("No MADT found");
267267
let madt = acpi::madt::MADT::new(&madt_sdt);
268268

269+
let hpet_sdt = rsdt.find_entry(b"HPET").expect("No HPET found");
270+
let hpet = acpi::hpet::HPET::new(&hpet_sdt)
271+
.unwrap_or_else(|e| panic!("Failed to create the HPET: {:?}", e));
272+
273+
info!("{:?}", hpet);
274+
269275
let apic_ids = madt
270276
.structures()
271277
.filter_map(|ics| match ics {

0 commit comments

Comments
 (0)