diff --git a/alioth-cli/src/boot/boot.rs b/alioth-cli/src/boot/boot.rs index 64907718..7103a1ec 100644 --- a/alioth-cli/src/boot/boot.rs +++ b/alioth-cli/src/boot/boot.rs @@ -18,6 +18,8 @@ use std::collections::HashMap; use std::mem; use std::path::{Path, PathBuf}; +#[cfg(target_arch = "aarch64")] +use alioth::board::PlatformSpec; use alioth::board::{BoardSpec, CpuSpec}; use alioth::device::console::ConsoleSpec; #[cfg(target_arch = "x86_64")] @@ -122,6 +124,12 @@ pub struct BootArgs { #[arg(long)] pvpanic: bool, + #[cfg(target_arch = "aarch64")] + #[arg(long, help( + help_text::("Configure the platform of the guest.") + ))] + platform: Option>, + #[cfg(target_arch = "x86_64")] #[arg(long, help( help_text::("Add an extra item to the fw_cfg device.") @@ -294,6 +302,11 @@ fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result AcpiTableHeader { + AcpiTableHeader { + checksum: 0, + oem_id: OEM_ID, + oem_table_id: *b"ALIOTHVM", + oem_revision: 1, + asl_compiler_id: *b"ALTH", + asl_compiler_revision: 1, + ..Default::default() + } +} + #[trace_error] #[derive(Snafu, DebugTrace)] #[snafu(module, context(suffix(false)))] @@ -143,11 +164,21 @@ impl CpuSpec { pub const PCIE_MMIO_64_SIZE: u64 = 1 << 40; +#[derive(Debug, Default, PartialEq, Eq, Deserialize, Help)] +pub struct PlatformSpec { + /// Enable ACPI for the guest. + #[cfg(target_arch = "aarch64")] + #[serde(default)] + pub acpi: bool, +} + #[derive(Debug, Default, PartialEq, Eq, Deserialize)] pub struct BoardSpec { pub mem: MemSpec, pub cpu: CpuSpec, pub coco: Option, + #[serde(default)] + pub platform: PlatformSpec, } impl BoardSpec { @@ -318,6 +349,39 @@ where } Ok(pages) } + + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure + fn create_rsdp(&self, xsdt_addr: u64) -> AcpiTableRsdp { + AcpiTableRsdp { + signature: SIG_RSDP, + oem_id: OEM_ID, + revision: RSDP_REVISION, + length: size_of::() as u32, + xsdt_physical_address: transmute!(xsdt_addr), + ..Default::default() + } + } + + fn create_mcfg(&self) -> AcpiTableMcfg1 { + let mut mcfg = AcpiTableMcfg1 { + header: AcpiTableHeader { + signature: AcpiSignature::MCFG, + length: size_of::() as u32, + revision: MCFG_REVISION, + ..default_acpi_header() + }, + reserved: [0; 8], + allocations: [AcpiMcfgAllocation { + address: transmute!(PCIE_CONFIG_START), + pci_segment: 0, + start_bus_number: 0, + end_bus_number: 0, + ..Default::default() + }], + }; + mcfg.header.checksum = 0u8.wrapping_sub(wrapping_sum(mcfg.as_bytes())); + mcfg + } } #[cfg(test)] diff --git a/alioth/src/board/board_amd64/board_amd64.rs b/alioth/src/board/board_amd64/board_amd64.rs index 41a010ca..292918e2 100644 --- a/alioth/src/board/board_amd64/board_amd64.rs +++ b/alioth/src/board/board_amd64/board_amd64.rs @@ -23,22 +23,25 @@ use std::sync::atomic::{AtomicU32, AtomicU64}; use parking_lot::Mutex; use snafu::ResultExt; -use zerocopy::{FromZeros, IntoBytes}; +use zerocopy::{FromZeros, IntoBytes, transmute}; use crate::arch::cpuid::{Cpuid1Ecx, CpuidIn}; use crate::arch::layout::{ - BIOS_DATA_END, EBDA_END, EBDA_START, IOAPIC_START, MEM_64_START, PORT_ACPI_RESET, - PORT_ACPI_SLEEP_CONTROL, PORT_ACPI_TIMER, RAM_32_SIZE, + APIC_START, BIOS_DATA_END, EBDA_END, EBDA_START, IOAPIC_START, MEM_64_START, PORT_ACPI_RESET, + PORT_ACPI_SLEEP_CONTROL, PORT_ACPI_SLEEP_STATUS, PORT_ACPI_TIMER, RAM_32_SIZE, +}; +use crate::board::{ + Board, BoardSpec, CpuTopology, PCIE_MMIO_64_SIZE, Result, default_acpi_header, error, }; -use crate::board::{Board, BoardSpec, CpuTopology, PCIE_MMIO_64_SIZE, Result, error}; use crate::device::ioapic::IoApic; +use crate::firmware::acpi::AcpiTable; use crate::firmware::acpi::bindings::{ - AcpiTableFadt, AcpiTableHeader, AcpiTableRsdp, AcpiTableXsdt3, -}; -use crate::firmware::acpi::reg::{AcpiPmTimer, FadtReset, FadtSleepControl}; -use crate::firmware::acpi::{ - AcpiTable, create_fadt, create_madt, create_mcfg, create_rsdp, create_xsdt, + AcpiAccessWidth, AcpiFadtFlag, AcpiGenericAddress, AcpiMadtIoApic, AcpiMadtLocalX2apic, + AcpiMadtType, AcpiSignature, AcpiSpaceId, AcpiSubtableHeader, AcpiTableFadt, AcpiTableHeader, + AcpiTableMadt, AcpiTableRsdp, AcpiTableXsdt3, FADT_MAJOR_VERSION, FADT_MINOR_VERSION, + MADT_REVISION, XSDT_REVISION, }; +use crate::firmware::acpi::reg::{AcpiPmTimer, FADT_RESET_VAL, FadtReset, FadtSleepControl}; use crate::hv::{CocoSpec, Hypervisor, Vm}; use crate::loader::{Executable, InitState, PayloadSpec}; use crate::mem::{MemRange, MemRegion, MemRegionEntry, MemRegionType}; @@ -260,6 +263,120 @@ where *checksum = checksum.wrapping_sub(sum); } + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#extended-system-description-table-fields-xsdt + fn create_xsdt(&self, entries: [u64; 3]) -> AcpiTableXsdt3 { + let total_length = size_of::() + size_of::() * 3; + let entries = entries.map(|e| transmute!(e)); + AcpiTableXsdt3 { + header: AcpiTableHeader { + signature: AcpiSignature::XSDT, + length: total_length as u32, + revision: XSDT_REVISION, + ..default_acpi_header() + }, + entries, + } + } + + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#fadt-format + fn create_fadt(&self, dsdt_addr: u64) -> AcpiTableFadt { + AcpiTableFadt { + header: AcpiTableHeader { + signature: AcpiSignature::FADT, + revision: FADT_MAJOR_VERSION, + length: size_of::() as u32, + ..default_acpi_header() + }, + reset_register: AcpiGenericAddress { + space_id: AcpiSpaceId::SYSTEM_IO, + bit_width: 8, + bit_offset: 0, + access_width: AcpiAccessWidth::BYTE, + address: transmute!(PORT_ACPI_RESET as u64), + }, + reset_value: FADT_RESET_VAL, + xpm_timer_block: AcpiGenericAddress { + space_id: AcpiSpaceId::SYSTEM_IO, + bit_width: 32, + bit_offset: 0, + access_width: AcpiAccessWidth::DWORD, + address: transmute!(PORT_ACPI_TIMER as u64), + }, + sleep_control: AcpiGenericAddress { + space_id: AcpiSpaceId::SYSTEM_IO, + bit_width: 8, + bit_offset: 0, + access_width: AcpiAccessWidth::BYTE, + address: transmute!(PORT_ACPI_SLEEP_CONTROL as u64), + }, + sleep_status: AcpiGenericAddress { + space_id: AcpiSpaceId::SYSTEM_IO, + bit_width: 8, + bit_offset: 0, + access_width: AcpiAccessWidth::BYTE, + address: transmute!(PORT_ACPI_SLEEP_STATUS as u64), + }, + flags: AcpiFadtFlag::HW_REDUCED_ACPI + | AcpiFadtFlag::RESET_REG_SUP + | AcpiFadtFlag::TMR_VAL_EXT, + minor_revision: FADT_MINOR_VERSION, + hypervisor_id: *b"ALIOTH ", + xdsdt: transmute!(dsdt_addr), + ..Default::default() + } + } + + // https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#multiple-apic-description-table-madt + fn create_madt(&self) -> (AcpiTableMadt, AcpiMadtIoApic, Vec) { + let total_length = size_of::() + + size_of::() + + self.spec.cpu.count as usize * size_of::(); + let mut checksum = 0u8; + + let mut madt = AcpiTableMadt { + header: AcpiTableHeader { + signature: AcpiSignature::MADT, + length: total_length as u32, + revision: MADT_REVISION, + ..default_acpi_header() + }, + address: APIC_START as u32, + flags: 0, + }; + checksum = checksum.wrapping_sub(wrapping_sum(madt.as_bytes())); + + let io_apic = AcpiMadtIoApic { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::IO_APIC, + length: size_of::() as u8, + }, + id: 0, + address: IOAPIC_START as u32, + global_irq_base: 0, + ..Default::default() + }; + checksum = checksum.wrapping_sub(wrapping_sum(io_apic.as_bytes())); + + let mut x2apics = vec![]; + for index in 0..self.spec.cpu.count { + let x2apic = AcpiMadtLocalX2apic { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::LOCAL_X2APIC, + length: size_of::() as u8, + }, + local_apic_id: self.encode_cpu_identity(index) as u32, + uid: index as u32, + lapic_flags: 1, + ..Default::default() + }; + checksum = checksum.wrapping_sub(wrapping_sum(x2apic.as_bytes())); + x2apics.push(x2apic); + } + madt.header.checksum = checksum; + + (madt, io_apic, x2apics) + } + fn create_acpi(&self) -> AcpiTable { let mut table_bytes = Vec::new(); let mut pointers = vec![]; @@ -276,7 +393,7 @@ where let offset_fadt = offset_dsdt + size_of_val(&DSDT_TEMPLATE); debug_assert_eq!(offset_fadt % 4, 0); - let fadt = create_fadt(offset_dsdt as u64); + let fadt = self.create_fadt(offset_dsdt as u64); let pointer_fadt_to_dsdt = offset_fadt + offset_of!(AcpiTableFadt, xdsdt); table_bytes.extend(fadt.as_bytes()); pointers.push(pointer_fadt_to_dsdt); @@ -284,10 +401,7 @@ where let offset_madt = offset_fadt + size_of_val(&fadt); debug_assert_eq!(offset_madt % 4, 0); - let apic_ids: Vec = (0..self.spec.cpu.count) - .map(|index| self.encode_cpu_identity(index) as u32) - .collect(); - let (madt, madt_ioapic, madt_apics) = create_madt(&apic_ids); + let (madt, madt_ioapic, madt_apics) = self.create_madt(); table_bytes.extend(madt.as_bytes()); table_bytes.extend(madt_ioapic.as_bytes()); for apic in madt_apics { @@ -296,19 +410,19 @@ where let offset_mcfg = offset_madt + madt.header.length as usize; debug_assert_eq!(offset_mcfg % 4, 0); - let mcfg = create_mcfg(); + let mcfg = self.create_mcfg(); table_bytes.extend(mcfg.as_bytes()); debug_assert_eq!(offset_xsdt % 4, 0); let xsdt_entries = [offset_fadt as u64, offset_madt as u64, offset_mcfg as u64]; - xsdt = create_xsdt(xsdt_entries); + xsdt = self.create_xsdt(xsdt_entries); xsdt.write_to_prefix(&mut table_bytes).unwrap(); for index in 0..xsdt_entries.len() { pointers.push(offset_xsdt + offset_of!(AcpiTableXsdt3, entries) + index * 8); } checksums.push((offset_xsdt, size_of_val(&xsdt))); - let rsdp = create_rsdp(offset_xsdt as u64); + let rsdp = self.create_rsdp(offset_xsdt as u64); AcpiTable { rsdp, diff --git a/alioth/src/board/board_arm64.rs b/alioth/src/board/board_arm64.rs deleted file mode 100644 index 375061df..00000000 --- a/alioth/src/board/board_arm64.rs +++ /dev/null @@ -1,537 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::collections::HashMap; -use std::sync::Arc; - -use crate::arch::layout::{ - DEVICE_TREE_LIMIT, DEVICE_TREE_START, GIC_DIST_START, GIC_MSI_START, - GIC_V2_CPU_INTERFACE_START, GIC_V3_REDIST_START, IO_END, IO_START, MEM_64_START, - PCIE_CONFIG_START, PCIE_MMIO_32_NON_PREFETCHABLE_END, PCIE_MMIO_32_NON_PREFETCHABLE_START, - PCIE_MMIO_32_PREFETCHABLE_END, PCIE_MMIO_32_PREFETCHABLE_START, PL011_START, PL031_START, - RAM_32_SIZE, RAM_32_START, -}; -use crate::arch::reg::MpidrEl1; -use crate::board::{Board, BoardSpec, CpuTopology, PCIE_MMIO_64_SIZE, Result}; -use crate::firmware::dt::{DeviceTree, Node, PropVal}; -use crate::hv::{GicV2, GicV2m, GicV3, Hypervisor, Its, Vm}; -use crate::loader::{Executable, InitState}; -use crate::mem::{MemRegion, MemRegionType}; - -enum Gic -where - V: Vm, -{ - V2(V::GicV2), - V3(V::GicV3), -} - -enum Msi -where - V: Vm, -{ - V2m(V::GicV2m), - Its(V::Its), -} - -pub struct ArchBoard -where - V: Vm, -{ - gic: Gic, - msi: Option>, -} - -impl ArchBoard { - pub fn new(_hv: &H, vm: &V, spec: &BoardSpec) -> Result - where - H: Hypervisor, - { - let gic = match vm.create_gic_v3(GIC_DIST_START, GIC_V3_REDIST_START, spec.cpu.count) { - Ok(v3) => Gic::V3(v3), - Err(e) => { - log::error!("Cannot create GIC v3: {e:?}trying v2..."); - Gic::V2(vm.create_gic_v2(GIC_DIST_START, GIC_V2_CPU_INTERFACE_START)?) - } - }; - - let create_gic_v2m = || match vm.create_gic_v2m(GIC_MSI_START) { - Ok(v2m) => Some(Msi::V2m(v2m)), - Err(e) => { - log::error!("Cannot create GIC v2m: {e:?}"); - None - } - }; - - let msi = if matches!(gic, Gic::V3(_)) { - match vm.create_its(GIC_MSI_START) { - Ok(its) => Some(Msi::Its(its)), - Err(e) => { - log::error!("Cannot create ITS: {e:?}trying v2m..."); - create_gic_v2m() - } - } - } else { - create_gic_v2m() - }; - - Ok(ArchBoard { gic, msi }) - } -} - -fn encode_mpidr(topology: &CpuTopology, index: u16) -> MpidrEl1 { - let (socket_id, core_id, thread_id) = topology.encode(index); - let mut mpidr = MpidrEl1(0); - mpidr.set_aff0(thread_id); - mpidr.set_aff1(core_id as u8); - mpidr.set_aff2(socket_id); - mpidr -} - -impl Board -where - V: Vm, -{ - pub fn encode_cpu_identity(&self, index: u16) -> u64 { - encode_mpidr(&self.spec.cpu.topology, index).0 - } - - pub fn create_ram(&self) -> Result<()> { - let mem_size = self.spec.mem.size; - let memory = &self.memory; - - let low_mem_size = std::cmp::min(mem_size, RAM_32_SIZE); - let pages_low = self.create_ram_pages(low_mem_size, c"ram-low")?; - memory.add_region( - RAM_32_START, - Arc::new(MemRegion::with_ram(pages_low, MemRegionType::Ram)), - )?; - - let high_mem_size = mem_size.saturating_sub(RAM_32_SIZE); - if high_mem_size > 0 { - let pages_high = self.create_ram_pages(high_mem_size, c"ram-high")?; - memory.add_region( - MEM_64_START, - Arc::new(MemRegion::with_ram(pages_high, MemRegionType::Ram)), - )?; - } - - Ok(()) - } - - pub fn coco_init(&self) -> Result<()> { - Ok(()) - } - - pub fn arch_init(&self) -> Result<()> { - match &self.arch.gic { - Gic::V2(v2) => v2.init(), - Gic::V3(v3) => v3.init(), - }?; - match &self.arch.msi { - Some(Msi::V2m(v2m)) => v2m.init(), - Some(Msi::Its(its)) => its.init(), - None => Ok(()), - }?; - Ok(()) - } - - fn create_chosen_node(&self, init_state: &InitState, root: &mut Node) { - let payload = self.payload.read(); - let Some(payload) = payload.as_ref() else { - return; - }; - if !matches!(payload.executable, Some(Executable::Linux(_))) { - return; - } - let mut node = Node::default(); - if let Some(cmdline) = &payload.cmdline { - let cmdline = cmdline.to_string(); - node.props.insert("bootargs", PropVal::String(cmdline)); - } - if let Some(initramfs_range) = &init_state.initramfs { - node.props.insert( - "linux,initrd-start", - PropVal::U32(initramfs_range.start as u32), - ); - node.props - .insert("linux,initrd-end", PropVal::U32(initramfs_range.end as u32)); - } - node.props.insert( - "stdout-path", - PropVal::String(format!("/pl011@{PL011_START:x}")), - ); - root.nodes.push(("chosen".to_owned(), node)); - } - - pub fn create_memory_node(&self, root: &mut Node) { - let regions = self.memory.mem_region_entries(); - for (start, region) in regions { - if region.type_ != MemRegionType::Ram { - continue; - }; - let node = Node { - props: HashMap::from([ - ("device_type", PropVal::Str("memory")), - ("reg", PropVal::U64List(vec![start, region.size])), - ]), - nodes: Vec::new(), - }; - root.nodes.push((format!("memory@{start:x}"), node)); - } - } - - pub fn create_cpu_nodes(&self, root: &mut Node) { - let topology = &self.spec.cpu.topology; - - let thread_node = |socket_id: u8, core_id: u16, thread_id: u8| { - let phandle = PHANDLE_CPU | topology.decode(socket_id, core_id, thread_id) as u32; - Node { - props: HashMap::from([("cpu", PropVal::PHandle(phandle))]), - nodes: Vec::new(), - } - }; - let core_node = |socket_id: u8, core_id: u16| { - if topology.smt { - Node { - props: HashMap::new(), - nodes: vec![ - ("thread0".to_owned(), thread_node(socket_id, core_id, 0)), - ("thread1".to_owned(), thread_node(socket_id, core_id, 1)), - ], - } - } else { - thread_node(socket_id, core_id, 0) - } - }; - let socket_node = |socket_id: u8| Node { - props: HashMap::new(), - nodes: vec![( - "cluster0".to_owned(), - Node { - props: HashMap::new(), - nodes: (0..topology.cores) - .map(|core_id| (format!("core{core_id}"), core_node(socket_id, core_id))) - .collect(), - }, - )], - }; - let cpu_map_node = Node { - props: HashMap::new(), - nodes: (0..topology.sockets) - .map(|socket_id| (format!("socket{socket_id}"), socket_node(socket_id))) - .collect(), - }; - - let cpus_nodes = (0..(self.spec.cpu.count)) - .map(|index| { - let mpidr = self.encode_cpu_identity(index); - ( - format!("cpu@{mpidr:x}"), - Node { - props: HashMap::from([ - ("device_type", PropVal::Str("cpu")), - ("compatible", PropVal::Str("arm,arm-v8")), - ("enable-method", PropVal::Str("psci")), - ("reg", PropVal::U64(mpidr)), - ("phandle", PropVal::PHandle(PHANDLE_CPU | index as u32)), - ]), - nodes: Vec::new(), - }, - ) - }) - .chain([("cpu-map".to_owned(), cpu_map_node)]) - .collect(); - - let cpus = Node { - props: HashMap::from([ - ("#address-cells", PropVal::U32(2)), - ("#size-cells", PropVal::U32(0)), - ]), - nodes: cpus_nodes, - }; - root.nodes.push(("cpus".to_owned(), cpus)); - } - - fn create_clock_node(&self, root: &mut Node) { - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("fixed-clock")), - ("clock-frequency", PropVal::U32(24000000)), - ("clock-output-names", PropVal::Str("clk24mhz")), - ("phandle", PropVal::PHandle(PHANDLE_CLOCK)), - ("#clock-cells", PropVal::U32(0)), - ]), - nodes: Vec::new(), - }; - root.nodes.push(("apb-pclk".to_owned(), node)); - } - - fn create_pl011_node(&self, root: &mut Node) { - let pin = 1; - let edge_trigger = 1; - let spi = 0; - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,primecell\0arm,pl011")), - ("reg", PropVal::U64List(vec![PL011_START, 0x1000])), - ("interrupts", PropVal::U32List(vec![spi, pin, edge_trigger])), - ("clock-names", PropVal::Str("uartclk\0apb_pclk")), - ( - "clocks", - PropVal::U32List(vec![PHANDLE_CLOCK, PHANDLE_CLOCK]), - ), - ]), - nodes: Vec::new(), - }; - root.nodes.push((format!("pl011@{PL011_START:x}"), node)); - } - - fn create_pl031_node(&self, root: &mut Node) { - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,primecell\0arm,pl031")), - ("reg", PropVal::U64List(vec![PL031_START, 0x1000])), - ("clock-names", PropVal::Str("apb_pclk")), - ("clocks", PropVal::U32List(vec![PHANDLE_CLOCK])), - ]), - nodes: Vec::new(), - }; - root.nodes.push((format!("pl031@{PL031_START:x}"), node)); - } - - // Documentation/devicetree/bindings/timer/arm,arch_timer.yaml - fn create_timer_node(&self, root: &mut Node) { - let mut interrupts = vec![]; - let irq_pins = [13, 14, 11, 10]; - let ppi = 1; - let level_trigger = 4; - let cpu_mask = match self.arch.gic { - Gic::V2(_) => (1 << self.spec.cpu.count) - 1, - Gic::V3 { .. } => 0, - }; - for pin in irq_pins { - interrupts.extend([ppi, pin, (cpu_mask << 8) | level_trigger]); - } - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,armv8-timer")), - ("interrupts", PropVal::U32List(interrupts)), - ("always-on", PropVal::Empty), - ]), - nodes: Vec::new(), - }; - root.nodes.push(("timer".to_owned(), node)); - } - - fn create_gic_msi_node(&self) -> Vec<(String, Node)> { - let Some(msi) = &self.arch.msi else { - return Vec::new(); - }; - match msi { - Msi::Its(_) => { - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,gic-v3-its")), - ("msi-controller", PropVal::Empty), - ("#msi-cells", PropVal::U32(1)), - ("reg", PropVal::U64List(vec![GIC_MSI_START, 128 << 10])), - ("phandle", PropVal::PHandle(PHANDLE_MSI)), - ]), - nodes: Vec::new(), - }; - vec![(format!("its@{GIC_MSI_START:x}"), node)] - } - Msi::V2m(_) => { - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,gic-v2m-frame")), - ("msi-controller", PropVal::Empty), - ("reg", PropVal::U64List(vec![GIC_MSI_START, 64 << 10])), - ("phandle", PropVal::PHandle(PHANDLE_MSI)), - ]), - nodes: Vec::new(), - }; - vec![(format!("v2m@{GIC_MSI_START:x}"), node)] - } - } - } - - fn create_gic_node(&self, root: &mut Node) { - let msi = self.create_gic_msi_node(); - let node = match self.arch.gic { - // Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml - Gic::V2(_) => Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,cortex-a15-gic")), - ("#interrupt-cells", PropVal::U32(3)), - ( - "reg", - PropVal::U64List(vec![ - GIC_DIST_START, - 0x1000, - GIC_V2_CPU_INTERFACE_START, - 0x2000, - ]), - ), - ("phandle", PropVal::U32(PHANDLE_GIC)), - ("interrupt-controller", PropVal::Empty), - ]), - nodes: msi, - }, - // Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml - Gic::V3(_) => Node { - props: HashMap::from([ - ("compatible", PropVal::Str("arm,gic-v3")), - ("#interrupt-cells", PropVal::U32(3)), - ("#address-cells", PropVal::U32(2)), - ("#size-cells", PropVal::U32(2)), - ("interrupt-controller", PropVal::Empty), - ("ranges", PropVal::Empty), - ( - "reg", - PropVal::U64List(vec![ - GIC_DIST_START, - 64 << 10, - GIC_V3_REDIST_START, - self.spec.cpu.count as u64 * (128 << 10), - ]), - ), - ("phandle", PropVal::U32(PHANDLE_GIC)), - ]), - nodes: msi, - }, - }; - root.nodes.push((format!("intc@{GIC_DIST_START:x}"), node)); - } - - // Documentation/devicetree/bindings/arm/psci.yaml - fn create_psci_node(&self, root: &mut Node) { - let node = Node { - props: HashMap::from([ - ("method", PropVal::Str("hvc")), - ("compatible", PropVal::Str("arm,psci-0.2\0arm,psci")), - ]), - nodes: Vec::new(), - }; - root.nodes.push(("psci".to_owned(), node)); - } - - // https://elinux.org/Device_Tree_Usage#PCI_Host_Bridge - // Documentation/devicetree/bindings/pci/host-generic-pci.yaml - // IEEE Std 1275-1994 - fn create_pci_bridge_node(&self, root: &mut Node) { - let Some(max_bus) = self.pci_bus.segment.max_bus() else { - return; - }; - let pcie_mmio_64_start = self.spec.pcie_mmio_64_start(); - let prefetchable = 1 << 30; - let io = 0b01 << 24; - let mem_32 = 0b10 << 24; - let mem_64 = 0b11 << 24; - let node = Node { - props: HashMap::from([ - ("compatible", PropVal::Str("pci-host-ecam-generic")), - ("device_type", PropVal::Str("pci")), - ("reg", PropVal::U64List(vec![PCIE_CONFIG_START, 256 << 20])), - ("bus-range", PropVal::U32List(vec![0, max_bus as u32])), - ("#address-cells", PropVal::U32(3)), - ("#size-cells", PropVal::U32(2)), - ( - "ranges", - PropVal::U32List(vec![ - io, - 0, - 0, - 0, - IO_START as u32, - 0, - (IO_END - IO_START) as u32, - mem_32 | prefetchable, - 0, - PCIE_MMIO_32_PREFETCHABLE_START as u32, - 0, - PCIE_MMIO_32_PREFETCHABLE_START as u32, - 0, - (PCIE_MMIO_32_PREFETCHABLE_END - PCIE_MMIO_32_PREFETCHABLE_START) as u32, - mem_32, - 0, - PCIE_MMIO_32_NON_PREFETCHABLE_START as u32, - 0, - PCIE_MMIO_32_NON_PREFETCHABLE_START as u32, - 0, - (PCIE_MMIO_32_NON_PREFETCHABLE_END - PCIE_MMIO_32_NON_PREFETCHABLE_START) - as u32, - mem_64 | prefetchable, - (pcie_mmio_64_start >> 32) as u32, - pcie_mmio_64_start as u32, - (pcie_mmio_64_start >> 32) as u32, - pcie_mmio_64_start as u32, - (PCIE_MMIO_64_SIZE >> 32) as u32, - PCIE_MMIO_64_SIZE as u32, - ]), - ), - ( - "msi-map", - // Identity map from RID (BDF) to msi-specifier. - // Documentation/devicetree/bindings/pci/pci-msi.txt - PropVal::U32List(vec![0, PHANDLE_MSI, 0, 0x10000]), - ), - ]), - nodes: Vec::new(), - }; - root.nodes - .push((format!("pci@{PCIE_CONFIG_START:x}"), node)); - } - - pub fn create_firmware_data(&self, init_state: &InitState) -> Result<()> { - let mut device_tree = DeviceTree::new(); - let root = &mut device_tree.root; - root.props.insert("#address-cells", PropVal::U32(2)); - root.props.insert("#size-cells", PropVal::U32(2)); - root.props.insert("model", PropVal::Str("linux,dummy-virt")); - root.props - .insert("compatible", PropVal::Str("linux,dummy-virt")); - root.props - .insert("interrupt-parent", PropVal::PHandle(PHANDLE_GIC)); - - self.create_chosen_node(init_state, root); - self.create_pl011_node(root); - self.create_pl031_node(root); - self.create_memory_node(root); - self.create_cpu_nodes(root); - self.create_gic_node(root); - if self.arch.msi.is_some() { - self.create_pci_bridge_node(root); - } - self.create_clock_node(root); - self.create_timer_node(root); - self.create_psci_node(root); - log::debug!("device tree: {device_tree:#x?}"); - let blob = device_tree.to_blob(); - let ram = self.memory.ram_bus(); - assert!(blob.len() as u64 <= DEVICE_TREE_LIMIT); - ram.write_range(DEVICE_TREE_START, blob.len() as u64, &*blob)?; - Ok(()) - } -} - -const PHANDLE_GIC: u32 = 1; -const PHANDLE_CLOCK: u32 = 2; -const PHANDLE_MSI: u32 = 3; -const PHANDLE_CPU: u32 = 1 << 31; - -#[cfg(test)] -#[path = "board_arm64_test.rs"] -mod tests; diff --git a/alioth/src/board/board_arm64/board_arm64.rs b/alioth/src/board/board_arm64/board_arm64.rs new file mode 100644 index 00000000..29ba8c3e --- /dev/null +++ b/alioth/src/board/board_arm64/board_arm64.rs @@ -0,0 +1,1250 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::mem::{offset_of, size_of, size_of_val}; +use std::sync::Arc; + +use parking_lot::Mutex; +use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, transmute}; + +use crate::arch::layout::{ + ACPI_LIMIT, ACPI_START, DEVICE_TREE_LIMIT, DEVICE_TREE_START, GIC_DIST_START, GIC_MSI_START, + GIC_V2_CPU_INTERFACE_START, GIC_V3_REDIST_START, IO_END, IO_START, KERNEL_IMAGE_START, + MEM_64_START, PCIE_CONFIG_START, PCIE_MMIO_32_NON_PREFETCHABLE_END, + PCIE_MMIO_32_NON_PREFETCHABLE_START, PCIE_MMIO_32_PREFETCHABLE_END, + PCIE_MMIO_32_PREFETCHABLE_START, PL011_START, PL031_START, RAM_32_SIZE, RAM_32_START, + UEFI_LIMIT, UEFI_START, +}; +use crate::arch::reg::MpidrEl1; +use crate::board::{Board, BoardSpec, CpuTopology, PCIE_MMIO_64_SIZE, Result, default_acpi_header}; +use crate::firmware::acpi::AcpiTable; +use crate::firmware::acpi::bindings::{ + AcpiAccessWidth, AcpiFadtArmBootFlag, AcpiFadtFlag, AcpiGenericAddress, AcpiGtdtFlag, + AcpiIortIdMapping, AcpiIortItsGroup1, AcpiIortMemoryAccess, AcpiIortNode, AcpiIortNodeType, + AcpiIortRootComplex1, AcpiMadtGenericDistributor, AcpiMadtGenericInterrupt, + AcpiMadtGenericMsiFrame, AcpiMadtGenericRedistributor, AcpiMadtGenericTranslator, + AcpiMadtGiccFlag, AcpiMadtType, AcpiPpttFlag, AcpiPpttProcessor, AcpiPpttType, AcpiSignature, + AcpiSpaceId, AcpiSpcrInterruptType, AcpiSubtableHeader, AcpiTableFadt, AcpiTableGtdt, + AcpiTableHeader, AcpiTableIort, AcpiTableMadt, AcpiTablePptt, AcpiTableRsdp, AcpiTableSpcr, + AcpiTableXsdt6, AcpiTableXsdt7, FADT_MAJOR_VERSION, FADT_MINOR_VERSION, GTDT_REVISION, + IORT_REVISION, MADT_REVISION, PPTT_REVISION, SPCR_INTERFACE_ARM_PL011, SPCR_REVISION, + XSDT_REVISION, +}; +use crate::firmware::dt::{DeviceTree, Node, PropVal}; +use crate::firmware::uefi::{ + EFI_2_100_SYSTEM_TABLE_REVISION, EFI_MEMORY_DESCRIPTOR_VERSION, + EFI_RT_PROPERTIES_TABLE_VERSION, EFI_SYSTEM_TABLE_SIGNATURE, EfiConfigTable64, + EfiMemoryAttribute, EfiMemoryDesc, EfiMemoryType, EfiRtPropertiesTable, EfiSystemTable64, + EfiTableHeader, GUID_ACPI_20_TABLE, GUID_EFI_RT_PROPERTIES_TABLE, + GUID_LINUX_EFI_MEMRESERVE_TABLE, LinuxEfiMemreserve, +}; +use crate::hv::{GicV2, GicV2m, GicV3, Hypervisor, Its, Vm}; +use crate::loader::{Executable, InitState}; +use crate::mem::{MemRange, MemRegion, MemRegionEntry, MemRegionType}; +use crate::utils::wrapping_sum; + +#[repr(C)] +#[derive(Debug, Clone, FromBytes, Immutable, IntoBytes)] +struct StaticUefiTables { + systab: EfiSystemTable64, + rt_prop: EfiRtPropertiesTable, + fw_vendor: [u16; 8], + memreserve: LinuxEfiMemreserve, + config_tables: [EfiConfigTable64; 3], +} + +enum Gic +where + V: Vm, +{ + V2(V::GicV2), + V3(V::GicV3), +} + +enum Msi +where + V: Vm, +{ + V2m(V::GicV2m), + Its(V::Its), +} + +pub struct ArchBoard +where + V: Vm, +{ + gic: Gic, + msi: Option>, +} + +impl ArchBoard { + pub fn new(_hv: &H, vm: &V, spec: &BoardSpec) -> Result + where + H: Hypervisor, + { + let gic = match vm.create_gic_v3(GIC_DIST_START, GIC_V3_REDIST_START, spec.cpu.count) { + Ok(v3) => Gic::V3(v3), + Err(e) => { + log::error!("Cannot create GIC v3: {e:?}trying v2..."); + Gic::V2(vm.create_gic_v2(GIC_DIST_START, GIC_V2_CPU_INTERFACE_START)?) + } + }; + + let create_gic_v2m = || match vm.create_gic_v2m(GIC_MSI_START) { + Ok(v2m) => Some(Msi::V2m(v2m)), + Err(e) => { + log::error!("Cannot create GIC v2m: {e:?}"); + None + } + }; + + let msi = if matches!(gic, Gic::V3(_)) { + match vm.create_its(GIC_MSI_START) { + Ok(its) => Some(Msi::Its(its)), + Err(e) => { + log::error!("Cannot create ITS: {e:?}trying v2m..."); + create_gic_v2m() + } + } + } else { + create_gic_v2m() + }; + + Ok(ArchBoard { gic, msi }) + } +} + +fn encode_mpidr(topology: &CpuTopology, index: u16) -> MpidrEl1 { + let (socket_id, core_id, thread_id) = topology.encode(index); + let mut mpidr = MpidrEl1(0); + mpidr.set_aff0(thread_id); + mpidr.set_aff1(core_id as u8); + mpidr.set_aff2(socket_id); + mpidr +} + +impl Board +where + V: Vm, +{ + pub fn encode_cpu_identity(&self, index: u16) -> u64 { + encode_mpidr(&self.spec.cpu.topology, index).0 + } + + pub fn create_ram(&self) -> Result<()> { + let mem_size = self.spec.mem.size; + let memory = &self.memory; + + let low_mem_size = std::cmp::min(mem_size, RAM_32_SIZE); + let pages_low = self.create_ram_pages(low_mem_size, c"ram-low")?; + let acpi_size = KERNEL_IMAGE_START - RAM_32_START; + let region_low = if self.spec.platform.acpi && low_mem_size > acpi_size { + MemRegion { + ranges: vec![MemRange::Ram(pages_low)], + entries: vec![ + MemRegionEntry { + size: acpi_size, + type_: MemRegionType::Acpi, + }, + MemRegionEntry { + size: low_mem_size - acpi_size, + type_: MemRegionType::Ram, + }, + ], + callbacks: Mutex::new(vec![]), + } + } else { + MemRegion::with_ram(pages_low, MemRegionType::Ram) + }; + memory.add_region(RAM_32_START, Arc::new(region_low))?; + + let high_mem_size = mem_size.saturating_sub(RAM_32_SIZE); + if high_mem_size > 0 { + let pages_high = self.create_ram_pages(high_mem_size, c"ram-high")?; + memory.add_region( + MEM_64_START, + Arc::new(MemRegion::with_ram(pages_high, MemRegionType::Ram)), + )?; + } + + Ok(()) + } + + pub fn coco_init(&self) -> Result<()> { + Ok(()) + } + + pub fn arch_init(&self) -> Result<()> { + match &self.arch.gic { + Gic::V2(v2) => v2.init(), + Gic::V3(v3) => v3.init(), + }?; + match &self.arch.msi { + Some(Msi::V2m(v2m)) => v2m.init(), + Some(Msi::Its(its)) => its.init(), + None => Ok(()), + }?; + Ok(()) + } + + fn create_xsdt6(&self, entries: [u64; 6]) -> AcpiTableXsdt6 { + let total_length = size_of::() + size_of::() * 6; + let entries = entries.map(|e| transmute!(e)); + AcpiTableXsdt6 { + header: AcpiTableHeader { + signature: AcpiSignature::XSDT, + length: total_length as u32, + revision: XSDT_REVISION, + ..default_acpi_header() + }, + entries, + } + } + + fn create_xsdt7(&self, entries: [u64; 7]) -> AcpiTableXsdt7 { + let total_length = size_of::() + size_of::() * 7; + let entries = entries.map(|e| transmute!(e)); + AcpiTableXsdt7 { + header: AcpiTableHeader { + signature: AcpiSignature::XSDT, + length: total_length as u32, + revision: XSDT_REVISION, + ..default_acpi_header() + }, + entries, + } + } + + fn create_fadt(&self, dsdt_addr: u64) -> AcpiTableFadt { + AcpiTableFadt { + header: AcpiTableHeader { + signature: AcpiSignature::FADT, + revision: FADT_MAJOR_VERSION, + length: size_of::() as u32, + ..default_acpi_header() + }, + flags: AcpiFadtFlag::HW_REDUCED_ACPI, + arm_boot_flags: AcpiFadtArmBootFlag::PSCI_COMPLIANT | AcpiFadtArmBootFlag::PSCI_USE_HVC, + minor_revision: FADT_MINOR_VERSION, + hypervisor_id: *b"ALIOTH ", + xdsdt: transmute!(dsdt_addr), + ..Default::default() + } + } + + fn create_madt(&self) -> (AcpiTableMadt, Vec) { + let mut subtables = Vec::new(); + + let (gic_version, cpu_base) = match self.arch.gic { + Gic::V2(_) => (2, GIC_V2_CPU_INTERFACE_START), + Gic::V3(_) => (3, 0), + }; + let gicd = AcpiMadtGenericDistributor { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::GENERIC_DISTRIBUTOR, + length: size_of::() as u8, + }, + base_address: transmute!(GIC_DIST_START), + version: gic_version, + ..Default::default() + }; + subtables.extend_from_slice(gicd.as_bytes()); + + if matches!(self.arch.gic, Gic::V3(_)) { + let gicr = AcpiMadtGenericRedistributor { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::GENERIC_REDISTRIBUTOR, + length: size_of::() as u8, + }, + base_address: transmute!(GIC_V3_REDIST_START), + length: (self.spec.cpu.count as u32) * (128 << 10), + ..Default::default() + }; + subtables.extend_from_slice(gicr.as_bytes()); + } + + match &self.arch.msi { + Some(Msi::Its(_)) => { + let its = AcpiMadtGenericTranslator { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::GENERIC_TRANSLATOR, + length: size_of::() as u8, + }, + translation_id: 0, + base_address: transmute!(GIC_MSI_START), + ..Default::default() + }; + subtables.extend_from_slice(its.as_bytes()); + } + Some(Msi::V2m(_)) => { + let v2m = AcpiMadtGenericMsiFrame { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::GENERIC_MSI_FRAME, + length: size_of::() as u8, + }, + msi_frame_id: 0, + base_address: transmute!(GIC_MSI_START), + ..Default::default() + }; + subtables.extend_from_slice(v2m.as_bytes()); + } + None => {} + } + + for index in 0..self.spec.cpu.count { + let mpidr = self.encode_cpu_identity(index); + let gicc = AcpiMadtGenericInterrupt { + header: AcpiSubtableHeader { + r#type: AcpiMadtType::GENERIC_INTERRUPT, + length: size_of::() as u8, + }, + cpu_interface_number: index as u32, + uid: index as u32, + flags: AcpiMadtGiccFlag::ENABLED, + base_address: transmute!(cpu_base), + arm_mpidr: transmute!(mpidr), + ..Default::default() + }; + subtables.extend_from_slice(gicc.as_bytes()); + } + + let total_length = size_of::() + subtables.len(); + let mut madt = AcpiTableMadt { + header: AcpiTableHeader { + signature: AcpiSignature::MADT, + length: total_length as u32, + revision: MADT_REVISION, + ..default_acpi_header() + }, + address: 0, + flags: 0, + }; + let checksum = wrapping_sum(madt.as_bytes()).wrapping_add(wrapping_sum(&subtables)); + madt.header.checksum = 0u8.wrapping_sub(checksum); + + (madt, subtables) + } + + fn create_gtdt(&self) -> AcpiTableGtdt { + let flags = AcpiGtdtFlag::INTERRUPT_POLARITY_ACTIVE_LOW | AcpiGtdtFlag::ALWAYS_ON; + let mut gtdt = AcpiTableGtdt { + header: AcpiTableHeader { + signature: AcpiSignature::GTDT, + length: size_of::() as u32, + revision: GTDT_REVISION, + ..default_acpi_header() + }, + counter_block_address: [u32::MAX; 2], + secure_el1_interrupt: ppi_to_gsiv(PPI_TIMER_SECURE_EL1_PHYS), + secure_el1_flags: flags, + non_secure_el1_interrupt: ppi_to_gsiv(PPI_TIMER_NON_SECURE_EL1_PHYS), + non_secure_el1_flags: flags, + virtual_timer_interrupt: ppi_to_gsiv(PPI_TIMER_VIRTUAL_EL1), + virtual_timer_flags: flags, + non_secure_el2_interrupt: ppi_to_gsiv(PPI_TIMER_NON_SECURE_EL2_PHYS), + non_secure_el2_flags: flags, + counter_read_block_address: [u32::MAX; 2], + virtual_el2_timer_gsiv: ppi_to_gsiv(PPI_TIMER_VIRTUAL_EL2), + virtual_el2_timer_flags: flags, + ..Default::default() + }; + gtdt.header.checksum = 0u8.wrapping_sub(wrapping_sum(gtdt.as_bytes())); + gtdt + } + + fn create_spcr(&self) -> AcpiTableSpcr { + let mut spcr = AcpiTableSpcr { + header: AcpiTableHeader { + signature: AcpiSignature::SPCR, + length: size_of::() as u32, + revision: SPCR_REVISION, + ..default_acpi_header() + }, + interface_type: SPCR_INTERFACE_ARM_PL011, + serial_port: AcpiGenericAddress { + space_id: AcpiSpaceId::SYSTEM_MEMORY, + bit_width: 32, + bit_offset: 0, + access_width: AcpiAccessWidth::DWORD, + address: transmute!(PL011_START), + }, + interrupt_type: AcpiSpcrInterruptType::GIC, + interrupt: (32u32 + 1).to_le_bytes(), + baud_rate: 7, + stop_bits: 1, + flow_control: 0, + terminal_type: 3, + pci_device_id: 0xffff, + pci_vendor_id: 0xffff, + ..Default::default() + }; + spcr.header.checksum = 0u8.wrapping_sub(wrapping_sum(spcr.as_bytes())); + spcr + } + + fn create_iort(&self) -> (AcpiTableIort, AcpiIortItsGroup1, AcpiIortRootComplex1) { + let total_length = size_of::() + + size_of::() + + size_of::(); + let offset_its = size_of::() as u32; + let its_group = AcpiIortItsGroup1 { + node: AcpiIortNode { + type_: AcpiIortNodeType::ITS_GROUP, + length: (size_of::() as u16).to_le_bytes(), + revision: 1, + identifier: 0, + mapping_count: 0, + mapping_offset: 0, + }, + its_count: 1, + identifiers: [0], + }; + let rc = AcpiIortRootComplex1 { + node: AcpiIortNode { + type_: AcpiIortNodeType::PCI_ROOT_COMPLEX, + length: (size_of::() as u16).to_le_bytes(), + revision: 4, + identifier: 1, + mapping_count: 1, + mapping_offset: offset_of!(AcpiIortRootComplex1, mappings) as u32, + }, + memory_properties: AcpiIortMemoryAccess { + cache_coherency: 1, + hints: 0, + reserved: [0; 2], + memory_flags: 0x3, + }, + ats_attribute: 0, + pci_segment_number: 0, + memory_address_limit: 64, + pasid_capabilities: [0; 2], + reserved: 0, + flags: 0, + mappings: [AcpiIortIdMapping { + input_base: 0, + id_count: 0xffff, + output_base: 0, + output_reference: offset_its, + flags: 0, + }], + }; + let mut iort = AcpiTableIort { + header: AcpiTableHeader { + signature: AcpiSignature::IORT, + length: total_length as u32, + revision: IORT_REVISION, + ..default_acpi_header() + }, + node_count: 2, + node_offset: offset_its, + reserved: 0, + }; + let checksum = wrapping_sum(iort.as_bytes()) + .wrapping_add(wrapping_sum(its_group.as_bytes())) + .wrapping_add(wrapping_sum(rc.as_bytes())); + iort.header.checksum = 0u8.wrapping_sub(checksum); + (iort, its_group, rc) + } + + fn create_pptt(&self) -> (AcpiTablePptt, Vec) { + let topology = &self.spec.cpu.topology; + let mut nodes = Vec::new(); + let mut current_offset = size_of::() as u32; + let node_size = size_of::() as u32; + + for socket_id in 0..topology.sockets { + let socket_offset = current_offset; + nodes.push(AcpiPpttProcessor { + header: AcpiSubtableHeader { + r#type: AcpiPpttType::PROCESSOR, + length: node_size as u8, + }, + flags: AcpiPpttFlag::PHYSICAL_PACKAGE | AcpiPpttFlag::ACPI_IDENTICAL, + parent: 0, + acpi_processor_id: socket_id as u32, + ..Default::default() + }); + current_offset += node_size; + + for core_id in 0..topology.cores { + if topology.smt { + let core_offset = current_offset; + nodes.push(AcpiPpttProcessor { + header: AcpiSubtableHeader { + r#type: AcpiPpttType::PROCESSOR, + length: node_size as u8, + }, + flags: AcpiPpttFlag::ACPI_IDENTICAL, + parent: socket_offset, + acpi_processor_id: core_id as u32, + ..Default::default() + }); + current_offset += node_size; + + for thread_id in 0..2 { + let uid = topology.decode(socket_id, core_id, thread_id) as u32; + nodes.push(AcpiPpttProcessor { + header: AcpiSubtableHeader { + r#type: AcpiPpttType::PROCESSOR, + length: node_size as u8, + }, + flags: AcpiPpttFlag::ACPI_PROCESSOR_ID_VALID + | AcpiPpttFlag::ACPI_PROCESSOR_IS_THREAD + | AcpiPpttFlag::ACPI_LEAF_NODE + | AcpiPpttFlag::ACPI_IDENTICAL, + parent: core_offset, + acpi_processor_id: uid, + ..Default::default() + }); + current_offset += node_size; + } + } else { + let uid = topology.decode(socket_id, core_id, 0) as u32; + nodes.push(AcpiPpttProcessor { + header: AcpiSubtableHeader { + r#type: AcpiPpttType::PROCESSOR, + length: node_size as u8, + }, + flags: AcpiPpttFlag::ACPI_PROCESSOR_ID_VALID + | AcpiPpttFlag::ACPI_LEAF_NODE + | AcpiPpttFlag::ACPI_IDENTICAL, + parent: socket_offset, + acpi_processor_id: uid, + ..Default::default() + }); + current_offset += node_size; + } + } + } + + let mut pptt = AcpiTablePptt { + header: AcpiTableHeader { + signature: AcpiSignature::PPTT, + length: current_offset, + revision: PPTT_REVISION, + ..default_acpi_header() + }, + }; + let checksum = wrapping_sum(pptt.as_bytes()).wrapping_add(wrapping_sum(nodes.as_bytes())); + pptt.header.checksum = 0u8.wrapping_sub(checksum); + (pptt, nodes) + } + + fn create_dsdt(&self) -> Vec { + let mut dsdt = Vec::from(DSDT_TEMPLATE); + let pcie_mmio_64_start = self.spec.pcie_mmio_64_start(); + let pcie_mmio_64_max = pcie_mmio_64_start - 1 + PCIE_MMIO_64_SIZE; + dsdt[DSDT_OFFSET_PCI_QWORD_MEM..(DSDT_OFFSET_PCI_QWORD_MEM + 8)] + .copy_from_slice(&pcie_mmio_64_start.to_le_bytes()); + dsdt[(DSDT_OFFSET_PCI_QWORD_MEM + 8)..(DSDT_OFFSET_PCI_QWORD_MEM + 16)] + .copy_from_slice(&pcie_mmio_64_max.to_le_bytes()); + for index in 0..self.spec.cpu.count { + let mut cpu_aml = AML_CPU_TEMPLATE; + let name = format!("C{index:03X}"); + cpu_aml[8..12].copy_from_slice(name.as_bytes()); + cpu_aml[33..35].copy_from_slice(&index.to_le_bytes()); + dsdt.extend_from_slice(&cpu_aml); + } + let len = dsdt.len() as u32; + let len_offset = offset_of!(AcpiTableHeader, length); + dsdt[len_offset..(len_offset + 4)].copy_from_slice(&len.to_le_bytes()); + let checksum_offset = offset_of!(AcpiTableHeader, checksum); + dsdt[checksum_offset] = 0; + let sum = wrapping_sum(&dsdt); + dsdt[checksum_offset] = 0u8.wrapping_sub(sum); + dsdt + } + + fn create_acpi(&self) -> AcpiTable { + let mut table_bytes = Vec::new(); + let mut pointers = vec![]; + let mut checksums = vec![]; + let has_its = matches!(self.arch.msi, Some(Msi::Its(_))); + + let offset_xsdt = 0; + if has_its { + let xsdt = AcpiTableXsdt7::new_zeroed(); + table_bytes.extend(xsdt.as_bytes()); + } else { + let xsdt = AcpiTableXsdt6::new_zeroed(); + table_bytes.extend(xsdt.as_bytes()); + } + + let offset_dsdt = table_bytes.len(); + let dsdt = self.create_dsdt(); + table_bytes.extend(dsdt); + table_bytes.resize(table_bytes.len().next_multiple_of(4), 0); + + let offset_fadt = table_bytes.len(); + debug_assert_eq!(offset_fadt % 4, 0); + let fadt = self.create_fadt(offset_dsdt as u64); + let pointer_fadt_to_dsdt = offset_fadt + offset_of!(AcpiTableFadt, xdsdt); + table_bytes.extend(fadt.as_bytes()); + pointers.push(pointer_fadt_to_dsdt); + checksums.push((offset_fadt, size_of_val(&fadt))); + + let offset_madt = table_bytes.len(); + debug_assert_eq!(offset_madt % 4, 0); + let (madt, madt_subtables) = self.create_madt(); + table_bytes.extend(madt.as_bytes()); + table_bytes.extend(madt_subtables); + + let offset_mcfg = table_bytes.len(); + debug_assert_eq!(offset_mcfg % 4, 0); + let mcfg = self.create_mcfg(); + table_bytes.extend(mcfg.as_bytes()); + + let offset_gtdt = table_bytes.len(); + debug_assert_eq!(offset_gtdt % 4, 0); + let gtdt = self.create_gtdt(); + table_bytes.extend(gtdt.as_bytes()); + + let offset_spcr = table_bytes.len(); + debug_assert_eq!(offset_spcr % 4, 0); + let spcr = self.create_spcr(); + table_bytes.extend(spcr.as_bytes()); + + let offset_pptt = table_bytes.len(); + debug_assert_eq!(offset_pptt % 4, 0); + let (pptt, pptt_nodes) = self.create_pptt(); + table_bytes.extend(pptt.as_bytes()); + for node in pptt_nodes { + table_bytes.extend(node.as_bytes()); + } + + if has_its { + let offset_iort = table_bytes.len(); + debug_assert_eq!(offset_iort % 4, 0); + let (iort, its_group, rc) = self.create_iort(); + table_bytes.extend(iort.as_bytes()); + table_bytes.extend(its_group.as_bytes()); + table_bytes.extend(rc.as_bytes()); + + let xsdt_entries = [ + offset_fadt as u64, + offset_madt as u64, + offset_mcfg as u64, + offset_gtdt as u64, + offset_spcr as u64, + offset_pptt as u64, + offset_iort as u64, + ]; + let xsdt = self.create_xsdt7(xsdt_entries); + xsdt.write_to_prefix(&mut table_bytes).unwrap(); + for index in 0..xsdt_entries.len() { + pointers.push(offset_xsdt + offset_of!(AcpiTableXsdt7, entries) + index * 8); + } + checksums.push((offset_xsdt, size_of_val(&xsdt))); + } else { + let xsdt_entries = [ + offset_fadt as u64, + offset_madt as u64, + offset_mcfg as u64, + offset_gtdt as u64, + offset_spcr as u64, + offset_pptt as u64, + ]; + let xsdt = self.create_xsdt6(xsdt_entries); + xsdt.write_to_prefix(&mut table_bytes).unwrap(); + for index in 0..xsdt_entries.len() { + pointers.push(offset_xsdt + offset_of!(AcpiTableXsdt6, entries) + index * 8); + } + checksums.push((offset_xsdt, size_of_val(&xsdt))); + } + + let rsdp = self.create_rsdp(offset_xsdt as u64); + + AcpiTable { + rsdp, + tables: table_bytes, + table_checksums: checksums, + table_pointers: pointers, + } + } + + fn create_uefi(&self) -> (StaticUefiTables, Vec) { + let tables = StaticUefiTables { + systab: EfiSystemTable64 { + hdr: EfiTableHeader { + signature: EFI_SYSTEM_TABLE_SIGNATURE, + revision: EFI_2_100_SYSTEM_TABLE_REVISION, + headersize: size_of::() as u32, + crc32: 0, // Linux does not check crc32 in synthetic direct-boot mode + reserved: 0, + }, + fw_vendor: UEFI_START + offset_of!(StaticUefiTables, fw_vendor) as u64, + fw_revision: 1, + _pad: 0, + con_in_handle: 0, + con_in: 0, + con_out_handle: 0, + con_out: 0, + stderr_handle: 0, + stderr: 0, + runtime: 0, + boottime: 0, + nr_tables: 3, + tables: UEFI_START + offset_of!(StaticUefiTables, config_tables) as u64, + }, + rt_prop: EfiRtPropertiesTable { + version: EFI_RT_PROPERTIES_TABLE_VERSION, + length: size_of::() as u16, + runtime_services_supported: 0, + }, + fw_vendor: [ + b'A' as u16, + b'l' as u16, + b'i' as u16, + b'o' as u16, + b't' as u16, + b'h' as u16, + 0, + 0, + ], + memreserve: LinuxEfiMemreserve { + size: 0, + count: 0, + next: 0, + }, + config_tables: [ + EfiConfigTable64 { + guid: GUID_ACPI_20_TABLE, + table: ACPI_START, + }, + EfiConfigTable64 { + guid: GUID_EFI_RT_PROPERTIES_TABLE, + table: UEFI_START + offset_of!(StaticUefiTables, rt_prop) as u64, + }, + EfiConfigTable64 { + guid: GUID_LINUX_EFI_MEMRESERVE_TABLE, + table: UEFI_START + offset_of!(StaticUefiTables, memreserve) as u64, + }, + ], + }; + + let mem_regions = self.memory.mem_region_entries(); + let mut mmap = Vec::new(); + for (start, entry) in mem_regions { + let ty = match entry.type_ { + MemRegionType::Ram => EfiMemoryType::CONVENTIONAL_MEMORY, + MemRegionType::Acpi => EfiMemoryType::ACPI_RECLAIM_MEMORY, + _ => continue, + }; + mmap.push(EfiMemoryDesc { + ty, + pad: 0, + phys_addr: start, + virt_addr: start, + num_pages: entry.size >> 12, + attribute: EfiMemoryAttribute::WB, + }); + } + + (tables, mmap) + } + + fn create_chosen_node( + &self, + init_state: &InitState, + uefi_mmap_size: Option, + root: &mut Node, + ) { + let payload = self.payload.read(); + let Some(payload) = payload.as_ref() else { + return; + }; + if !matches!(payload.executable, Some(Executable::Linux(_))) { + return; + } + let mut node = Node::default(); + if let Some(cmdline) = &payload.cmdline { + let cmdline = cmdline.to_string(); + node.props.insert("bootargs", PropVal::String(cmdline)); + } + if let Some(initramfs_range) = &init_state.initramfs { + node.props + .insert("linux,initrd-start", PropVal::U64(initramfs_range.start)); + node.props + .insert("linux,initrd-end", PropVal::U64(initramfs_range.end)); + } + if let Some(mmap_size) = uefi_mmap_size { + let mmap_start = UEFI_START + size_of::() as u64; + node.props + .insert("linux,uefi-system-table", PropVal::U64(UEFI_START)); + node.props + .insert("linux,uefi-mmap-start", PropVal::U64(mmap_start)); + node.props + .insert("linux,uefi-mmap-size", PropVal::U32(mmap_size as u32)); + node.props.insert( + "linux,uefi-mmap-desc-size", + PropVal::U32(size_of::() as u32), + ); + node.props.insert( + "linux,uefi-mmap-desc-ver", + PropVal::U32(EFI_MEMORY_DESCRIPTOR_VERSION), + ); + } else { + node.props.insert( + "stdout-path", + PropVal::String(format!("/pl011@{PL011_START:x}")), + ); + } + root.nodes.push(("chosen".to_owned(), node)); + } + + pub fn create_memory_node(&self, root: &mut Node) { + let regions = self.memory.mem_region_entries(); + for (start, region) in regions { + if region.type_ != MemRegionType::Ram { + continue; + }; + let node = Node { + props: HashMap::from([ + ("device_type", PropVal::Str("memory")), + ("reg", PropVal::U64List(vec![start, region.size])), + ]), + nodes: Vec::new(), + }; + root.nodes.push((format!("memory@{start:x}"), node)); + } + } + + pub fn create_cpu_nodes(&self, root: &mut Node) { + let topology = &self.spec.cpu.topology; + + let thread_node = |socket_id: u8, core_id: u16, thread_id: u8| { + let phandle = PHANDLE_CPU | topology.decode(socket_id, core_id, thread_id) as u32; + Node { + props: HashMap::from([("cpu", PropVal::PHandle(phandle))]), + nodes: Vec::new(), + } + }; + let core_node = |socket_id: u8, core_id: u16| { + if topology.smt { + Node { + props: HashMap::new(), + nodes: vec![ + ("thread0".to_owned(), thread_node(socket_id, core_id, 0)), + ("thread1".to_owned(), thread_node(socket_id, core_id, 1)), + ], + } + } else { + thread_node(socket_id, core_id, 0) + } + }; + let socket_node = |socket_id: u8| Node { + props: HashMap::new(), + nodes: vec![( + "cluster0".to_owned(), + Node { + props: HashMap::new(), + nodes: (0..topology.cores) + .map(|core_id| (format!("core{core_id}"), core_node(socket_id, core_id))) + .collect(), + }, + )], + }; + let cpu_map_node = Node { + props: HashMap::new(), + nodes: (0..topology.sockets) + .map(|socket_id| (format!("socket{socket_id}"), socket_node(socket_id))) + .collect(), + }; + + let cpus_nodes = (0..(self.spec.cpu.count)) + .map(|index| { + let mpidr = self.encode_cpu_identity(index); + ( + format!("cpu@{mpidr:x}"), + Node { + props: HashMap::from([ + ("device_type", PropVal::Str("cpu")), + ("compatible", PropVal::Str("arm,arm-v8")), + ("enable-method", PropVal::Str("psci")), + ("reg", PropVal::U64(mpidr)), + ("phandle", PropVal::PHandle(PHANDLE_CPU | index as u32)), + ]), + nodes: Vec::new(), + }, + ) + }) + .chain([("cpu-map".to_owned(), cpu_map_node)]) + .collect(); + + let cpus = Node { + props: HashMap::from([ + ("#address-cells", PropVal::U32(2)), + ("#size-cells", PropVal::U32(0)), + ]), + nodes: cpus_nodes, + }; + root.nodes.push(("cpus".to_owned(), cpus)); + } + + fn create_clock_node(&self, root: &mut Node) { + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("fixed-clock")), + ("clock-frequency", PropVal::U32(24000000)), + ("clock-output-names", PropVal::Str("clk24mhz")), + ("phandle", PropVal::PHandle(PHANDLE_CLOCK)), + ("#clock-cells", PropVal::U32(0)), + ]), + nodes: Vec::new(), + }; + root.nodes.push(("apb-pclk".to_owned(), node)); + } + + fn create_pl011_node(&self, root: &mut Node) { + let pin = 1; + let edge_trigger = 1; + let spi = 0; + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,primecell\0arm,pl011")), + ("reg", PropVal::U64List(vec![PL011_START, 0x1000])), + ("interrupts", PropVal::U32List(vec![spi, pin, edge_trigger])), + ("clock-names", PropVal::Str("uartclk\0apb_pclk")), + ( + "clocks", + PropVal::U32List(vec![PHANDLE_CLOCK, PHANDLE_CLOCK]), + ), + ]), + nodes: Vec::new(), + }; + root.nodes.push((format!("pl011@{PL011_START:x}"), node)); + } + + fn create_pl031_node(&self, root: &mut Node) { + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,primecell\0arm,pl031")), + ("reg", PropVal::U64List(vec![PL031_START, 0x1000])), + ("clock-names", PropVal::Str("apb_pclk")), + ("clocks", PropVal::U32List(vec![PHANDLE_CLOCK])), + ]), + nodes: Vec::new(), + }; + root.nodes.push((format!("pl031@{PL031_START:x}"), node)); + } + + // Documentation/devicetree/bindings/timer/arm,arch_timer.yaml + fn create_timer_node(&self, root: &mut Node) { + let mut interrupts = vec![]; + let irq_pins = [ + PPI_TIMER_SECURE_EL1_PHYS, + PPI_TIMER_NON_SECURE_EL1_PHYS, + PPI_TIMER_VIRTUAL_EL1, + PPI_TIMER_NON_SECURE_EL2_PHYS, + ]; + let ppi = 1; + let level_trigger = 4; + let cpu_mask = match self.arch.gic { + Gic::V2(_) => (1 << self.spec.cpu.count) - 1, + Gic::V3 { .. } => 0, + }; + for pin in irq_pins { + interrupts.extend([ppi, pin, (cpu_mask << 8) | level_trigger]); + } + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,armv8-timer")), + ("interrupts", PropVal::U32List(interrupts)), + ("always-on", PropVal::Empty), + ]), + nodes: Vec::new(), + }; + root.nodes.push(("timer".to_owned(), node)); + } + + fn create_gic_msi_node(&self) -> Vec<(String, Node)> { + let Some(msi) = &self.arch.msi else { + return Vec::new(); + }; + match msi { + Msi::Its(_) => { + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,gic-v3-its")), + ("msi-controller", PropVal::Empty), + ("#msi-cells", PropVal::U32(1)), + ("reg", PropVal::U64List(vec![GIC_MSI_START, 128 << 10])), + ("phandle", PropVal::PHandle(PHANDLE_MSI)), + ]), + nodes: Vec::new(), + }; + vec![(format!("its@{GIC_MSI_START:x}"), node)] + } + Msi::V2m(_) => { + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,gic-v2m-frame")), + ("msi-controller", PropVal::Empty), + ("reg", PropVal::U64List(vec![GIC_MSI_START, 64 << 10])), + ("phandle", PropVal::PHandle(PHANDLE_MSI)), + ]), + nodes: Vec::new(), + }; + vec![(format!("v2m@{GIC_MSI_START:x}"), node)] + } + } + } + + fn create_gic_node(&self, root: &mut Node) { + let msi = self.create_gic_msi_node(); + let node = match self.arch.gic { + // Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + Gic::V2(_) => Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,cortex-a15-gic")), + ("#interrupt-cells", PropVal::U32(3)), + ( + "reg", + PropVal::U64List(vec![ + GIC_DIST_START, + 0x1000, + GIC_V2_CPU_INTERFACE_START, + 0x2000, + ]), + ), + ("phandle", PropVal::U32(PHANDLE_GIC)), + ("interrupt-controller", PropVal::Empty), + ]), + nodes: msi, + }, + // Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml + Gic::V3(_) => Node { + props: HashMap::from([ + ("compatible", PropVal::Str("arm,gic-v3")), + ("#interrupt-cells", PropVal::U32(3)), + ("#address-cells", PropVal::U32(2)), + ("#size-cells", PropVal::U32(2)), + ("interrupt-controller", PropVal::Empty), + ("ranges", PropVal::Empty), + ( + "reg", + PropVal::U64List(vec![ + GIC_DIST_START, + 64 << 10, + GIC_V3_REDIST_START, + self.spec.cpu.count as u64 * (128 << 10), + ]), + ), + ("phandle", PropVal::U32(PHANDLE_GIC)), + ]), + nodes: msi, + }, + }; + root.nodes.push((format!("intc@{GIC_DIST_START:x}"), node)); + } + + // Documentation/devicetree/bindings/arm/psci.yaml + fn create_psci_node(&self, root: &mut Node) { + let node = Node { + props: HashMap::from([ + ("method", PropVal::Str("hvc")), + ("compatible", PropVal::Str("arm,psci-0.2\0arm,psci")), + ]), + nodes: Vec::new(), + }; + root.nodes.push(("psci".to_owned(), node)); + } + + // https://elinux.org/Device_Tree_Usage#PCI_Host_Bridge + // Documentation/devicetree/bindings/pci/host-generic-pci.yaml + // IEEE Std 1275-1994 + fn create_pci_bridge_node(&self, root: &mut Node) { + let Some(max_bus) = self.pci_bus.segment.max_bus() else { + return; + }; + let pcie_mmio_64_start = self.spec.pcie_mmio_64_start(); + let prefetchable = 1 << 30; + let io = 0b01 << 24; + let mem_32 = 0b10 << 24; + let mem_64 = 0b11 << 24; + let node = Node { + props: HashMap::from([ + ("compatible", PropVal::Str("pci-host-ecam-generic")), + ("device_type", PropVal::Str("pci")), + ("reg", PropVal::U64List(vec![PCIE_CONFIG_START, 256 << 20])), + ("bus-range", PropVal::U32List(vec![0, max_bus as u32])), + ("#address-cells", PropVal::U32(3)), + ("#size-cells", PropVal::U32(2)), + ( + "ranges", + PropVal::U32List(vec![ + io, + 0, + 0, + 0, + IO_START as u32, + 0, + (IO_END - IO_START) as u32, + mem_32 | prefetchable, + 0, + PCIE_MMIO_32_PREFETCHABLE_START as u32, + 0, + PCIE_MMIO_32_PREFETCHABLE_START as u32, + 0, + (PCIE_MMIO_32_PREFETCHABLE_END - PCIE_MMIO_32_PREFETCHABLE_START) as u32, + mem_32, + 0, + PCIE_MMIO_32_NON_PREFETCHABLE_START as u32, + 0, + PCIE_MMIO_32_NON_PREFETCHABLE_START as u32, + 0, + (PCIE_MMIO_32_NON_PREFETCHABLE_END - PCIE_MMIO_32_NON_PREFETCHABLE_START) + as u32, + mem_64 | prefetchable, + (pcie_mmio_64_start >> 32) as u32, + pcie_mmio_64_start as u32, + (pcie_mmio_64_start >> 32) as u32, + pcie_mmio_64_start as u32, + (PCIE_MMIO_64_SIZE >> 32) as u32, + PCIE_MMIO_64_SIZE as u32, + ]), + ), + ( + "msi-map", + // Identity map from RID (BDF) to msi-specifier. + // Documentation/devicetree/bindings/pci/pci-msi.txt + PropVal::U32List(vec![0, PHANDLE_MSI, 0, 0x10000]), + ), + ]), + nodes: Vec::new(), + }; + root.nodes + .push((format!("pci@{PCIE_CONFIG_START:x}"), node)); + } + + pub fn create_firmware_data(&self, init_state: &InitState) -> Result<()> { + let ram = self.memory.ram_bus(); + let mut device_tree = DeviceTree::new(); + let root = &mut device_tree.root; + root.props.insert("#address-cells", PropVal::U32(2)); + root.props.insert("#size-cells", PropVal::U32(2)); + + if self.spec.platform.acpi { + let mut acpi_table = self.create_acpi(); + let rsdp_size = size_of::(); + let tables_len = acpi_table.tables().len(); + assert!((rsdp_size + tables_len) as u64 <= ACPI_LIMIT); + let tables_gpa = ACPI_START + rsdp_size as u64; + acpi_table.relocate(tables_gpa); + acpi_table.update_checksums(); + ram.write_range(ACPI_START, rsdp_size as u64, acpi_table.rsdp().as_bytes())?; + ram.write_range(tables_gpa, tables_len as u64, acpi_table.tables())?; + + let (uefi_tables, mmap) = self.create_uefi(); + let uefi_tables_size = size_of::(); + let mmap_bytes = mmap.as_bytes(); + assert!((uefi_tables_size + mmap_bytes.len()) as u64 <= UEFI_LIMIT); + let mmap_gpa = UEFI_START + uefi_tables_size as u64; + ram.write_range(UEFI_START, uefi_tables_size as u64, uefi_tables.as_bytes())?; + ram.write_range(mmap_gpa, mmap_bytes.len() as u64, mmap_bytes)?; + + self.create_chosen_node(init_state, Some(mmap_bytes.len()), root); + } else { + root.props.insert("model", PropVal::Str("linux,dummy-virt")); + root.props + .insert("compatible", PropVal::Str("linux,dummy-virt")); + root.props + .insert("interrupt-parent", PropVal::PHandle(PHANDLE_GIC)); + + self.create_chosen_node(init_state, None, root); + self.create_pl011_node(root); + self.create_pl031_node(root); + self.create_memory_node(root); + self.create_cpu_nodes(root); + self.create_gic_node(root); + if self.arch.msi.is_some() { + self.create_pci_bridge_node(root); + } + self.create_clock_node(root); + self.create_timer_node(root); + self.create_psci_node(root); + } + + log::debug!("device tree: {device_tree:#x?}"); + let blob = device_tree.to_blob(); + assert!(blob.len() as u64 <= DEVICE_TREE_LIMIT); + ram.write_range(DEVICE_TREE_START, blob.len() as u64, &*blob)?; + Ok(()) + } +} + +const PHANDLE_GIC: u32 = 1; +const PHANDLE_CLOCK: u32 = 2; +const PHANDLE_MSI: u32 = 3; +const PHANDLE_CPU: u32 = 1 << 31; + +// ARMv8 Generic Timer PPI (Private Peripheral Interrupt) numbers (0-based within PPI range), +// as specified by Arm Server Base System Architecture (SBSA) and +// Documentation/devicetree/bindings/timer/arm,arch_timer.yaml. +const PPI_TIMER_NON_SECURE_EL2_PHYS: u32 = 10; // CNTHP (hyp-phys) +const PPI_TIMER_VIRTUAL_EL1: u32 = 11; // CNTV (virt) +const PPI_TIMER_VIRTUAL_EL2: u32 = 12; // CNTHV (hyp-virt, ARMv8.1 FEAT_VHE) +const PPI_TIMER_SECURE_EL1_PHYS: u32 = 13; // CNTPS (sec-phys) +const PPI_TIMER_NON_SECURE_EL1_PHYS: u32 = 14; // CNTP (phys) + +// Converts a 0-based PPI index (0..16) to an ARM GIC hardware INTID / ACPI GSIV (16..32). +// In ARM GIC, INTIDs 0..16 are SGIs, 16..32 are PPIs, and 32..1020 are SPIs. +const fn ppi_to_gsiv(ppi: u32) -> u32 { + 16 + ppi +} + +const DSDT_TEMPLATE: [u8; 403] = [ + 0x44, 0x53, 0x44, 0x54, 0x93, 0x01, 0x00, 0x00, 0x02, 0x54, 0x41, 0x4C, 0x49, 0x4F, 0x54, 0x48, + 0x41, 0x4C, 0x49, 0x4F, 0x54, 0x48, 0x56, 0x4D, 0x01, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x54, 0x4C, + 0x12, 0x12, 0x25, 0x20, 0x5B, 0x82, 0x47, 0x04, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, + 0x30, 0x08, 0x5F, 0x48, 0x49, 0x44, 0x0D, 0x41, 0x52, 0x4D, 0x48, 0x30, 0x30, 0x31, 0x31, 0x00, + 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F, 0x53, 0x54, 0x41, 0x0A, 0x0F, 0x08, 0x5F, 0x43, + 0x52, 0x53, 0x11, 0x1A, 0x0A, 0x17, 0x86, 0x09, 0x00, 0x01, 0x00, 0xF0, 0xFF, 0x2F, 0x00, 0x10, + 0x00, 0x00, 0x89, 0x06, 0x00, 0x03, 0x01, 0x21, 0x00, 0x00, 0x00, 0x79, 0x00, 0x5B, 0x82, 0x44, + 0x12, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, + 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x03, 0x08, 0x5F, + 0x53, 0x45, 0x47, 0x00, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F, 0x43, 0x43, 0x41, 0x01, + 0x14, 0x32, 0x5F, 0x44, 0x53, 0x4D, 0x04, 0xA0, 0x29, 0x93, 0x68, 0x11, 0x13, 0x0A, 0x10, 0xD0, + 0x37, 0xC9, 0xE5, 0x53, 0x35, 0x7A, 0x4D, 0x91, 0x17, 0xEA, 0x4D, 0x19, 0xC3, 0x43, 0x4D, 0xA0, + 0x09, 0x93, 0x6A, 0x00, 0xA4, 0x11, 0x03, 0x01, 0x21, 0xA0, 0x07, 0x93, 0x6A, 0x0A, 0x05, 0xA4, + 0x00, 0xA4, 0x00, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x42, 0x09, 0x0A, 0x8E, 0x88, 0x0D, 0x00, + 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x87, 0x17, 0x00, + 0x00, 0x0C, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xDF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x87, 0x17, 0x00, 0x00, 0x0C, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x87, + 0x17, 0x00, 0x01, 0x0C, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x00, 0x01, 0x00, 0x79, 0x00, 0x5B, 0x82, 0x26, 0x52, 0x45, + 0x53, 0x30, 0x08, 0x5F, 0x48, 0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0C, 0x02, 0x08, 0x5F, 0x43, 0x52, + 0x53, 0x11, 0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x10, 0x79, 0x00, +]; + +const DSDT_OFFSET_PCI_QWORD_MEM: usize = 0x12f; + +const AML_CPU_TEMPLATE: [u8; 42] = [ + 0x5B, 0x82, 0x28, 0x2E, 0x5F, 0x53, 0x42, 0x5F, // Device (_SB.C000) + 0x43, 0x30, 0x30, 0x30, // "C000" + 0x08, 0x5F, 0x48, 0x49, 0x44, 0x0D, 0x41, 0x43, 0x50, 0x49, 0x30, 0x30, 0x30, 0x37, + 0x00, // Name (_HID, "ACPI0007") + 0x08, 0x5F, 0x55, 0x49, 0x44, 0x0B, 0x00, 0x00, // Name (_UID, 0x0000) + 0x08, 0x5F, 0x53, 0x54, 0x41, 0x0A, 0x0F, // Name (_STA, 0x0F) +]; + +#[cfg(test)] +#[path = "board_arm64_test.rs"] +mod tests; diff --git a/alioth/src/board/board_arm64_test.rs b/alioth/src/board/board_arm64/board_arm64_test.rs similarity index 87% rename from alioth/src/board/board_arm64_test.rs rename to alioth/src/board/board_arm64/board_arm64_test.rs index 070fb8d8..22c84d24 100644 --- a/alioth/src/board/board_arm64_test.rs +++ b/alioth/src/board/board_arm64/board_arm64_test.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::mem::size_of; + use rstest::rstest; use crate::arch::reg::MpidrEl1; use crate::board::CpuTopology; -use crate::board::aarch64::encode_mpidr; +use crate::board::aarch64::{StaticUefiTables, encode_mpidr}; #[rstest] #[case(CpuTopology{smt: false, cores: 1, sockets: 1, ..Default::default()}, 1, 1)] @@ -25,3 +27,8 @@ use crate::board::aarch64::encode_mpidr; fn test_encode_mpidr(#[case] topology: CpuTopology, #[case] index: u16, #[case] mpidr: u64) { assert_eq!(encode_mpidr(&topology, index), MpidrEl1(mpidr)); } + +#[test] +fn test_size() { + assert_eq!(size_of::(), 232); +} diff --git a/alioth/src/board/board_arm64/dsdt_arm64.dsl b/alioth/src/board/board_arm64/dsdt_arm64.dsl new file mode 100644 index 00000000..377022cd --- /dev/null +++ b/alioth/src/board/board_arm64/dsdt_arm64.dsl @@ -0,0 +1,125 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +DefinitionBlock ("dsdt.aml", "DSDT", 2, "ALIOTH", "ALIOTHVM", 0x00000001) +{ + Device (_SB.COM0) + { + Name (_HID, "ARMH0011") + Name (_UID, Zero) + Name (_STA, 0x0F) + Name (_CRS, ResourceTemplate () + { + Memory32Fixed (ReadWrite, + 0x2FFFF000, + 0x00001000, + ) + Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, ) + { + 0x00000021, + } + }) + } + + Device (_SB.PCI0) + { + Name (_HID, EisaId ("PNP0A08")) + Name (_CID, EisaId ("PNP0A03")) + Name (_SEG, Zero) + Name (_UID, Zero) + Name (_CCA, One) + Method (_DSM, 4, NotSerialized) + { + // Arg0: UUID of function + // Arg1: Revision + // Arg2: function index + + // PCI Firmware Spec 3.1, + // Sec. 4.6. _DSM Definitions for PCI + If ((Arg0 == ToUUID ("e5c937d0-3553-4d7a-9117-ea4d19c3434d"))) + { + // Function 0, returns the function bit map + If ((Arg2 == Zero)) + { + Return (Buffer (One) + { + // 0b100001, supports function 0 and function 5 + 0x21 + }) + } + + // PCI Firmware Spec 3.1, + // Sec. 4.6.5 _DSM for Preserving PCI Boot Configurations + If ((Arg2 == 0x05)) + { + // OS preserves resource assignment + Return (Zero) + } + } + + Return (Zero) + } + + Name (_CRS, ResourceTemplate () + { + WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0001, + ,, ) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Prefetchable, ReadWrite, + 0x00000000, + 0xC0000000, + 0xDFFFFFFF, + 0x00000000, + 0x20000000, + ,, , AddressRangeMemory, TypeStatic) + DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite, + 0x00000000, + 0xE0000000, + 0xFFFFFFFF, + 0x00000000, + 0x20000000, + ,, , AddressRangeMemory, TypeStatic) + QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Prefetchable, ReadWrite, + 0x0000000000000000, + 0x0000000100000000, + 0x00000100FFFFFFFF, + 0x0000000000000000, + 0x0000010000000000, + ,, , AddressRangeMemory, TypeStatic) + DWordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange, + 0x00000000, + 0x00000000, + 0x0000FFFF, + 0x0FFF0000, + 0x00010000, + ,, , TypeTranslation, DenseTranslation) + }) + + Device (RES0) + { + Name (_HID, EisaId ("PNP0C02")) + Name (_CRS, ResourceTemplate () + { + Memory32Fixed (ReadWrite, + 0x30000000, + 0x10000000, + ) + }) + } + } +} diff --git a/alioth/src/firmware/acpi/acpi.rs b/alioth/src/firmware/acpi/acpi.rs index 1d90e0fd..99570c4e 100644 --- a/alioth/src/firmware/acpi/acpi.rs +++ b/alioth/src/firmware/acpi/acpi.rs @@ -15,188 +15,13 @@ pub mod bindings; pub mod reg; -use std::mem::{offset_of, size_of}; +use std::mem::offset_of; use zerocopy::{FromBytes, IntoBytes, transmute}; -#[cfg(target_arch = "x86_64")] -use crate::arch::layout::{ - APIC_START, IOAPIC_START, PORT_ACPI_RESET, PORT_ACPI_SLEEP_CONTROL, PORT_ACPI_SLEEP_STATUS, -}; -use crate::arch::layout::{PCIE_CONFIG_START, PORT_ACPI_TIMER}; -use crate::firmware::acpi::bindings::AcpiFadtFlag; use crate::utils::wrapping_sum; -use self::bindings::{ - AcpiGenericAddress, AcpiMadtIoApic, AcpiMadtLocalX2apic, AcpiMcfgAllocation, - AcpiSubtableHeader, AcpiTableFadt, AcpiTableHeader, AcpiTableMadt, AcpiTableMcfg1, - AcpiTableRsdp, AcpiTableXsdt3, FADT_MAJOR_VERSION, FADT_MINOR_VERSION, MADT_IO_APIC, - MADT_LOCAL_X2APIC, MADT_REVISION, MCFG_REVISION, RSDP_REVISION, SIG_FADT, SIG_MADT, SIG_MCFG, - SIG_RSDP, SIG_XSDT, XSDT_REVISION, -}; -use self::reg::FADT_RESET_VAL; - -const OEM_ID: [u8; 6] = *b"ALIOTH"; - -fn default_header() -> AcpiTableHeader { - AcpiTableHeader { - checksum: 0, - oem_id: OEM_ID, - oem_table_id: *b"ALIOTHVM", - oem_revision: 1, - asl_compiler_id: *b"ALTH", - asl_compiler_revision: 1, - ..Default::default() - } -} - -// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure -pub fn create_rsdp(xsdt_addr: u64) -> AcpiTableRsdp { - AcpiTableRsdp { - signature: SIG_RSDP, - oem_id: OEM_ID, - revision: RSDP_REVISION, - length: size_of::() as u32, - xsdt_physical_address: transmute!(xsdt_addr), - ..Default::default() - } -} - -// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#extended-system-description-table-fields-xsdt -pub fn create_xsdt(entries: [u64; 3]) -> AcpiTableXsdt3 { - let total_length = size_of::() + size_of::() * 3; - let entries = entries.map(|e| transmute!(e)); - AcpiTableXsdt3 { - header: AcpiTableHeader { - signature: SIG_XSDT, - length: total_length as u32, - revision: XSDT_REVISION, - ..default_header() - }, - entries, - } -} - -// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#fadt-format -pub fn create_fadt(dsdt_addr: u64) -> AcpiTableFadt { - AcpiTableFadt { - header: AcpiTableHeader { - signature: SIG_FADT, - revision: FADT_MAJOR_VERSION, - length: size_of::() as u32, - ..default_header() - }, - reset_register: AcpiGenericAddress { - space_id: 1, - bit_width: 8, - bit_offset: 0, - access_width: 1, - address: transmute!(PORT_ACPI_RESET as u64), - }, - reset_value: FADT_RESET_VAL, - xpm_timer_block: AcpiGenericAddress { - space_id: 1, - bit_width: 32, - bit_offset: 0, - access_width: 3, - address: transmute!(PORT_ACPI_TIMER as u64), - }, - sleep_control: AcpiGenericAddress { - space_id: 1, - bit_width: 8, - bit_offset: 0, - access_width: 1, - address: transmute!(PORT_ACPI_SLEEP_CONTROL as u64), - }, - sleep_status: AcpiGenericAddress { - space_id: 1, - bit_width: 8, - bit_offset: 0, - access_width: 1, - address: transmute!(PORT_ACPI_SLEEP_STATUS as u64), - }, - flags: AcpiFadtFlag::HW_REDUCED_ACPI - | AcpiFadtFlag::RESET_REG_SUP - | AcpiFadtFlag::TMR_VAL_EXT, - minor_revision: FADT_MINOR_VERSION, - hypervisor_id: *b"ALIOTH ", - xdsdt: transmute!(dsdt_addr), - ..Default::default() - } -} - -// https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#multiple-apic-description-table-madt -#[cfg(target_arch = "x86_64")] -pub fn create_madt(apic_ids: &[u32]) -> (AcpiTableMadt, AcpiMadtIoApic, Vec) { - let total_length = size_of::() - + size_of::() - + apic_ids.len() * size_of::(); - let mut checksum = 0u8; - - let mut madt = AcpiTableMadt { - header: AcpiTableHeader { - signature: SIG_MADT, - length: total_length as u32, - revision: MADT_REVISION, - ..default_header() - }, - address: APIC_START as u32, - flags: 0, - }; - checksum = checksum.wrapping_sub(wrapping_sum(madt.as_bytes())); - - let io_apic = AcpiMadtIoApic { - header: AcpiSubtableHeader { - type_: MADT_IO_APIC, - length: size_of::() as u8, - }, - id: 0, - address: IOAPIC_START as u32, - global_irq_base: 0, - ..Default::default() - }; - checksum = checksum.wrapping_sub(wrapping_sum(io_apic.as_bytes())); - - let mut x2apics = vec![]; - for (index, apic_id) in apic_ids.iter().enumerate() { - let x2apic = AcpiMadtLocalX2apic { - header: AcpiSubtableHeader { - type_: MADT_LOCAL_X2APIC, - length: size_of::() as u8, - }, - local_apic_id: *apic_id, - uid: index as u32, - lapic_flags: 1, - ..Default::default() - }; - checksum = checksum.wrapping_sub(wrapping_sum(x2apic.as_bytes())); - x2apics.push(x2apic); - } - madt.header.checksum = checksum; - - (madt, io_apic, x2apics) -} - -pub fn create_mcfg() -> AcpiTableMcfg1 { - let mut mcfg = AcpiTableMcfg1 { - header: AcpiTableHeader { - signature: SIG_MCFG, - length: size_of::() as u32, - revision: MCFG_REVISION, - ..default_header() - }, - reserved: [0; 8], - allocations: [AcpiMcfgAllocation { - address: transmute!(PCIE_CONFIG_START), - pci_segment: 0, - start_bus_number: 0, - end_bus_number: 0, - ..Default::default() - }], - }; - mcfg.header.checksum = 0u8.wrapping_sub(wrapping_sum(mcfg.as_bytes())); - mcfg -} +use self::bindings::{AcpiTableHeader, AcpiTableRsdp}; pub struct AcpiTable { pub(crate) rsdp: AcpiTableRsdp, diff --git a/alioth/src/firmware/acpi/bindings.rs b/alioth/src/firmware/acpi/bindings.rs index f7e4fc6a..82baa4ea 100644 --- a/alioth/src/firmware/acpi/bindings.rs +++ b/alioth/src/firmware/acpi/bindings.rs @@ -15,15 +15,23 @@ use bitfield::bitfield; use zerocopy::{FromBytes, Immutable, IntoBytes}; -use crate::bitflags; +use crate::{bitflags, consts}; pub const SIG_RSDP: [u8; 8] = *b"RSD PTR "; -pub const SIG_XSDT: [u8; 4] = *b"XSDT"; -pub const SIG_FADT: [u8; 4] = *b"FACP"; -pub const SIG_MADT: [u8; 4] = *b"APIC"; -pub const SIG_MCFG: [u8; 4] = *b"MCFG"; -#[allow(dead_code)] -pub const SIG_DSDT: [u8; 4] = *b"DSDT"; + +consts! { + pub struct AcpiSignature([u8; 4]) { + XSDT = *b"XSDT"; + FADT = *b"FACP"; + MADT = *b"APIC"; + MCFG = *b"MCFG"; + GTDT = *b"GTDT"; + IORT = *b"IORT"; + SPCR = *b"SPCR"; + PPTT = *b"PPTT"; + DSDT = *b"DSDT"; + } +} pub const RSDP_REVISION: u8 = 2; @@ -44,7 +52,7 @@ pub struct AcpiTableRsdp { #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiTableHeader { - pub signature: [u8; 4], + pub signature: AcpiSignature, pub length: u32, pub revision: u8, pub checksum: u8, @@ -71,13 +79,56 @@ pub struct AcpiTableXsdt3 { pub entries: [[u32; 2]; 3], } +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTableXsdt6 { + pub header: AcpiTableHeader, + pub entries: [[u32; 2]; 6], +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTableXsdt7 { + pub header: AcpiTableHeader, + pub entries: [[u32; 2]; 7], +} + +consts! { + #[derive(zerocopy::Unaligned)] + pub struct AcpiSpaceId(u8) { + SYSTEM_MEMORY = 0x00; + SYSTEM_IO = 0x01; + PCI_CONFIG = 0x02; + EMBEDDED_CONTROLLER = 0x03; + SMBUS = 0x04; + SYSTEM_CMOS = 0x05; + PCI_BAR_TARGET = 0x06; + IPMI = 0x07; + GPIO = 0x08; + GENERIC_SERIAL_BUS = 0x09; + PCC = 0x0A; + FUNCTIONAL_FIXED_HARDWARE = 0x7F; + } +} + +consts! { + #[derive(zerocopy::Unaligned)] + pub struct AcpiAccessWidth(u8) { + UNDEFINED = 0; + BYTE = 1; + WORD = 2; + DWORD = 3; + QWORD = 4; + } +} + #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiGenericAddress { - pub space_id: u8, + pub space_id: AcpiSpaceId, pub bit_width: u8, pub bit_offset: u8, - pub access_width: u8, + pub access_width: AcpiAccessWidth, pub address: [u32; 2], } @@ -92,6 +143,13 @@ bitflags! { } } +bitflags! { + pub struct AcpiFadtArmBootFlag(u8) { + PSCI_COMPLIANT = 1 << 0; + PSCI_USE_HVC = 1 << 1; + } +} + #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiTableFadt { @@ -137,7 +195,7 @@ pub struct AcpiTableFadt { pub flags: AcpiFadtFlag, pub reset_register: AcpiGenericAddress, pub reset_value: u8, - pub arm_boot_flags: u8, + pub arm_boot_flags: AcpiFadtArmBootFlag, pub arm_boot_flags_hi: u8, pub minor_revision: u8, pub xfacs: [u32; 2], @@ -165,20 +223,30 @@ pub struct AcpiTableMadt { pub flags: u32, } -pub const MADT_IO_APIC: u8 = 1; -pub const MADT_LOCAL_X2APIC: u8 = 9; +consts! { + #[derive(zerocopy::Unaligned)] + pub struct AcpiMadtType(u8) { + IO_APIC = 1; + LOCAL_X2APIC = 9; + GENERIC_INTERRUPT = 11; + GENERIC_DISTRIBUTOR = 12; + GENERIC_MSI_FRAME = 13; + GENERIC_REDISTRIBUTOR = 14; + GENERIC_TRANSLATOR = 15; + } +} #[repr(C)] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] -pub struct AcpiSubtableHeader { - pub type_: u8, +pub struct AcpiSubtableHeader { + pub r#type: T, pub length: u8, } #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiMadtLocalX2apic { - pub header: AcpiSubtableHeader, + pub header: AcpiSubtableHeader, pub reserved: u16, pub local_apic_id: u32, pub lapic_flags: u32, @@ -188,13 +256,89 @@ pub struct AcpiMadtLocalX2apic { #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiMadtIoApic { - pub header: AcpiSubtableHeader, + pub header: AcpiSubtableHeader, pub id: u8, pub reserved: u8, pub address: u32, pub global_irq_base: u32, } +bitflags! { + pub struct AcpiMadtGiccFlag(u32) { + ENABLED = 1 << 0; + PERFORMANCE_INTERRUPT_EDGE_TRIGGERED = 1 << 1; + VGIC_INTERRUPT_EDGE_TRIGGERED = 1 << 2; + ONLINE_CAPABLE = 1 << 3; + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiMadtGenericInterrupt { + pub header: AcpiSubtableHeader, + pub reserved: u16, + pub cpu_interface_number: u32, + pub uid: u32, + pub flags: AcpiMadtGiccFlag, + pub parking_version: u32, + pub performance_interrupt: u32, + pub parked_address: [u32; 2], + pub base_address: [u32; 2], + pub gicv_base_address: [u32; 2], + pub gich_base_address: [u32; 2], + pub vgic_interrupt: u32, + pub gicr_base_address: [u32; 2], + pub arm_mpidr: [u32; 2], + pub efficiency_class: u8, + pub reserved2: u8, + pub spe_interrupt: u16, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiMadtGenericDistributor { + pub header: AcpiSubtableHeader, + pub reserved: u16, + pub gic_id: u32, + pub base_address: [u32; 2], + pub global_irq_base: u32, + pub version: u8, + pub reserved2: [u8; 3], +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiMadtGenericMsiFrame { + pub header: AcpiSubtableHeader, + pub reserved: u16, + pub msi_frame_id: u32, + pub base_address: [u32; 2], + pub flags: u32, + pub spi_count: u16, + pub spi_base: u16, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiMadtGenericRedistributor { + pub header: AcpiSubtableHeader, + pub flags: u8, + pub reserved: u8, + pub base_address: [u32; 2], + pub length: u32, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiMadtGenericTranslator { + pub header: AcpiSubtableHeader, + pub flags: u8, + pub reserved: u8, + pub translation_id: u32, + pub base_address: [u32; 2], + pub reserved2: u32, +} + #[repr(C, align(4))] #[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] pub struct AcpiMcfgAllocation { @@ -231,6 +375,191 @@ pub struct AcpiTableMcfg3 { pub allocations: [AcpiMcfgAllocation; 3], } +pub const GTDT_REVISION: u8 = 3; + +bitflags! { + pub struct AcpiGtdtFlag(u32) { + INTERRUPT_MODE_EDGE = 1 << 0; + INTERRUPT_POLARITY_ACTIVE_LOW = 1 << 1; + ALWAYS_ON = 1 << 2; + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTableGtdt { + pub header: AcpiTableHeader, + pub counter_block_address: [u32; 2], + pub reserved: u32, + pub secure_el1_interrupt: u32, + pub secure_el1_flags: AcpiGtdtFlag, + pub non_secure_el1_interrupt: u32, + pub non_secure_el1_flags: AcpiGtdtFlag, + pub virtual_timer_interrupt: u32, + pub virtual_timer_flags: AcpiGtdtFlag, + pub non_secure_el2_interrupt: u32, + pub non_secure_el2_flags: AcpiGtdtFlag, + pub counter_read_block_address: [u32; 2], + pub platform_timer_count: u32, + pub platform_timer_offset: u32, + pub virtual_el2_timer_gsiv: u32, + pub virtual_el2_timer_flags: AcpiGtdtFlag, +} + +pub const IORT_REVISION: u8 = 5; + +consts! { + #[derive(zerocopy::Unaligned)] + pub struct AcpiIortNodeType(u8) { + ITS_GROUP = 0x00; + NAMED_COMPONENT = 0x01; + PCI_ROOT_COMPLEX = 0x02; + SMMU_V1_V2 = 0x03; + SMMU_V3 = 0x04; + PMCG = 0x05; + RMR = 0x06; + IWB = 0x07; + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTableIort { + pub header: AcpiTableHeader, + pub node_count: u32, + pub node_offset: u32, + pub reserved: u32, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiIortNode { + pub type_: AcpiIortNodeType, + pub length: [u8; 2], + pub revision: u8, + pub identifier: u32, + pub mapping_count: u32, + pub mapping_offset: u32, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiIortIdMapping { + pub input_base: u32, + pub id_count: u32, + pub output_base: u32, + pub output_reference: u32, + pub flags: u32, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiIortMemoryAccess { + pub cache_coherency: u32, + pub hints: u8, + pub reserved: [u8; 2], + pub memory_flags: u8, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiIortItsGroup1 { + pub node: AcpiIortNode, + pub its_count: u32, + pub identifiers: [u32; 1], +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiIortRootComplex1 { + pub node: AcpiIortNode, + pub memory_properties: AcpiIortMemoryAccess, + pub ats_attribute: u32, + pub pci_segment_number: u32, + pub memory_address_limit: u8, + pub pasid_capabilities: [u8; 2], + pub reserved: u8, + pub flags: u32, + pub mappings: [AcpiIortIdMapping; 1], +} + +pub const SPCR_REVISION: u8 = 2; +pub const SPCR_INTERFACE_ARM_PL011: u8 = 0x03; + +bitflags! { + pub struct AcpiSpcrInterruptType(u8) { + PC_AT_PIC = 1 << 0; + IO_APIC = 1 << 1; + IO_SAPIC = 1 << 2; + GIC = 1 << 3; + PLIC_APLIC = 1 << 4; + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTableSpcr { + pub header: AcpiTableHeader, + pub interface_type: u8, + pub reserved: [u8; 3], + pub serial_port: AcpiGenericAddress, + pub interrupt_type: AcpiSpcrInterruptType, + pub pc_interrupt: u8, + pub interrupt: [u8; 4], + pub baud_rate: u8, + pub parity: u8, + pub stop_bits: u8, + pub flow_control: u8, + pub terminal_type: u8, + pub language: u8, + pub pci_device_id: u16, + pub pci_vendor_id: u16, + pub pci_bus: u8, + pub pci_device: u8, + pub pci_function: u8, + pub pci_flags: [u8; 4], + pub pci_segment: u8, + pub uart_clk_freq: [u8; 4], +} + +pub const PPTT_REVISION: u8 = 2; + +consts! { + #[derive(zerocopy::Unaligned)] + pub struct AcpiPpttType(u8) { + PROCESSOR = 0; + CACHE = 1; + ID = 2; + } +} + +bitflags! { + pub struct AcpiPpttFlag(u32) { + PHYSICAL_PACKAGE = 1 << 0; + ACPI_PROCESSOR_ID_VALID = 1 << 1; + ACPI_PROCESSOR_IS_THREAD = 1 << 2; + ACPI_LEAF_NODE = 1 << 3; + ACPI_IDENTICAL = 1 << 4; + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiTablePptt { + pub header: AcpiTableHeader, +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Default, FromBytes, Immutable, IntoBytes)] +pub struct AcpiPpttProcessor { + pub header: AcpiSubtableHeader, + pub reserved: u16, + pub flags: AcpiPpttFlag, + pub parent: u32, + pub acpi_processor_id: u32, + pub number_of_priv_resources: u32, +} + bitfield! { /// Sleep Control Register /// diff --git a/alioth/src/firmware/acpi/bindings_test.rs b/alioth/src/firmware/acpi/bindings_test.rs index 2c101e8a..b8677972 100644 --- a/alioth/src/firmware/acpi/bindings_test.rs +++ b/alioth/src/firmware/acpi/bindings_test.rs @@ -15,8 +15,12 @@ use std::mem::size_of; use super::{ - AcpiGenericAddress, AcpiMadtIoApic, AcpiMadtLocalX2apic, AcpiMcfgAllocation, AcpiTableFadt, - AcpiTableHeader, AcpiTableMadt, AcpiTableMcfg1, AcpiTableRsdp, AcpiTableXsdt, + AcpiGenericAddress, AcpiIortIdMapping, AcpiIortItsGroup1, AcpiIortMemoryAccess, AcpiIortNode, + AcpiIortRootComplex1, AcpiMadtGenericDistributor, AcpiMadtGenericInterrupt, + AcpiMadtGenericMsiFrame, AcpiMadtGenericRedistributor, AcpiMadtGenericTranslator, + AcpiMadtIoApic, AcpiMadtLocalX2apic, AcpiMcfgAllocation, AcpiPpttProcessor, AcpiTableFadt, + AcpiTableGtdt, AcpiTableHeader, AcpiTableIort, AcpiTableMadt, AcpiTableMcfg1, AcpiTablePptt, + AcpiTableRsdp, AcpiTableSpcr, AcpiTableXsdt, }; #[test] @@ -28,8 +32,23 @@ fn test_size() { assert_eq!(size_of::(), 44); assert_eq!(size_of::(), 12); assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 20); assert_eq!(size_of::(), 16); assert_eq!(size_of::(), 60); + assert_eq!(size_of::(), 104); + assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 20); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 60); + assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 36); + assert_eq!(size_of::(), 20); assert_eq!(size_of::>(), 36); assert_eq!(size_of::>(), 36 + 4 * 8); } diff --git a/alioth/src/firmware/firmware.rs b/alioth/src/firmware/firmware.rs index 6757a727..1559aad3 100644 --- a/alioth/src/firmware/firmware.rs +++ b/alioth/src/firmware/firmware.rs @@ -13,7 +13,6 @@ // limitations under the License. #[path = "acpi/acpi.rs"] -#[cfg(target_arch = "x86_64")] pub mod acpi; #[path = "dt/dt.rs"] pub mod dt; diff --git a/alioth/src/firmware/ovmf/ovmf_amd64/ovmf_amd64.rs b/alioth/src/firmware/ovmf/ovmf_amd64/ovmf_amd64.rs index 24937a09..2550ae46 100644 --- a/alioth/src/firmware/ovmf/ovmf_amd64/ovmf_amd64.rs +++ b/alioth/src/firmware/ovmf/ovmf_amd64/ovmf_amd64.rs @@ -17,7 +17,7 @@ pub mod tdx; use zerocopy::FromBytes; -pub const GUID_SIZE: usize = 16; +use crate::firmware::uefi::GUID_SIZE; pub const OFFSET_R_LENGTH_GUID_TABLE: usize = 50; pub const GUID_TABLE_FOOTER: [u8; GUID_SIZE] = [ diff --git a/alioth/src/firmware/ovmf/ovmf_amd64/sev.rs b/alioth/src/firmware/ovmf/ovmf_amd64/sev.rs index bf3457c0..c22ff704 100644 --- a/alioth/src/firmware/ovmf/ovmf_amd64/sev.rs +++ b/alioth/src/firmware/ovmf/ovmf_amd64/sev.rs @@ -15,7 +15,8 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::consts; -use crate::firmware::ovmf::x86_64::{GUID_SIZE, parse_data}; +use crate::firmware::ovmf::x86_64::parse_data; +use crate::firmware::uefi::GUID_SIZE; use crate::firmware::{Result, error}; pub const GUID_SEV_ES_RESET_BLOCK: [u8; GUID_SIZE] = [ diff --git a/alioth/src/firmware/ovmf/ovmf_amd64/tdx.rs b/alioth/src/firmware/ovmf/ovmf_amd64/tdx.rs index 31cb145f..060d4eba 100644 --- a/alioth/src/firmware/ovmf/ovmf_amd64/tdx.rs +++ b/alioth/src/firmware/ovmf/ovmf_amd64/tdx.rs @@ -18,9 +18,9 @@ use std::io::Write; use snafu::ResultExt; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; -use crate::firmware::ovmf::x86_64::{GUID_SIZE, parse_data}; +use crate::firmware::ovmf::x86_64::parse_data; use crate::firmware::uefi::{ - HOB_HANDOFF_TABLE_VERSION, HobGenericHeader, HobHandoffInfoTable, HobResourceDesc, + GUID_SIZE, HOB_HANDOFF_TABLE_VERSION, HobGenericHeader, HobHandoffInfoTable, HobResourceDesc, HobResourceType, HobType, ResourceAttr, }; use crate::firmware::{Result, error}; diff --git a/alioth/src/firmware/uefi/uefi.rs b/alioth/src/firmware/uefi/uefi.rs index 320ead07..0ec9e7db 100644 --- a/alioth/src/firmware/uefi/uefi.rs +++ b/alioth/src/firmware/uefi/uefi.rs @@ -16,6 +16,8 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::{bitflags, consts}; +pub const GUID_SIZE: usize = 16; + consts! { pub struct HobType(u16) { HANDOFF = 0x0001; @@ -66,9 +68,137 @@ bitflags! { #[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] pub struct HobResourceDesc { pub hdr: HobGenericHeader, - pub owner: [u8; 16], + pub owner: [u8; GUID_SIZE], pub r#type: HobResourceType, pub attr: ResourceAttr, pub address: u64, pub len: u64, } + +pub const EFI_SYSTEM_TABLE_SIGNATURE: u64 = 0x5453_5953_2049_4249; // "IBI SYST" +pub const EFI_2_100_SYSTEM_TABLE_REVISION: u32 = (2 << 16) | 100; + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct EfiTableHeader { + pub signature: u64, + pub revision: u32, + pub headersize: u32, + pub crc32: u32, + pub reserved: u32, +} + +// 8868e871-e4f1-11d3-bc22-0080c73c8881 +pub const GUID_ACPI_20_TABLE: [u8; GUID_SIZE] = [ + 0x71, 0xe8, 0x68, 0x88, 0xf1, 0xe4, 0xd3, 0x11, 0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81, +]; + +// eb66918a-7eef-402a-842e-931d21c38ae9 +pub const GUID_EFI_RT_PROPERTIES_TABLE: [u8; GUID_SIZE] = [ + 0x8a, 0x91, 0x66, 0xeb, 0xef, 0x7e, 0x2a, 0x40, 0x84, 0x2e, 0x93, 0x1d, 0x21, 0xc3, 0x8a, 0xe9, +]; + +// 888eb0c6-8ede-4ff5-a8f0-9aee5cb977c2 +pub const GUID_LINUX_EFI_MEMRESERVE_TABLE: [u8; GUID_SIZE] = [ + 0xc6, 0xb0, 0x8e, 0x88, 0xde, 0x8e, 0xf5, 0x4f, 0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2, +]; + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct EfiConfigTable64 { + pub guid: [u8; GUID_SIZE], + pub table: u64, +} + +pub const EFI_RT_PROPERTIES_TABLE_VERSION: u16 = 0x1; + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct EfiRtPropertiesTable { + pub version: u16, + pub length: u16, + pub runtime_services_supported: u32, +} + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct LinuxEfiMemreserve { + pub size: i32, + pub count: i32, + pub next: u64, +} + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct EfiSystemTable64 { + pub hdr: EfiTableHeader, + pub fw_vendor: u64, + pub fw_revision: u32, + pub _pad: u32, + pub con_in_handle: u64, + pub con_in: u64, + pub con_out_handle: u64, + pub con_out: u64, + pub stderr_handle: u64, + pub stderr: u64, + pub runtime: u64, + pub boottime: u64, + pub nr_tables: u64, + pub tables: u64, +} + +pub const EFI_MEMORY_DESCRIPTOR_VERSION: u32 = 1; + +consts! { + pub struct EfiMemoryType(u32) { + RESERVED = 0; + LOADER_CODE = 1; + LOADER_DATA = 2; + BOOT_SERVICES_CODE = 3; + BOOT_SERVICES_DATA = 4; + RUNTIME_SERVICES_CODE = 5; + RUNTIME_SERVICES_DATA = 6; + CONVENTIONAL_MEMORY = 7; + UNUSABLE_MEMORY = 8; + ACPI_RECLAIM_MEMORY = 9; + ACPI_MEMORY_NVS = 10; + MMIO = 11; + MMIO_PORT_SPACE = 12; + PAL_CODE = 13; + PERSISTENT_MEMORY = 14; + } +} + +bitflags! { + pub struct EfiMemoryAttribute(u64) { + UC = 1 << 0; + WC = 1 << 1; + WT = 1 << 2; + WB = 1 << 3; + UCE = 1 << 4; + WP = 1 << 12; + RP = 1 << 13; + XP = 1 << 14; + NV = 1 << 15; + MORE_RELIABLE = 1 << 16; + RO = 1 << 17; + SP = 1 << 18; + CPU_CRYPTO = 1 << 19; + RUNTIME = 1 << 63; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct EfiMemoryDesc { + pub ty: EfiMemoryType, + pub pad: u32, + pub phys_addr: u64, + pub virt_addr: u64, + pub num_pages: u64, + pub attribute: EfiMemoryAttribute, +} + +#[cfg(test)] +#[path = "uefi_test.rs"] +mod tests; diff --git a/alioth/src/firmware/uefi/uefi_test.rs b/alioth/src/firmware/uefi/uefi_test.rs new file mode 100644 index 00000000..23e05fde --- /dev/null +++ b/alioth/src/firmware/uefi/uefi_test.rs @@ -0,0 +1,30 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::mem::size_of; + +use super::{ + EfiConfigTable64, EfiMemoryDesc, EfiRtPropertiesTable, EfiSystemTable64, EfiTableHeader, + LinuxEfiMemreserve, +}; + +#[test] +fn test_size() { + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 120); + assert_eq!(size_of::(), 40); +}