use std::{string::String, vec::Vec};
use super::AcpiBuildError;
const COMMAND_ALLOCATE: u32 = 1;
const COMMAND_ADD_POINTER: u32 = 2;
const COMMAND_ADD_CHECKSUM: u32 = 3;
const ENTRY_SIZE: usize = 128;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum LoaderZone {
High = 1,
Fseg = 2,
}
enum LoaderCommand {
Allocate {
file: String,
alignment: u32,
zone: LoaderZone,
},
AddPointer {
pointer_file: String,
pointee_file: String,
pointer_offset: u32,
pointer_size: u8,
},
AddChecksum {
file: String,
checksum_offset: u32,
start: u32,
length: u32,
},
}
#[derive(Default)]
pub(crate) struct AcpiLoaderPlan {
commands: Vec<LoaderCommand>,
}
impl AcpiLoaderPlan {
pub(crate) const fn new() -> Self {
Self {
commands: Vec::new(),
}
}
pub(crate) fn allocate(
&mut self,
file: &str,
alignment: u32,
zone: LoaderZone,
) -> Result<(), AcpiBuildError> {
validate_file(file)?;
if !alignment.is_power_of_two() {
return Err(AcpiBuildError::InvalidAlignment {
object: file.into(),
alignment: alignment as usize,
});
}
self.commands.push(LoaderCommand::Allocate {
file: file.into(),
alignment,
zone,
});
Ok(())
}
pub(crate) fn add_pointer(
&mut self,
pointer_file: &str,
pointee_file: &str,
pointer_offset: u32,
pointer_size: u8,
) -> Result<(), AcpiBuildError> {
validate_file(pointer_file)?;
validate_file(pointee_file)?;
if !matches!(pointer_size, 1 | 2 | 4 | 8) {
return Err(AcpiBuildError::InvalidValue {
field: "table-loader pointer size",
value: std::format!("{pointer_size}"),
});
}
self.commands.push(LoaderCommand::AddPointer {
pointer_file: pointer_file.into(),
pointee_file: pointee_file.into(),
pointer_offset,
pointer_size,
});
Ok(())
}
pub(crate) fn add_checksum(
&mut self,
file: &str,
checksum_offset: u32,
start: u32,
length: u32,
) -> Result<(), AcpiBuildError> {
validate_file(file)?;
self.commands.push(LoaderCommand::AddChecksum {
file: file.into(),
checksum_offset,
start,
length,
});
Ok(())
}
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(self.commands.len() * ENTRY_SIZE);
for command in &self.commands {
let mut entry = [0u8; ENTRY_SIZE];
match command {
LoaderCommand::Allocate {
file,
alignment,
zone,
} => {
entry[0..4].copy_from_slice(&COMMAND_ALLOCATE.to_le_bytes());
write_file(&mut entry[4..60], file);
entry[60..64].copy_from_slice(&alignment.to_le_bytes());
entry[64] = *zone as u8;
}
LoaderCommand::AddPointer {
pointer_file,
pointee_file,
pointer_offset,
pointer_size,
} => {
entry[0..4].copy_from_slice(&COMMAND_ADD_POINTER.to_le_bytes());
write_file(&mut entry[4..60], pointer_file);
write_file(&mut entry[60..116], pointee_file);
entry[116..120].copy_from_slice(&pointer_offset.to_le_bytes());
entry[120] = *pointer_size;
}
LoaderCommand::AddChecksum {
file,
checksum_offset,
start,
length,
} => {
entry[0..4].copy_from_slice(&COMMAND_ADD_CHECKSUM.to_le_bytes());
write_file(&mut entry[4..60], file);
entry[60..64].copy_from_slice(&checksum_offset.to_le_bytes());
entry[64..68].copy_from_slice(&start.to_le_bytes());
entry[68..72].copy_from_slice(&length.to_le_bytes());
}
}
bytes.extend_from_slice(&entry);
}
bytes
}
}
fn validate_file(file: &str) -> Result<(), AcpiBuildError> {
if file.is_empty() || file.len() >= 56 {
return Err(AcpiBuildError::InvalidLoaderFile { name: file.into() });
}
Ok(())
}
fn write_file(destination: &mut [u8], file: &str) {
destination[..file.len()].copy_from_slice(file.as_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serializes_allocate_pointer_and_checksum_command_layouts() {
let mut plan = AcpiLoaderPlan::new();
plan.allocate("etc/acpi/tables", 64, LoaderZone::High)
.unwrap();
plan.add_pointer("etc/acpi/rsdp", "etc/acpi/tables", 24, 8)
.unwrap();
plan.add_checksum("etc/acpi/rsdp", 8, 0, 20).unwrap();
let bytes = plan.serialize();
assert_eq!(bytes.len(), 3 * ENTRY_SIZE);
let allocate = &bytes[..ENTRY_SIZE];
assert_eq!(&allocate[0..4], &COMMAND_ALLOCATE.to_le_bytes());
assert_file_field(&allocate[4..60], "etc/acpi/tables");
assert_eq!(&allocate[60..64], &64u32.to_le_bytes());
assert_eq!(allocate[64], LoaderZone::High as u8);
assert!(allocate[65..].iter().all(|byte| *byte == 0));
let pointer = &bytes[ENTRY_SIZE..2 * ENTRY_SIZE];
assert_eq!(&pointer[0..4], &COMMAND_ADD_POINTER.to_le_bytes());
assert_file_field(&pointer[4..60], "etc/acpi/rsdp");
assert_file_field(&pointer[60..116], "etc/acpi/tables");
assert_eq!(&pointer[116..120], &24u32.to_le_bytes());
assert_eq!(pointer[120], 8);
assert!(pointer[121..].iter().all(|byte| *byte == 0));
let checksum = &bytes[2 * ENTRY_SIZE..];
assert_eq!(&checksum[0..4], &COMMAND_ADD_CHECKSUM.to_le_bytes());
assert_file_field(&checksum[4..60], "etc/acpi/rsdp");
assert_eq!(&checksum[60..64], &8u32.to_le_bytes());
assert_eq!(&checksum[64..68], &0u32.to_le_bytes());
assert_eq!(&checksum[68..72], &20u32.to_le_bytes());
assert!(checksum[72..].iter().all(|byte| *byte == 0));
}
fn assert_file_field(field: &[u8], expected: &str) {
assert_eq!(&field[..expected.len()], expected.as_bytes());
assert!(field[expected.len()..].iter().all(|byte| *byte == 0));
}
}