use crate::arch::x86_64::mm::paging::{BasePageSize, PageSize, PageTableEntryFlags};
use crate::arch::x86_64::mm::{paging, virtualmem};
use crate::x86::io::*;
use core::{mem, slice, str};
const EBDA_PTR_LOCATION: usize = 0x0000_040E;
const EBDA_MINIMUM_ADDRESS: usize = 0x400;
const EBDA_WINDOW_SIZE: usize = 1024;
const RSDP_SEARCH_ADDRESS_LOW: usize = 0xE_0000;
const RSDP_SEARCH_ADDRESS_HIGH: usize = 0xF_FFFF;
const RSDP_CHECKSUM_LENGTH: usize = 20;
const RSDP_XCHECKSUM_LENGTH: usize = 36;
const AML_NAMEOP: u8 = 0x08;
const AML_PACKAGEOP: u8 = 0x12;
const AML_ZEROOP: u8 = 0x00;
const AML_ONEOP: u8 = 0x01;
const AML_BYTEPREFIX: u8 = 0x0A;
const SLP_EN: u16 = 1 << 13;
static mut MADT: Option<AcpiTable<'_>> = None;
static mut PM1A_CNT_BLK: Option<u16> = None;
static mut SLP_TYPA: Option<u8> = None;
#[repr(C, packed)]
struct AcpiRsdp {
signature: [u8; 8],
checksum: u8,
oem_id: [u8; 6],
revision: u8,
rsdt_physical_address: u32,
length: u32,
xsdt_physical_address: u64,
extended_checksum: u8,
reserved: [u8; 3],
}
impl AcpiRsdp {
fn oem_id(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.oem_id) }
}
fn signature(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.signature) }
}
}
#[repr(C, packed)]
struct AcpiSdtHeader {
signature: [u8; 4],
length: u32,
revision: u8,
checksum: u8,
oem_id: [u8; 6],
oem_table_id: [u8; 8],
oem_revision: u32,
creator_id: u32,
creator_revision: u32,
}
impl AcpiSdtHeader {
fn signature(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.signature) }
}
}
pub struct AcpiTable<'a> {
header: &'a AcpiSdtHeader,
allocated_virtual_address: usize,
allocated_length: usize,
}
impl<'a> AcpiTable<'a> {
fn map(physical_address: usize) -> Self {
let mut flags = PageTableEntryFlags::empty();
flags.normal().read_only().execute_disable();
let mut allocated_length = 2 * BasePageSize::SIZE;
let mut count = allocated_length / BasePageSize::SIZE;
let physical_map_address = align_down!(physical_address, BasePageSize::SIZE);
let offset = physical_address - physical_map_address;
let mut virtual_address = virtualmem::allocate(allocated_length).unwrap();
paging::map::<BasePageSize>(virtual_address, physical_map_address, count, flags);
let mut header_ptr = (virtual_address + offset) as *const AcpiSdtHeader;
let table_length = unsafe { (*header_ptr).length } as usize;
if table_length > allocated_length - offset {
virtualmem::deallocate(virtual_address, allocated_length);
allocated_length = align_up!(table_length + offset, BasePageSize::SIZE);
count = allocated_length / BasePageSize::SIZE;
virtual_address = virtualmem::allocate(allocated_length).unwrap();
paging::map::<BasePageSize>(virtual_address, physical_map_address, count, flags);
header_ptr = (virtual_address + offset) as *const AcpiSdtHeader;
}
Self {
header: unsafe { &*header_ptr },
allocated_virtual_address: virtual_address,
allocated_length,
}
}
pub fn header_start_address(&self) -> usize {
self.header as *const _ as usize
}
pub fn table_start_address(&self) -> usize {
self.header_start_address() + mem::size_of::<AcpiSdtHeader>()
}
pub fn table_end_address(&self) -> usize {
self.header_start_address() + self.header.length as usize
}
}
impl<'a> Drop for AcpiTable<'a> {
fn drop(&mut self) {
virtualmem::deallocate(self.allocated_virtual_address, self.allocated_length);
}
}
#[repr(C, packed)]
struct AcpiGenericAddress {
address_space: u8,
bit_width: u8,
bit_offset: u8,
access_size: u8,
address: u64,
}
const GENERIC_ADDRESS_IO_SPACE: u8 = 1;
#[repr(C, packed)]
struct AcpiFadt {
firmware_ctrl: u32,
dsdt: u32,
reserved1: u8,
preferred_pm_profile: u8,
sci_int: u16,
smi_cmd: u32,
acpi_enable: u8,
acpi_disable: u8,
s4bios_req: u8,
pstate_cnt: u8,
pm1a_evt_blk: u32,
pm1b_evt_blk: u32,
pm1a_cnt_blk: u32,
pm1b_cnt_blk: u32,
pm2_cnt_blk: u32,
pm_tmr_blk: u32,
gpe0_blk: u32,
gpe1_blk: u32,
pm1_evt_len: u8,
pm1_cnt_len: u8,
pm2_cnt_len: u8,
pm_tmr_len: u8,
gpe0_blk_len: u8,
gpe1_blk_len: u8,
gpe1_base: u8,
cst_cnt: u8,
p_lvl2_lat: u16,
p_lvl3_lat: u16,
flush_size: u16,
flush_stride: u16,
duty_offset: u8,
duty_width: u8,
day_alrm: u8,
mon_alrm: u8,
century: u8,
iapc_boot_arch: u16,
reserved2: u8,
flags: u32,
reset_reg: AcpiGenericAddress,
reset_value: u8,
arm_boot_arch: u16,
fadt_minor_version: u8,
x_firmware_ctrl: u64,
x_dsdt: u64,
x_pm1a_evt_blk: AcpiGenericAddress,
x_pm1b_evt_blk: AcpiGenericAddress,
x_pm1a_cnt_blk: AcpiGenericAddress,
x_pm1b_cnt_blk: AcpiGenericAddress,
x_pm2_cnt_blk: AcpiGenericAddress,
x_pm_tmr_blk: AcpiGenericAddress,
x_gpe0_blk: AcpiGenericAddress,
x_gpe1_blk: AcpiGenericAddress,
sleep_control_reg: AcpiGenericAddress,
sleep_status_reg: AcpiGenericAddress,
hypervisor_vendor_id: u64,
}
fn verify_checksum(start_address: usize, length: usize) -> Result<(), ()> {
let slice = unsafe { slice::from_raw_parts(start_address as *const u8, length) };
let checksum = slice.iter().fold(0, |acc: u8, x| acc.wrapping_add(*x));
if checksum == 0 {
Ok(())
} else {
Err(())
}
}
fn detect_rsdp(start_address: usize, end_address: usize) -> Result<&'static AcpiRsdp, ()> {
let mut current_page = 0;
for current_address in (start_address..end_address).step_by(16) {
if current_address / BasePageSize::SIZE > current_page {
paging::identity_map(current_address, current_address);
current_page = current_address / BasePageSize::SIZE;
}
let rsdp = unsafe { &*(current_address as *const AcpiRsdp) };
if rsdp.signature() != "RSD PTR " {
continue;
}
if verify_checksum(current_address, RSDP_CHECKSUM_LENGTH).is_err() {
debug!(
"Found an ACPI table at {:#X}, but its RSDP checksum is invalid",
current_address
);
continue;
}
if rsdp.revision >= 2 && verify_checksum(current_address, RSDP_XCHECKSUM_LENGTH).is_err() {
debug!(
"Found an ACPI table at {:#X}, but its RSDP extended checksum is invalid",
current_address
);
continue;
}
info!(
"Found an ACPI revision {} table at {:#X} with OEM ID \"{}\"",
rsdp.revision,
current_address,
rsdp.oem_id()
);
return Ok(rsdp);
}
Err(())
}
fn detect_acpi() -> Result<&'static AcpiRsdp, ()> {
paging::identity_map(EBDA_PTR_LOCATION, EBDA_PTR_LOCATION);
let ebda_ptr_location = unsafe { &*(EBDA_PTR_LOCATION as *const u16) };
let ebda_address = (*ebda_ptr_location as usize) << 4;
if ebda_address > EBDA_MINIMUM_ADDRESS {
if let Ok(rsdp) = detect_rsdp(ebda_address, ebda_address + EBDA_WINDOW_SIZE) {
return Ok(rsdp);
}
}
if let Ok(rsdp) = detect_rsdp(RSDP_SEARCH_ADDRESS_LOW, RSDP_SEARCH_ADDRESS_HIGH) {
return Ok(rsdp);
}
Err(())
}
fn search_s5_in_table(table: AcpiTable<'_>) {
let aml = unsafe {
slice::from_raw_parts(
table.table_start_address() as *const u8,
table.table_end_address() - table.table_start_address(),
)
};
let s5 = [b'_', b'S', b'5', b'_', AML_PACKAGEOP];
let s5_position = aml.windows(s5.len()).position(|window| window == s5);
if let Some(i) = s5_position {
if i > 2 && (aml[i - 1] == AML_NAMEOP || (aml[i - 2] == AML_NAMEOP && aml[i - 1] == b'\\'))
{
let pkg_length = aml[i + 5];
let num_elements = aml[i + 6];
if pkg_length & 0b1100_0000 == 0 && num_elements > 0 {
let op = aml[i + 7];
let slp_typa;
match op {
AML_ZEROOP => slp_typa = 0,
AML_ONEOP => slp_typa = 1,
AML_BYTEPREFIX => slp_typa = aml[i + 8],
_ => return,
}
unsafe {
SLP_TYPA = Some(slp_typa);
}
}
}
}
}
fn parse_fadt(fadt: AcpiTable<'_>) {
let fadt_table = unsafe { &*(fadt.table_start_address() as *const AcpiFadt) };
let x_pm1a_cnt_blk_field_address = &fadt_table.x_pm1a_cnt_blk as *const _ as usize;
let pm1a_cnt_blk = if x_pm1a_cnt_blk_field_address < fadt.table_end_address()
&& fadt_table.x_pm1a_cnt_blk.address_space == GENERIC_ADDRESS_IO_SPACE
{
fadt_table.x_pm1a_cnt_blk.address as u16
} else {
fadt_table.pm1a_cnt_blk as u16
};
unsafe {
PM1A_CNT_BLK = Some(pm1a_cnt_blk);
}
let x_dsdt_field_address = unsafe { &fadt_table.x_dsdt as *const _ as usize };
let dsdt_address = if x_dsdt_field_address < fadt.table_end_address() && fadt_table.x_dsdt > 0 {
fadt_table.x_dsdt as usize
} else {
fadt_table.dsdt as usize
};
let dsdt = AcpiTable::map(dsdt_address);
assert!(
dsdt.header.signature() == "DSDT",
"DSDT at {:#X} has invalid signature \"{}\"",
dsdt_address,
dsdt.header.signature()
);
assert!(
verify_checksum(dsdt.header_start_address(), dsdt.header.length as usize).is_ok(),
"DSDT at {:#X} has invalid checksum",
dsdt_address
);
search_s5_in_table(dsdt);
}
fn parse_ssdt(ssdt: AcpiTable<'_>) {
if unsafe { SLP_TYPA }.is_some() {
return;
}
search_s5_in_table(ssdt);
}
pub fn get_madt() -> Option<&'static AcpiTable<'static>> {
unsafe { MADT.as_ref() }
}
pub fn poweroff() {
unsafe {
if let (Some(pm1a_cnt_blk), Some(slp_typa)) = (PM1A_CNT_BLK, SLP_TYPA) {
let bits = (u16::from(slp_typa) << 10) | SLP_EN;
debug!(
"Powering Off through ACPI (port {:#X}, bitmask {:#X})",
pm1a_cnt_blk, bits
);
outw(pm1a_cnt_blk, bits);
} else {
debug!("ACPI Power Off is not available");
}
}
}
pub fn init() {
let rsdp = detect_acpi().expect("HermitCore requires an ACPI-compliant system");
let rsdt_physical_address = if rsdp.revision >= 2 {
rsdp.xsdt_physical_address as usize
} else {
rsdp.rsdt_physical_address as usize
};
let rsdt = AcpiTable::map(rsdt_physical_address);
let mut current_address = rsdt.table_start_address();
while current_address < rsdt.table_end_address() {
let table_physical_address = if rsdp.revision >= 2 {
let address = unsafe { *(current_address as *const u64) } as usize;
current_address += mem::size_of::<u64>();
address
} else {
let address = unsafe { *(current_address as *const u32) } as usize;
current_address += mem::size_of::<u32>();
address
};
let table = AcpiTable::map(table_physical_address);
debug!("Found ACPI table: {}", table.header.signature());
if table.header.signature() == "APIC" {
assert!(
verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(),
"MADT at {:#X} has invalid checksum",
table_physical_address
);
unsafe {
MADT = Some(table);
}
} else if table.header.signature() == "FACP" {
assert!(
verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(),
"FADT at {:#X} has invalid checksum",
table_physical_address
);
parse_fadt(table);
} else if table.header.signature() == "SSDT" {
assert!(
verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(),
"SSDT at {:#X} has invalid checksum",
table_physical_address
);
parse_ssdt(table);
}
}
}