From 1e813dd220699ddeffd9174d9a235c522fbb4e7b Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 20 Feb 2026 10:21:17 -0800 Subject: [PATCH 01/14] refactor(board): encapsulate initialization within constructor Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 27 ++++++++++++++++++++------- alioth/src/vm/vm.rs | 19 ++++--------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index 0f23395a..3528c4a7 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -45,7 +45,7 @@ use crate::device::MmioDev; #[cfg(target_arch = "x86_64")] use crate::device::fw_cfg::FwCfg; use crate::errors::{DebugTrace, trace_error}; -use crate::hv::{Coco, Vcpu, Vm, VmEntry, VmExit}; +use crate::hv::{Coco, Hypervisor, Vcpu, Vm, VmConfig, VmEntry, VmExit}; #[cfg(target_arch = "x86_64")] use crate::loader::xen; use crate::loader::{Executable, InitState, Payload, linux}; @@ -58,9 +58,9 @@ use crate::vfio::container::Container; use crate::vfio::iommu::Ioas; #[cfg(target_arch = "aarch64")] -pub(crate) use self::aarch64::ArchBoard; +use self::aarch64::ArchBoard; #[cfg(target_arch = "x86_64")] -pub(crate) use self::x86_64::ArchBoard; +use self::x86_64::ArchBoard; #[trace_error] #[derive(Snafu, DebugTrace)] @@ -233,10 +233,22 @@ impl Board where V: Vm, { - pub fn new(vm: V, memory: Memory, arch: ArchBoard, config: BoardConfig) -> Self { - Board { + pub fn new(hv: &H, mut config: BoardConfig) -> Result + where + H: Hypervisor, + { + config.config_fixup()?; + + let vm_config = VmConfig { + coco: config.coco.clone(), + }; + let mut vm = hv.create_vm(&vm_config)?; + let vm_memory = vm.create_vm_memory()?; + let arch = ArchBoard::new(hv, &vm, &config)?; + + let board = Board { vm, - memory, + memory: Memory::new(vm_memory), arch, config, payload: RwLock::new(None), @@ -257,7 +269,8 @@ where fatal: false, }), cond_var: Condvar::new(), - } + }; + Ok(board) } pub fn boot(&self) -> Result<()> { diff --git a/alioth/src/vm/vm.rs b/alioth/src/vm/vm.rs index ba652394..961788db 100644 --- a/alioth/src/vm/vm.rs +++ b/alioth/src/vm/vm.rs @@ -27,7 +27,7 @@ use snafu::{ResultExt, Snafu}; use crate::arch::layout::{PL011_START, PL031_START}; #[cfg(target_arch = "x86_64")] use crate::arch::layout::{PORT_COM1, PORT_FW_CFG_SELECTOR}; -use crate::board::{ArchBoard, Board, BoardConfig}; +use crate::board::{Board, BoardConfig}; #[cfg(target_arch = "x86_64")] use crate::device::fw_cfg::{FwCfg, FwCfgItemParam}; #[cfg(target_arch = "aarch64")] @@ -37,9 +37,8 @@ use crate::device::pl031::Pl031; #[cfg(target_arch = "x86_64")] use crate::device::serial::Serial; use crate::errors::{DebugTrace, trace_error}; -use crate::hv::{Hypervisor, IoeventFdRegistry, Vm, VmConfig}; +use crate::hv::{Hypervisor, IoeventFdRegistry, Vm}; use crate::loader::Payload; -use crate::mem::Memory; use crate::pci::pvpanic::PvPanic; use crate::pci::{Bdf, Pci}; #[cfg(target_os = "linux")] @@ -117,18 +116,8 @@ impl Machine where H: Hypervisor, { - pub fn new(hv: &H, mut config: BoardConfig) -> Result { - config.config_fixup()?; - - let vm_config = VmConfig { - coco: config.coco.clone(), - }; - let mut vm = hv.create_vm(&vm_config)?; - let vm_memory = vm.create_vm_memory()?; - let memory = Memory::new(vm_memory); - let arch = ArchBoard::new(hv, &vm, &config)?; - - let board = Arc::new(Board::new(vm, memory, arch, config)); + pub fn new(hv: &H, config: BoardConfig) -> Result { + let board = Arc::new(Board::new(hv, config)?); let (event_tx, event_rx) = mpsc::channel(); From 7cc11cb9215e87bc3f7c551b9d1b732b69298e7a Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 08:18:59 -0800 Subject: [PATCH 02/14] refactor(board): move SEV-specific functions to separate modules Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 6 +- .../board/{ => board_x86_64}/board_x86_64.rs | 180 +++--------------- .../{ => board_x86_64}/board_x86_64_test.rs | 0 .../board/{ => board_x86_64}/dsdt.x86_64.dsl | 0 alioth/src/board/board_x86_64/sev.rs | 165 ++++++++++++++++ alioth/src/firmware/ovmf/ovmf_x86_64/sev.rs | 50 ++++- 6 files changed, 243 insertions(+), 158 deletions(-) rename alioth/src/board/{ => board_x86_64}/board_x86_64.rs (73%) rename alioth/src/board/{ => board_x86_64}/board_x86_64_test.rs (100%) rename alioth/src/board/{ => board_x86_64}/dsdt.x86_64.dsl (100%) create mode 100644 alioth/src/board/board_x86_64/sev.rs diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index 3528c4a7..d03468fa 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -16,7 +16,7 @@ #[path = "board_aarch64.rs"] mod aarch64; #[cfg(target_arch = "x86_64")] -#[path = "board_x86_64.rs"] +#[path = "board_x86_64/board_x86_64.rs"] mod x86_64; #[cfg(target_os = "linux")] @@ -92,7 +92,7 @@ pub enum Error { #[snafu(display("Failed to reset PCI devices"))] ResetPci { source: Box }, #[snafu(display("Failed to configure firmware"))] - Firmware { error: std::io::Error }, + FwCfg { error: std::io::Error }, #[snafu(display("Missing payload"))] MissingPayload, #[snafu(display("Failed to notify the VMM thread"))] @@ -104,6 +104,8 @@ pub enum Error { #[cfg(target_arch = "x86_64")] #[snafu(display("Missing CPUID leaf {leaf:x?}"))] MissingCpuid { leaf: CpuidIn }, + #[snafu(display("Firmware error"), context(false))] + Firmware { source: Box }, } type Result = std::result::Result; diff --git a/alioth/src/board/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs similarity index 73% rename from alioth/src/board/board_x86_64.rs rename to alioth/src/board/board_x86_64/board_x86_64.rs index 8080a2f4..e72a3b4a 100644 --- a/alioth/src/board/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -12,17 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod sev; + use std::arch::x86_64::{__cpuid, CpuidResult}; use std::collections::HashMap; -use std::iter::zip; use std::mem::{offset_of, size_of, size_of_val}; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::AtomicU32; use parking_lot::Mutex; use snafu::ResultExt; -use zerocopy::{FromBytes, FromZeros, IntoBytes}; +use zerocopy::{FromZeros, IntoBytes}; use crate::arch::cpuid::CpuidIn; use crate::arch::layout::{ @@ -30,8 +31,6 @@ use crate::arch::layout::{ PORT_ACPI_SLEEP_CONTROL, RAM_32_SIZE, }; use crate::arch::msr::{IA32_MISC_ENABLE, MiscEnable}; -use crate::arch::reg::{Reg, SegAccess, SegReg, SegRegVal}; -use crate::arch::sev::SnpPageType; use crate::board::{Board, BoardConfig, CpuTopology, PCIE_MMIO_64_SIZE, Result, VcpuGuard, error}; use crate::device::ioapic::IoApic; use crate::firmware::acpi::bindings::{ @@ -41,11 +40,6 @@ use crate::firmware::acpi::reg::{FadtReset, FadtSleepControl}; use crate::firmware::acpi::{ AcpiTable, create_fadt, create_madt, create_mcfg, create_rsdp, create_xsdt, }; -use crate::firmware::ovmf::parse_data; -use crate::firmware::ovmf::sev::{ - GUID_SEV_ES_RESET_BLOCK, GUID_SEV_METADATA, SevDescType, SevMetaData, SevMetadataDesc, - SnpCpuidFunc, SnpCpuidInfo, -}; use crate::hv::{Coco, Hypervisor, Vcpu, Vm}; use crate::loader::{Executable, InitState, Payload, firmware}; use crate::mem::mapped::ArcMemPages; @@ -197,88 +191,19 @@ where encode_x2apic_id(&self.config.cpu.topology, index) as u64 } - fn fill_snp_cpuid(&self, entries: &mut [SnpCpuidFunc]) { - for ((in_, out), dst) in zip(self.arch.cpuids.iter(), entries.iter_mut()) { - dst.eax_in = in_.func; - dst.ecx_in = in_.index.unwrap_or(0); - dst.eax = out.eax; - dst.ebx = out.ebx; - dst.ecx = out.ecx; - dst.edx = out.edx; - if dst.eax_in == 0xd && (dst.ecx_in == 0x0 || dst.ecx_in == 0x1) { - dst.ebx = 0x240; - dst.xcr0_in = 1; - dst.xss_in = 0; - } - } - } - - fn parse_sev_es_ap(&self, coco: &Coco, fw: &ArcMemPages) { - match coco { - Coco::AmdSev { policy } if policy.es() => {} - Coco::AmdSnp { .. } => {} - _ => return, - } - let ap_eip = parse_data(fw.as_slice(), &GUID_SEV_ES_RESET_BLOCK).unwrap(); - let ap_eip = u32::read_from_bytes(ap_eip).unwrap(); - self.arch.sev_ap_eip.store(ap_eip, Ordering::Release); - } - - fn update_snp_desc(&self, offset: usize, fw_range: &mut [u8]) -> Result<()> { - let mut cpuid_table = SnpCpuidInfo::new_zeroed(); - let ram_bus = self.memory.ram_bus(); - let ram = ram_bus.lock_layout(); - let (desc, _) = SevMetadataDesc::read_from_prefix(&fw_range[offset..]).unwrap(); - let snp_page_type = match desc.type_ { - SevDescType::SNP_DESC_MEM => SnpPageType::UNMEASURED, - SevDescType::SNP_SECRETS => SnpPageType::SECRETS, - SevDescType::CPUID => { - assert!(desc.len as usize >= size_of::()); - assert!(cpuid_table.entries.len() >= self.arch.cpuids.len()); - cpuid_table.count = self.arch.cpuids.len() as u32; - self.fill_snp_cpuid(&mut cpuid_table.entries); - ram.write_t(desc.base as _, &cpuid_table)?; - SnpPageType::CPUID - } - _ => unimplemented!(), - }; - let range_ref = ram.get_slice::(desc.base as u64, desc.len as u64)?; - let range_bytes = - unsafe { std::slice::from_raw_parts_mut(range_ref.as_ptr() as _, range_ref.len()) }; - self.memory - .mark_private_memory(desc.base as _, desc.len as _, true)?; - let mut ret = self - .vm - .snp_launch_update(range_bytes, desc.base as _, snp_page_type); - if ret.is_err() && desc.type_ == SevDescType::CPUID { - let updated_cpuid: SnpCpuidInfo = ram.read_t(desc.base as _)?; - for (set, got) in zip(cpuid_table.entries.iter(), updated_cpuid.entries.iter()) { - if set != got { - log::error!("set {set:#x?}, but firmware expects {got:#x?}"); - } - } - ram.write_t(desc.base as _, &updated_cpuid)?; - ret = self - .vm - .snp_launch_update(range_bytes, desc.base as _, snp_page_type); - } - ret?; - Ok(()) - } - fn setup_fw_cfg(&self, payload: &Payload) -> Result<()> { let Some(dev) = &*self.fw_cfg.lock() else { return Ok(()); }; let mut dev = dev.lock(); if let Some(Executable::Linux(image)) = &payload.executable { - dev.add_kernel_data(image).context(error::Firmware)?; + dev.add_kernel_data(image).context(error::FwCfg)?; }; if let Some(cmdline) = &payload.cmdline { dev.add_kernel_cmdline(cmdline.to_owned()); }; if let Some(initramfs) = &payload.initramfs { - dev.add_initramfs_data(initramfs).context(error::Firmware)?; + dev.add_initramfs_data(initramfs).context(error::FwCfg)?; }; Ok(()) } @@ -287,34 +212,11 @@ where let Some(coco) = &self.config.coco else { return Ok(()); }; - self.memory.register_encrypted_pages(fw)?; - self.parse_sev_es_ap(coco, fw); match coco { - Coco::AmdSev { .. } => { - self.vm.sev_launch_update_data(fw.as_slice_mut())?; - } - Coco::AmdSnp { .. } => { - let fw_range = fw.as_slice_mut(); - let metadata_offset_r = parse_data(fw_range, &GUID_SEV_METADATA).unwrap(); - let metadata_offset = - fw_range.len() - u32::read_from_bytes(metadata_offset_r).unwrap() as usize; - let (metadata, _) = - SevMetaData::read_from_prefix(&fw_range[metadata_offset..]).unwrap(); - let desc_offset = metadata_offset + size_of::(); - for i in 0..metadata.num_desc as usize { - let offset = desc_offset + i * size_of::(); - self.update_snp_desc(offset, fw_range)?; - } - let fw_gpa = MEM_64_START - fw_range.len() as u64; - self.memory - .mark_private_memory(fw_gpa, fw_range.len() as _, true)?; - self.vm - .snp_launch_update(fw_range, fw_gpa, SnpPageType::NORMAL) - .unwrap(); - } + Coco::AmdSev { policy } => self.setup_sev(fw, *policy), + Coco::AmdSnp { .. } => self.setup_snp(fw), Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), } - Ok(()) } pub fn setup_firmware(&self, fw: &Path, payload: &Payload) -> Result { @@ -325,30 +227,18 @@ where } pub fn init_ap(&self, index: u16, vcpu: &mut V::Vcpu, vcpus: &VcpuGuard) -> Result<()> { - match &self.config.coco { - Some(Coco::AmdSev { policy }) if policy.es() => {} - Some(Coco::AmdSnp { .. }) => {} - _ => return Ok(()), - } - self.sync_vcpus(vcpus)?; - if index == 0 { + let Some(coco) = &self.config.coco else { return Ok(()); + }; + match coco { + Coco::AmdSev { policy } => { + if policy.es() { + self.sev_init_ap(index, vcpu, vcpus)?; + } + } + Coco::AmdSnp { .. } => self.sev_init_ap(index, vcpu, vcpus)?, + Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), } - let eip = self.arch.sev_ap_eip.load(Ordering::Acquire); - vcpu.set_regs(&[(Reg::Rip, eip as u64 & 0xffff)])?; - vcpu.set_sregs( - &[], - &[( - SegReg::Cs, - SegRegVal { - selector: 0xf000, - base: eip as u64 & 0xffff_0000, - limit: 0xffff, - access: SegAccess(0x9b), - }, - )], - &[], - )?; Ok(()) } @@ -443,26 +333,18 @@ where } pub fn coco_finalize(&self, index: u16, vcpus: &VcpuGuard) -> Result<()> { - if let Some(coco) = &self.config.coco { - self.sync_vcpus(vcpus)?; - if index == 0 { - match coco { - Coco::AmdSev { policy } => { - if policy.es() { - self.vm.sev_launch_update_vmsa()?; - } - self.vm.sev_launch_measure()?; - self.vm.sev_launch_finish()?; - } - Coco::AmdSnp { .. } => { - self.vm.snp_launch_finish()?; - } - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), - } - } - self.sync_vcpus(vcpus)?; + let Some(coco) = &self.config.coco else { + return Ok(()); + }; + self.sync_vcpus(vcpus)?; + if index != 0 { + return Ok(()); + }; + match coco { + Coco::AmdSev { policy } => self.sev_finalize(*policy), + Coco::AmdSnp { .. } => self.snp_finalize(), + Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), } - Ok(()) } fn patch_dsdt(&self, data: &mut [u8; 352]) { @@ -556,9 +438,9 @@ where } if let Some(fw_cfg) = &*self.fw_cfg.lock() { let mut dev = fw_cfg.lock(); - dev.add_acpi(acpi_table).context(error::Firmware)?; + dev.add_acpi(acpi_table).context(error::FwCfg)?; let mem_regions = memory.mem_region_entries(); - dev.add_e820(&mem_regions).context(error::Firmware)?; + dev.add_e820(&mem_regions).context(error::FwCfg)?; } Ok(()) } diff --git a/alioth/src/board/board_x86_64_test.rs b/alioth/src/board/board_x86_64/board_x86_64_test.rs similarity index 100% rename from alioth/src/board/board_x86_64_test.rs rename to alioth/src/board/board_x86_64/board_x86_64_test.rs diff --git a/alioth/src/board/dsdt.x86_64.dsl b/alioth/src/board/board_x86_64/dsdt.x86_64.dsl similarity index 100% rename from alioth/src/board/dsdt.x86_64.dsl rename to alioth/src/board/board_x86_64/dsdt.x86_64.dsl diff --git a/alioth/src/board/board_x86_64/sev.rs b/alioth/src/board/board_x86_64/sev.rs new file mode 100644 index 00000000..f282cd84 --- /dev/null +++ b/alioth/src/board/board_x86_64/sev.rs @@ -0,0 +1,165 @@ +// 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::iter::zip; +use std::sync::atomic::Ordering; + +use zerocopy::FromZeros; + +use crate::arch::layout::MEM_64_START; +use crate::arch::reg::{Reg, SegAccess, SegReg, SegRegVal}; +use crate::arch::sev::{SevPolicy, SnpPageType}; +use crate::board::{Board, Result, VcpuGuard}; +use crate::firmware::ovmf::sev::{ + SevDescType, SevMetadataDesc, SnpCpuidFunc, SnpCpuidInfo, parse_desc, parse_sev_ap_eip, +}; +use crate::hv::{Vcpu, Vm}; +use crate::mem::mapped::ArcMemPages; + +impl Board +where + V: Vm, +{ + fn fill_snp_cpuid(&self, entries: &mut [SnpCpuidFunc]) { + for ((in_, out), dst) in zip(self.arch.cpuids.iter(), entries.iter_mut()) { + dst.eax_in = in_.func; + dst.ecx_in = in_.index.unwrap_or(0); + dst.eax = out.eax; + dst.ebx = out.ebx; + dst.ecx = out.ecx; + dst.edx = out.edx; + if dst.eax_in == 0xd && (dst.ecx_in == 0x0 || dst.ecx_in == 0x1) { + dst.ebx = 0x240; + dst.xcr0_in = 1; + dst.xss_in = 0; + } + } + } + + fn parse_sev_api_eip(&self, data: &[u8]) -> Result<()> { + let ap_eip = parse_sev_ap_eip(data)?; + self.arch.sev_ap_eip.store(ap_eip, Ordering::Release); + Ok(()) + } + + fn update_snp_desc(&self, desc: &SevMetadataDesc) -> Result<()> { + let mut cpuid_table = SnpCpuidInfo::new_zeroed(); + let ram_bus = self.memory.ram_bus(); + let ram = ram_bus.lock_layout(); + let snp_page_type = match desc.type_ { + SevDescType::SNP_DESC_MEM => SnpPageType::UNMEASURED, + SevDescType::SNP_SECRETS => SnpPageType::SECRETS, + SevDescType::CPUID => { + assert!(desc.len as usize >= size_of::()); + assert!(cpuid_table.entries.len() >= self.arch.cpuids.len()); + cpuid_table.count = self.arch.cpuids.len() as u32; + self.fill_snp_cpuid(&mut cpuid_table.entries); + ram.write_t(desc.base as _, &cpuid_table)?; + SnpPageType::CPUID + } + _ => unimplemented!(), + }; + let range_ref = ram.get_slice::(desc.base as u64, desc.len as u64)?; + let range_bytes = + unsafe { std::slice::from_raw_parts_mut(range_ref.as_ptr() as _, range_ref.len()) }; + self.memory + .mark_private_memory(desc.base as _, desc.len as _, true)?; + let mut ret = self + .vm + .snp_launch_update(range_bytes, desc.base as _, snp_page_type); + if ret.is_err() && desc.type_ == SevDescType::CPUID { + let updated_cpuid: SnpCpuidInfo = ram.read_t(desc.base as _)?; + for (set, got) in zip(cpuid_table.entries.iter(), updated_cpuid.entries.iter()) { + if set != got { + log::error!("set {set:#x?}, but firmware expects {got:#x?}"); + } + } + ram.write_t(desc.base as _, &updated_cpuid)?; + ret = self + .vm + .snp_launch_update(range_bytes, desc.base as _, snp_page_type); + } + ret?; + Ok(()) + } + + pub(crate) fn setup_sev(&self, fw: &mut ArcMemPages, policy: SevPolicy) -> Result<()> { + self.memory.register_encrypted_pages(fw)?; + + let data = fw.as_slice_mut(); + if policy.es() { + self.parse_sev_api_eip(data)?; + } + self.vm.sev_launch_update_data(data)?; + Ok(()) + } + + pub(crate) fn setup_snp(&self, fw: &mut ArcMemPages) -> Result<()> { + self.memory.register_encrypted_pages(fw)?; + + let data = fw.as_slice_mut(); + self.parse_sev_api_eip(data)?; + for desc in parse_desc(data)? { + self.update_snp_desc(desc)?; + } + let fw_gpa = MEM_64_START - data.len() as u64; + self.memory + .mark_private_memory(fw_gpa, data.len() as _, true)?; + self.vm + .snp_launch_update(data, fw_gpa, SnpPageType::NORMAL)?; + Ok(()) + } + + pub(crate) fn sev_finalize(&self, policy: SevPolicy) -> Result<()> { + if policy.es() { + self.vm.sev_launch_update_vmsa()?; + } + self.vm.sev_launch_measure()?; + self.vm.sev_launch_finish()?; + Ok(()) + } + + pub(crate) fn snp_finalize(&self) -> Result<()> { + self.vm.snp_launch_finish()?; + Ok(()) + } + + pub(crate) fn sev_init_ap( + &self, + index: u16, + vcpu: &mut V::Vcpu, + vcpus: &VcpuGuard, + ) -> Result<()> { + self.sync_vcpus(vcpus)?; + if index == 0 { + return Ok(()); + } + let eip = self.arch.sev_ap_eip.load(Ordering::Acquire); + vcpu.set_regs(&[(Reg::Rip, eip as u64 & 0xffff)])?; + vcpu.set_sregs( + &[], + &[( + SegReg::Cs, + SegRegVal { + selector: 0xf000, + base: eip as u64 & 0xffff_0000, + limit: 0xffff, + access: SegAccess(0x9b), + }, + )], + &[], + )?; + Ok(()) + } +} diff --git a/alioth/src/firmware/ovmf/ovmf_x86_64/sev.rs b/alioth/src/firmware/ovmf/ovmf_x86_64/sev.rs index 73d9b78b..a039bb86 100644 --- a/alioth/src/firmware/ovmf/ovmf_x86_64/sev.rs +++ b/alioth/src/firmware/ovmf/ovmf_x86_64/sev.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use zerocopy::{FromBytes, Immutable, IntoBytes}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::consts; -use crate::firmware::ovmf::x86_64::GUID_SIZE; +use crate::firmware::ovmf::x86_64::{GUID_SIZE, parse_data}; +use crate::firmware::{Result, error}; pub const GUID_SEV_ES_RESET_BLOCK: [u8; GUID_SIZE] = [ 0xde, 0x71, 0xf7, 0x00, 0x7e, 0x1a, 0xcb, 0x4f, 0x89, 0x0e, 0x68, 0xc7, 0x7e, 0x2f, 0xb4, 0x4e, @@ -25,7 +26,7 @@ pub const GUID_SEV_METADATA: [u8; GUID_SIZE] = [ 0x66, 0x65, 0x88, 0xdc, 0x4a, 0x98, 0x98, 0x47, 0xA7, 0x5e, 0x55, 0x85, 0xa7, 0xbf, 0x67, 0xcc, ]; -#[derive(Debug, FromBytes, IntoBytes, Immutable)] +#[derive(Debug, KnownLayout, Immutable, FromBytes, IntoBytes)] #[repr(C)] pub struct SevMetaData { pub signature: u32, @@ -35,7 +36,7 @@ pub struct SevMetaData { } consts! { - #[derive(FromBytes, IntoBytes, Immutable)] + #[derive(KnownLayout, Immutable, FromBytes, IntoBytes)] pub struct SevDescType(u32) { SNP_DESC_MEM = 1; SNP_SECRETS = 2; @@ -43,7 +44,7 @@ consts! { } } -#[derive(Debug, FromBytes, IntoBytes, Immutable)] +#[derive(Debug, KnownLayout, Immutable, FromBytes, IntoBytes)] #[repr(C)] pub struct SevMetadataDesc { pub base: u32, @@ -52,7 +53,7 @@ pub struct SevMetadataDesc { } #[repr(C)] -#[derive(Debug, Clone, PartialEq, Eq, FromBytes, IntoBytes, Immutable)] +#[derive(Debug, Clone, PartialEq, Eq, KnownLayout, Immutable, FromBytes, IntoBytes)] pub struct SnpCpuidFunc { pub eax_in: u32, pub ecx_in: u32, @@ -66,10 +67,45 @@ pub struct SnpCpuidFunc { } #[repr(C)] -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +#[derive(Debug, Clone, KnownLayout, Immutable, FromBytes, IntoBytes)] pub struct SnpCpuidInfo { pub count: u32, pub _reserved1: u32, pub _reserved2: u64, pub entries: [SnpCpuidFunc; 64], } + +pub fn parse_sev_ap_eip(data: &[u8]) -> Result { + let Some(ap_eip) = parse_data(data, &GUID_SEV_ES_RESET_BLOCK) else { + return error::MissingMetadata { + name: "SevEsResetBlock", + } + .fail(); + }; + let Ok(ap_eip) = u32::read_from_bytes(ap_eip) else { + return error::InvalidLayout.fail(); + }; + Ok(ap_eip) +} + +pub fn parse_desc(data: &[u8]) -> Result<&[SevMetadataDesc]> { + let Some(offset_r) = parse_data(data, &GUID_SEV_METADATA) else { + return error::MissingMetadata { + name: "SevMetadata", + } + .fail(); + }; + let Ok(offset_r) = u32::read_from_bytes(offset_r) else { + return error::InvalidLayout.fail(); + }; + let offset = data.len() - offset_r as usize; + let Ok((metadata, remain)) = SevMetaData::ref_from_prefix(&data[offset..]) else { + return error::InvalidLayout.fail(); + }; + let Ok((entries, _)) = + <[SevMetadataDesc]>::ref_from_prefix_with_elems(remain, metadata.num_desc as usize) + else { + return error::InvalidLayout.fail(); + }; + Ok(entries) +} From e8477f86e759625b669f017ab119a0d75b44a915 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 20 Feb 2026 10:26:12 -0800 Subject: [PATCH 03/14] refactor(board): move Coco initialization to the Board constructor Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 4 +++- alioth/src/board/board_aarch64.rs | 2 +- alioth/src/board/board_x86_64/board_x86_64.rs | 16 +++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index d03468fa..99169a29 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -272,6 +272,9 @@ where }), cond_var: Condvar::new(), }; + + board.coco_init()?; + Ok(board) } @@ -429,7 +432,6 @@ where fn boot_init_sync(&self, index: u16, vcpu: &mut V::Vcpu) -> Result<()> { let vcpus = self.vcpus.read(); - self.coco_init(index)?; if index == 0 { self.create_ram()?; for (port, dev) in self.io_devs.read().iter() { diff --git a/alioth/src/board/board_aarch64.rs b/alioth/src/board/board_aarch64.rs index 56b8d0a0..4ab12f15 100644 --- a/alioth/src/board/board_aarch64.rs +++ b/alioth/src/board/board_aarch64.rs @@ -154,7 +154,7 @@ where Ok(()) } - pub fn coco_init(&self, _id: u16) -> Result<()> { + pub fn coco_init(&self) -> Result<()> { Ok(()) } diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index e72a3b4a..69838396 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -318,16 +318,14 @@ where Ok(()) } - pub fn coco_init(&self, index: u16) -> Result<()> { - if index != 0 { + pub fn coco_init(&self) -> Result<()> { + let Some(coco) = &self.config.coco else { return Ok(()); - } - if let Some(coco) = &self.config.coco { - match coco { - Coco::AmdSev { policy } => self.vm.sev_launch_start(*policy)?, - Coco::AmdSnp { policy } => self.vm.snp_launch_start(*policy)?, - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), - } + }; + match coco { + Coco::AmdSev { policy } => self.vm.sev_launch_start(*policy)?, + Coco::AmdSnp { policy } => self.vm.snp_launch_start(*policy)?, + Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), } Ok(()) } From 5775ac6a6760ecb14ec12cbbcc5e33194cde266b Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 20 Feb 2026 11:07:24 -0800 Subject: [PATCH 04/14] refactor(snp): implement memory encryption as layout change callbacks Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 6 +-- alioth/src/board/board_aarch64.rs | 2 +- alioth/src/board/board_x86_64/board_x86_64.rs | 19 ++------- alioth/src/board/board_x86_64/sev.rs | 40 ++++++++++++++++++- alioth/src/mem/mem.rs | 24 +++++++++-- alioth/src/pci/config_test.rs | 4 +- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index 99169a29..691e42f4 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -245,12 +245,12 @@ where coco: config.coco.clone(), }; let mut vm = hv.create_vm(&vm_config)?; - let vm_memory = vm.create_vm_memory()?; + let vm_memory = Arc::new(vm.create_vm_memory()?); let arch = ArchBoard::new(hv, &vm, &config)?; let board = Board { vm, - memory: Memory::new(vm_memory), + memory: Memory::new(vm_memory.clone()), arch, config, payload: RwLock::new(None), @@ -273,7 +273,7 @@ where cond_var: Condvar::new(), }; - board.coco_init()?; + board.coco_init(vm_memory)?; Ok(board) } diff --git a/alioth/src/board/board_aarch64.rs b/alioth/src/board/board_aarch64.rs index 4ab12f15..83f7fdd7 100644 --- a/alioth/src/board/board_aarch64.rs +++ b/alioth/src/board/board_aarch64.rs @@ -154,7 +154,7 @@ where Ok(()) } - pub fn coco_init(&self) -> Result<()> { + pub fn coco_init(&self, _: Arc) -> Result<()> { Ok(()) } diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 69838396..7d954364 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -297,34 +297,23 @@ where callbacks: Mutex::new(vec![]), }; memory.add_region(0, Arc::new(region_low))?; - if let Some(coco) = &self.config.coco { - memory.register_encrypted_pages(&pages_low)?; - if let Coco::AmdSnp { .. } = coco { - memory.mark_private_memory(0, low_mem_size as _, true)?; - } - } + if config.mem.size > RAM_32_SIZE { let mem_hi_size = config.mem.size - RAM_32_SIZE; let mem_hi = self.create_ram_pages(mem_hi_size, c"ram-high")?; let region_hi = MemRegion::with_ram(mem_hi.clone(), MemRegionType::Ram); memory.add_region(MEM_64_START, Arc::new(region_hi))?; - if let Some(coco) = &self.config.coco { - memory.register_encrypted_pages(&mem_hi)?; - if let Coco::AmdSnp { .. } = coco { - memory.mark_private_memory(MEM_64_START as _, mem_hi_size as _, true)?; - } - } } Ok(()) } - pub fn coco_init(&self) -> Result<()> { + pub fn coco_init(&self, memory: Arc) -> Result<()> { let Some(coco) = &self.config.coco else { return Ok(()); }; match coco { - Coco::AmdSev { policy } => self.vm.sev_launch_start(*policy)?, - Coco::AmdSnp { policy } => self.vm.snp_launch_start(*policy)?, + Coco::AmdSev { policy } => self.sev_init(*policy, memory)?, + Coco::AmdSnp { policy } => self.snp_init(*policy, memory)?, Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), } Ok(()) diff --git a/alioth/src/board/board_x86_64/sev.rs b/alioth/src/board/board_x86_64/sev.rs index f282cd84..b48eb816 100644 --- a/alioth/src/board/board_x86_64/sev.rs +++ b/alioth/src/board/board_x86_64/sev.rs @@ -13,19 +13,21 @@ // limitations under the License. use std::iter::zip; +use std::sync::Arc; use std::sync::atomic::Ordering; use zerocopy::FromZeros; use crate::arch::layout::MEM_64_START; use crate::arch::reg::{Reg, SegAccess, SegReg, SegRegVal}; -use crate::arch::sev::{SevPolicy, SnpPageType}; +use crate::arch::sev::{SevPolicy, SnpPageType, SnpPolicy}; use crate::board::{Board, Result, VcpuGuard}; use crate::firmware::ovmf::sev::{ SevDescType, SevMetadataDesc, SnpCpuidFunc, SnpCpuidInfo, parse_desc, parse_sev_ap_eip, }; -use crate::hv::{Vcpu, Vm}; +use crate::hv::{Vcpu, Vm, VmMemory}; use crate::mem::mapped::ArcMemPages; +use crate::mem::{self, LayoutChanged, MarkPrivateMemory}; impl Board where @@ -162,4 +164,38 @@ where )?; Ok(()) } + + pub(crate) fn sev_init(&self, policy: SevPolicy, memory: Arc) -> Result<()> { + self.vm.sev_launch_start(policy)?; + let encrypt_pages = Box::new(EncryptPages { memory }); + self.memory.register_change_callback(encrypt_pages)?; + Ok(()) + } + + pub(crate) fn snp_init(&self, policy: SnpPolicy, memory: Arc) -> Result<()> { + self.vm.snp_launch_start(policy)?; + let encrypt_pages = Box::new(EncryptPages { + memory: memory.clone(), + }); + self.memory.register_change_callback(encrypt_pages)?; + let mark_private_memory = Box::new(MarkPrivateMemory { memory }); + self.memory.register_change_callback(mark_private_memory)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct EncryptPages { + memory: Arc, +} + +impl LayoutChanged for EncryptPages { + fn ram_added(&self, _: u64, pages: &ArcMemPages) -> mem::Result<()> { + self.memory.register_encrypted_range(pages.as_slice())?; + Ok(()) + } + + fn ram_removed(&self, _: u64, _: &ArcMemPages) -> mem::Result<()> { + Ok(()) + } } diff --git a/alioth/src/mem/mem.rs b/alioth/src/mem/mem.rs index 46a50969..59e53b3d 100644 --- a/alioth/src/mem/mem.rs +++ b/alioth/src/mem/mem.rs @@ -264,7 +264,7 @@ pub struct Memory { callbacks: Mutex, ram_bus: Arc, mmio_bus: RwLock, - vm_memory: Box, + vm_memory: Arc, #[cfg(target_arch = "x86_64")] io_bus: RwLock, @@ -272,13 +272,13 @@ pub struct Memory { } impl Memory { - pub fn new(vm_memory: impl VmMemory) -> Self { + pub fn new(vm_memory: Arc) -> Self { Memory { regions: Mutex::new(Addressable::new()), callbacks: Mutex::new(LayoutCallbacks::default()), ram_bus: Arc::new(RamBus::new()), mmio_bus: RwLock::new(MmioBus::new()), - vm_memory: Box::new(vm_memory), + vm_memory, #[cfg(target_arch = "x86_64")] io_bus: RwLock::new(MmioBus::new()), io_regions: Mutex::new(Addressable::new()), @@ -397,7 +397,7 @@ impl Memory { } MemRange::Ram(r) => { self.ram_bus.remove(gpa)?; - for callback in &callbacks.changed { + for callback in callbacks.changed.iter().rev() { callback.ram_removed(gpa, r)?; } self.unmap_from_vm(gpa, r)?; @@ -605,3 +605,19 @@ impl Memory { } } } + +#[derive(Debug)] +pub struct MarkPrivateMemory { + pub memory: Arc, +} + +impl LayoutChanged for MarkPrivateMemory { + fn ram_added(&self, gpa: u64, pages: &ArcMemPages) -> Result<()> { + self.memory.mark_private_memory(gpa, pages.size(), true)?; + Ok(()) + } + + fn ram_removed(&self, _: u64, _: &ArcMemPages) -> Result<()> { + Ok(()) + } +} diff --git a/alioth/src/pci/config_test.rs b/alioth/src/pci/config_test.rs index 5b4ab804..4be79929 100644 --- a/alioth/src/pci/config_test.rs +++ b/alioth/src/pci/config_test.rs @@ -291,7 +291,7 @@ impl VmMemory for FakeVmMemory { #[test] fn test_mem_bar_layout_change() { - let memory = Memory::new(FakeVmMemory); + let memory = Memory::new(Arc::new(FakeVmMemory)); let header = fixture_emulated_header(); let callback = assert_matches!( @@ -351,7 +351,7 @@ fn test_mem_bar_layout_change() { #[test] fn test_io_bar_layout_change() { - let memory = Memory::new(FakeVmMemory); + let memory = Memory::new(Arc::new(FakeVmMemory)); let header = fixture_emulated_header(); let callback = assert_matches!( From ca8764d6abe641be19bdfd3f48f058eb6f0a4399 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 08:51:39 -0800 Subject: [PATCH 05/14] fix(snp): explicitly enable the MAP_GPA_RANGE hypercall Signed-off-by: Changyuan Lyu --- alioth/src/hv/kvm/vcpu/vmexit.rs | 4 ++-- alioth/src/hv/kvm/vm/vm_x86_64/sev.rs | 6 +++--- alioth/src/sys/linux/kvm.rs | 10 +++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/alioth/src/hv/kvm/vcpu/vmexit.rs b/alioth/src/hv/kvm/vcpu/vmexit.rs index 4ffecf38..c5be3d5d 100644 --- a/alioth/src/hv/kvm/vcpu/vmexit.rs +++ b/alioth/src/hv/kvm/vcpu/vmexit.rs @@ -16,7 +16,7 @@ use crate::hv::kvm::vcpu::KvmVcpu; use crate::hv::{Error, VmExit, error}; #[cfg(target_arch = "x86_64")] use crate::sys::kvm::KvmExitIo; -use crate::sys::kvm::{KVM_HC_MAP_GPA_RANGE, KvmMapGpaRangeFlag, KvmSystemEvent}; +use crate::sys::kvm::{KvmHypercall, KvmMapGpaRangeFlag, KvmSystemEvent}; impl KvmVcpu { #[cfg(target_endian = "little")] @@ -61,7 +61,7 @@ impl KvmVcpu { pub(super) fn handle_hypercall(&mut self) -> Result { let hypercall = unsafe { self.kvm_run.exit.hypercall }; match hypercall.nr { - KVM_HC_MAP_GPA_RANGE => { + KvmHypercall::MAP_GPA_RANGE => { let flag = KvmMapGpaRangeFlag::from_bits_retain(hypercall.args[2]); Ok(VmExit::ConvertMemory { gpa: hypercall.args[0], diff --git a/alioth/src/hv/kvm/vm/vm_x86_64/sev.rs b/alioth/src/hv/kvm/vm/vm_x86_64/sev.rs index 59edac2c..881855c1 100644 --- a/alioth/src/hv/kvm/vm/vm_x86_64/sev.rs +++ b/alioth/src/hv/kvm/vm/vm_x86_64/sev.rs @@ -19,7 +19,7 @@ use snafu::ResultExt; use crate::arch::sev::{SevPolicy, SevStatus, SnpPageType, SnpPolicy}; use crate::hv::kvm::KvmVm; use crate::hv::{Result, error}; -use crate::sys::kvm::{KvmCap, kvm_memory_encrypt_op}; +use crate::sys::kvm::{KvmCap, KvmHypercall, kvm_memory_encrypt_op}; use crate::sys::sev::{ KvmSevCmd, KvmSevCmdId, KvmSevInit, KvmSevLaunchMeasure, KvmSevLaunchStart, KvmSevLaunchUpdateData, KvmSevSnpLaunchFinish, KvmSevSnpLaunchStart, KvmSevSnpLaunchUpdate, @@ -55,8 +55,8 @@ impl KvmVm { } pub fn snp_init(&self) -> Result<()> { - let bitmap = self.vm.check_extension(KvmCap::EXIT_HYPERCALL)?.get(); - self.vm.enable_cap(KvmCap::EXIT_HYPERCALL, bitmap as u64)?; + let map_gpa_range = 1 << KvmHypercall::MAP_GPA_RANGE.raw(); + self.vm.enable_cap(KvmCap::EXIT_HYPERCALL, map_gpa_range)?; let mut init = KvmSevInit::default(); self.sev_op(KvmSevCmdId::INIT2, Some(&mut init)) } diff --git a/alioth/src/sys/linux/kvm.rs b/alioth/src/sys/linux/kvm.rs index 5762bfce..fb89fd03 100644 --- a/alioth/src/sys/linux/kvm.rs +++ b/alioth/src/sys/linux/kvm.rs @@ -360,10 +360,16 @@ pub struct KvmRunExitIo { pub data_offset: u64, } +consts! { + pub struct KvmHypercall(u64) { + MAP_GPA_RANGE = 12; + } +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct KvmRunExitHypercall { - pub nr: u64, + pub nr: KvmHypercall, pub args: [u64; 6], pub ret: u64, pub flags: u64, @@ -517,8 +523,6 @@ bitflags! { } } -pub const KVM_HC_MAP_GPA_RANGE: u64 = 12; - bitflags! { pub struct KvmMapGpaRangeFlag(u64) { PAGE_2M = 1 << 0; From 66ca7ec5ba718ca60fde0fd111384df49d39b435 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 10:29:40 -0800 Subject: [PATCH 06/14] fix(board): restrict sub-memory entry creation to non-confidential VMs ACPI tables and other init data are created in guest memory only for non-confidential VMs. Signed-off-by: Changyuan Lyu --- alioth/src/board/board_x86_64/board_x86_64.rs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 7d954364..7d161ec1 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -276,24 +276,31 @@ where let pages_low = self.create_ram_pages(low_mem_size, c"ram-low")?; let region_low = MemRegion { ranges: vec![MemRange::Ram(pages_low.clone())], - entries: vec![ - MemRegionEntry { - size: BIOS_DATA_END, - type_: MemRegionType::Reserved, - }, - MemRegionEntry { - size: EBDA_START - BIOS_DATA_END, + entries: if self.config.coco.is_none() { + vec![ + MemRegionEntry { + size: BIOS_DATA_END, + type_: MemRegionType::Reserved, + }, + MemRegionEntry { + size: EBDA_START - BIOS_DATA_END, + type_: MemRegionType::Ram, + }, + MemRegionEntry { + size: EBDA_END - EBDA_START, + type_: MemRegionType::Acpi, + }, + MemRegionEntry { + size: low_mem_size - EBDA_END, + type_: MemRegionType::Ram, + }, + ] + } else { + vec![MemRegionEntry { + size: low_mem_size, type_: MemRegionType::Ram, - }, - MemRegionEntry { - size: EBDA_END - EBDA_START, - type_: MemRegionType::Acpi, - }, - MemRegionEntry { - size: low_mem_size - EBDA_END, - type_: MemRegionType::Ram, - }, - ], + }] + }, callbacks: Mutex::new(vec![]), }; memory.add_region(0, Arc::new(region_low))?; From 69202a5cfa30cfedc040901dd3b3aec50dfbb5d1 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 07:57:45 -0800 Subject: [PATCH 07/14] feat(tdx): handle KVM_EXIT_MEMORY_FAULT to support memory conversion Signed-off-by: Changyuan Lyu --- alioth/src/hv/kvm/vcpu/vcpu.rs | 16 +++++++++------- alioth/src/hv/kvm/vcpu/vmexit.rs | 12 +++++++++++- alioth/src/sys/linux/kvm.rs | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/alioth/src/hv/kvm/vcpu/vcpu.rs b/alioth/src/hv/kvm/vcpu/vcpu.rs index 94eff494..f29b0587 100644 --- a/alioth/src/hv/kvm/vcpu/vcpu.rs +++ b/alioth/src/hv/kvm/vcpu/vcpu.rs @@ -26,7 +26,6 @@ mod vmexit; use std::arch::x86_64::CpuidResult; #[cfg(target_arch = "x86_64")] use std::collections::HashMap; -use std::io::ErrorKind; use std::ops::{Deref, DerefMut}; use std::os::fd::{AsRawFd, OwnedFd}; use std::ptr::null_mut; @@ -183,17 +182,17 @@ impl Vcpu for KvmVcpu { }; let ret = unsafe { kvm_run(&self.fd) }; match ret { - Err(e) => match (e.kind(), entry) { - (ErrorKind::WouldBlock, _) => Ok(VmExit::Interrupted), - (ErrorKind::Interrupted, VmEntry::Shutdown) => { + Err(e) => match (e.raw_os_error(), entry) { + (Some(libc::EAGAIN), _) => Ok(VmExit::Interrupted), + (Some(libc::EINTR), VmEntry::Shutdown) => { self.set_immediate_exit(false); Ok(VmExit::Shutdown) } - (ErrorKind::Interrupted, VmEntry::Reboot) => { + (Some(libc::EINTR), VmEntry::Reboot) => { self.set_immediate_exit(false); Ok(VmExit::Reboot) } - (ErrorKind::Interrupted, VmEntry::Pause) => { + (Some(libc::EINTR), VmEntry::Pause) => { #[cfg(target_arch = "x86_64")] if let Err(e) = self.kvmclock_ctrl() { log::error!("Failed to control kvmclock: {e:?}"); @@ -201,7 +200,10 @@ impl Vcpu for KvmVcpu { self.set_immediate_exit(false); Ok(VmExit::Paused) } - (ErrorKind::Interrupted, _) => Ok(VmExit::Interrupted), + (Some(libc::EINTR), _) => Ok(VmExit::Interrupted), + (Some(libc::EFAULT), _) if self.kvm_run.exit_reason == KvmExit::MEMORY_FAULT => { + Ok(self.handle_memory_fault()) + } _ => Err(e).context(error::RunVcpu), }, Ok(_) => match self.kvm_run.exit_reason { diff --git a/alioth/src/hv/kvm/vcpu/vmexit.rs b/alioth/src/hv/kvm/vcpu/vmexit.rs index c5be3d5d..9ccc11b2 100644 --- a/alioth/src/hv/kvm/vcpu/vmexit.rs +++ b/alioth/src/hv/kvm/vcpu/vmexit.rs @@ -16,7 +16,7 @@ use crate::hv::kvm::vcpu::KvmVcpu; use crate::hv::{Error, VmExit, error}; #[cfg(target_arch = "x86_64")] use crate::sys::kvm::KvmExitIo; -use crate::sys::kvm::{KvmHypercall, KvmMapGpaRangeFlag, KvmSystemEvent}; +use crate::sys::kvm::{KvmHypercall, KvmMapGpaRangeFlag, KvmMemoryFaultFlag, KvmSystemEvent}; impl KvmVcpu { #[cfg(target_endian = "little")] @@ -84,4 +84,14 @@ impl KvmVcpu { .fail(), } } + + pub(crate) fn handle_memory_fault(&mut self) -> VmExit { + let memory_fault = unsafe { &self.kvm_run.exit.memory_fault }; + let private = memory_fault.flags.contains(KvmMemoryFaultFlag::PRIVATE); + VmExit::ConvertMemory { + gpa: memory_fault.gpa, + size: memory_fault.size, + private, + } + } } diff --git a/alioth/src/sys/linux/kvm.rs b/alioth/src/sys/linux/kvm.rs index fb89fd03..dbefa344 100644 --- a/alioth/src/sys/linux/kvm.rs +++ b/alioth/src/sys/linux/kvm.rs @@ -290,6 +290,7 @@ consts! { MMIO = 6; SHUTDOWN = 8; SYSTEM_EVENT = 24; + MEMORY_FAULT = 39; } } @@ -307,6 +308,20 @@ pub struct KvmRunExitSystemEvent { pub flags: u64, } +bitflags! { + pub struct KvmMemoryFaultFlag(u64) { + PRIVATE = 1 << 3; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct KvmRunExitMemoryFault { + pub flags: KvmMemoryFaultFlag, + pub gpa: u64, + pub size: u64, +} + #[repr(C)] #[derive(Copy, Clone)] pub struct KvmRun { @@ -324,6 +339,7 @@ pub struct KvmRun { pub kvm_dirty_regs: u64, pub s: KvmSyncRegsBlock, } + #[repr(C)] #[derive(Copy, Clone)] pub union KvmRunExit { @@ -331,6 +347,7 @@ pub union KvmRunExit { pub io: KvmRunExitIo, pub hypercall: KvmRunExitHypercall, pub system_event: KvmRunExitSystemEvent, + pub memory_fault: KvmRunExitMemoryFault, pub padding: [u8; 256], } From c523bca32de86f8af293ca63bd8b7acfce69273a Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 09:59:43 -0800 Subject: [PATCH 08/14] feat(tdx): configure required KVM capabilities for TDX VMs Signed-off-by: Changyuan Lyu --- alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs | 26 +++++++++++++++++++++ alioth/src/hv/kvm/vm/vm_x86_64/vm_x86_64.rs | 22 +++++++++-------- alioth/src/sys/linux/kvm.rs | 1 + 3 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs diff --git a/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs new file mode 100644 index 00000000..28846f54 --- /dev/null +++ b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs @@ -0,0 +1,26 @@ +// 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 crate::hv::Result; +use crate::hv::kvm::vm::KvmVm; +use crate::sys::kvm::{KvmCap, KvmHypercall}; + +impl KvmVm { + pub fn tdx_init(&self) -> Result<()> { + let map_gpa_range = 1 << KvmHypercall::MAP_GPA_RANGE.raw(); + self.vm.enable_cap(KvmCap::EXIT_HYPERCALL, map_gpa_range)?; + self.vm.enable_cap(KvmCap::X86_APIC_BUS_CYCLES_NS, 40)?; + Ok(()) + } +} diff --git a/alioth/src/hv/kvm/vm/vm_x86_64/vm_x86_64.rs b/alioth/src/hv/kvm/vm/vm_x86_64/vm_x86_64.rs index 2af28773..4cecbbe8 100644 --- a/alioth/src/hv/kvm/vm/vm_x86_64/vm_x86_64.rs +++ b/alioth/src/hv/kvm/vm/vm_x86_64/vm_x86_64.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod sev; +mod sev; +mod tdx; use std::os::fd::{FromRawFd, OwnedFd}; use std::path::Path; @@ -58,7 +59,7 @@ impl VmArch { let fd = SevFd::new(dev_sev)?; Ok(VmArch { sev_fd: Some(fd) }) } - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), + Coco::IntelTdx { .. } => Ok(VmArch::default()), } } } @@ -91,14 +92,6 @@ impl KvmVm { } pub fn init(&self, config: &VmConfig) -> Result<()> { - if let Some(coco) = &config.coco { - match coco { - Coco::AmdSev { policy } => self.sev_init(*policy), - Coco::AmdSnp { .. } => self.snp_init(), - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), - }?; - } - let x2apic_caps = KvmX2apicApiFlag::USE_32BIT_IDS | KvmX2apicApiFlag::DISABLE_BROADCAST_QUIRK; if let Err(e) = self.vm.enable_cap(KvmCap::X2APIC_API, x2apic_caps.bits()) { @@ -109,6 +102,15 @@ impl KvmVm { unsafe { kvm_set_tss_addr(&self.vm.fd, 0xf000_0000) }.context(error::SetVmParam)?; unsafe { kvm_set_identity_map_addr(&self.vm.fd, &0xf000_3000) } .context(error::SetVmParam)?; + + if let Some(coco) = &config.coco { + match coco { + Coco::AmdSev { policy } => self.sev_init(*policy), + Coco::AmdSnp { .. } => self.snp_init(), + Coco::IntelTdx { .. } => self.tdx_init(), + }?; + } + Ok(()) } } diff --git a/alioth/src/sys/linux/kvm.rs b/alioth/src/sys/linux/kvm.rs index dbefa344..c8104b84 100644 --- a/alioth/src/sys/linux/kvm.rs +++ b/alioth/src/sys/linux/kvm.rs @@ -530,6 +530,7 @@ consts! { EXIT_HYPERCALL = 201; // GUEST_MEMFD = 234; // VM_TYPES = 235; + X86_APIC_BUS_CYCLES_NS = 237; } } From ca9a64b8636bff4be8a469dad6de27e20100334e Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 08:48:56 -0800 Subject: [PATCH 09/14] feat(tdx): initialize TDX VMs with supported CPUID features Signed-off-by: Changyuan Lyu --- alioth/src/board/board_x86_64/board_x86_64.rs | 3 +- alioth/src/board/board_x86_64/tdx.rs | 32 ++++++++++ alioth/src/hv/hv.rs | 3 + alioth/src/hv/kvm/kvm_x86_64.rs | 41 ++++++------ alioth/src/hv/kvm/kvm_x86_64_test.rs | 64 ++++++++++++++++++- alioth/src/hv/kvm/vm/vm.rs | 11 ++++ alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs | 61 +++++++++++++++++- alioth/src/sys/linux/tdx.rs | 8 +-- 8 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 alioth/src/board/board_x86_64/tdx.rs diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 7d161ec1..0e778208 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -13,6 +13,7 @@ // limitations under the License. mod sev; +mod tdx; use std::arch::x86_64::{__cpuid, CpuidResult}; use std::collections::HashMap; @@ -321,7 +322,7 @@ where match coco { Coco::AmdSev { policy } => self.sev_init(*policy, memory)?, Coco::AmdSnp { policy } => self.snp_init(*policy, memory)?, - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), + Coco::IntelTdx { attr } => self.tdx_init(*attr, memory)?, } Ok(()) } diff --git a/alioth/src/board/board_x86_64/tdx.rs b/alioth/src/board/board_x86_64/tdx.rs new file mode 100644 index 00000000..f1e054be --- /dev/null +++ b/alioth/src/board/board_x86_64/tdx.rs @@ -0,0 +1,32 @@ +// 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::sync::Arc; + +use crate::arch::tdx::TdAttr; +use crate::board::{Board, Result}; +use crate::hv::{Vm, VmMemory}; +use crate::mem::MarkPrivateMemory; + +impl Board +where + V: Vm, +{ + pub(crate) fn tdx_init(&self, attr: TdAttr, memory: Arc) -> Result<()> { + self.vm.tdx_init_vm(attr, &self.arch.cpuids)?; + let mark_private_memory = Box::new(MarkPrivateMemory { memory }); + self.memory.register_change_callback(mark_private_memory)?; + Ok(()) + } +} diff --git a/alioth/src/hv/hv.rs b/alioth/src/hv/hv.rs index c18a4f8a..727ae600 100644 --- a/alioth/src/hv/hv.rs +++ b/alioth/src/hv/hv.rs @@ -390,6 +390,9 @@ pub trait Vm { #[cfg(target_arch = "x86_64")] fn snp_launch_finish(&self) -> Result<()>; + #[cfg(target_arch = "x86_64")] + fn tdx_init_vm(&self, attr: TdAttr, cpuids: &HashMap) -> Result<()>; + #[cfg(target_arch = "aarch64")] type GicV2: GicV2; #[cfg(target_arch = "aarch64")] diff --git a/alioth/src/hv/kvm/kvm_x86_64.rs b/alioth/src/hv/kvm/kvm_x86_64.rs index 3b9d5412..9c2c125b 100644 --- a/alioth/src/hv/kvm/kvm_x86_64.rs +++ b/alioth/src/hv/kvm/kvm_x86_64.rs @@ -24,6 +24,26 @@ use crate::sys::kvm::{ KvmCpuidFeature, KvmX2apicApiFlag, kvm_get_supported_cpuid, }; +impl From for (CpuidIn, CpuidResult) { + fn from(value: KvmCpuidEntry2) -> Self { + let in_ = CpuidIn { + func: value.function, + index: if value.flags.contains(KvmCpuid2Flag::SIGNIFCANT_INDEX) || value.index > 0 { + Some(value.index) + } else { + None + }, + }; + let result = CpuidResult { + eax: value.eax, + ebx: value.ebx, + ecx: value.ecx, + edx: value.edx, + }; + (in_, result) + } +} + impl Kvm { pub fn get_supported_cpuids(&self) -> Result> { let mut kvm_cpuid2 = KvmCpuid2 { @@ -32,29 +52,12 @@ impl Kvm { entries: [KvmCpuidEntry2::default(); KVM_MAX_CPUID_ENTRIES], }; unsafe { kvm_get_supported_cpuid(&self.fd, &mut kvm_cpuid2) }.context(error::GuestCpuid)?; - let map_f = |e: &KvmCpuidEntry2| { - let in_ = CpuidIn { - func: e.function, - index: if e.flags.contains(KvmCpuid2Flag::SIGNIFCANT_INDEX) { - Some(e.index) - } else { - None - }, - }; - let out = CpuidResult { - eax: e.eax, - ebx: e.ebx, - ecx: e.ecx, - edx: e.edx, - }; - (in_, out) - }; let mut cpuids: HashMap<_, _> = kvm_cpuid2 .entries - .iter() + .into_iter() .filter(|e| e.eax != 0 || e.ebx != 0 || e.ecx != 0 || e.edx != 0) .take(kvm_cpuid2.nent as usize) - .map(map_f) + .map(From::from) .collect(); let leaf_features = CpuidIn { diff --git a/alioth/src/hv/kvm/kvm_x86_64_test.rs b/alioth/src/hv/kvm/kvm_x86_64_test.rs index faceeaf8..a976cbc4 100644 --- a/alioth/src/hv/kvm/kvm_x86_64_test.rs +++ b/alioth/src/hv/kvm/kvm_x86_64_test.rs @@ -12,8 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::arch::x86_64::CpuidResult; + +use rstest::rstest; + +use crate::arch::cpuid::CpuidIn; use crate::hv::kvm::{Kvm, KvmConfig}; -use crate::sys::kvm::KVM_CPUID_SIGNATURE; +use crate::sys::kvm::{KVM_CPUID_SIGNATURE, KvmCpuid2Flag, KvmCpuidEntry2}; #[test] #[cfg_attr(not(feature = "test-hv"), ignore)] @@ -32,3 +37,60 @@ fn test_get_supported_cpuid() { } assert!(kvm_cpuid_exist); } + +#[rstest] +#[case( + KvmCpuidEntry2 { + function: 0, + index: 0, + flags: KvmCpuid2Flag::empty(), + eax: 0x10, + ebx: u32::from_le_bytes(*b"Auth"), + ecx: u32::from_le_bytes(*b"cAMD"), + edx: u32::from_le_bytes(*b"enti"), + padding: [0; 3], + }, + ( + CpuidIn { + func: 0, + index: None, + }, + CpuidResult { + eax: 0x10, + ebx: u32::from_le_bytes(*b"Auth"), + ecx: u32::from_le_bytes(*b"cAMD"), + edx: u32::from_le_bytes(*b"enti"), + } + ) +)] +#[case( + KvmCpuidEntry2 { + function: 0xb, + index: 0, + flags: KvmCpuid2Flag::SIGNIFCANT_INDEX, + eax: 0x0, + ebx: 0x0, + ecx: 0x0, + edx: 0x6d, + padding: [0; 3], + }, + ( + CpuidIn { + func: 0xb, + index: Some(0), + }, + CpuidResult { + eax: 0x0, + ebx: 0x0, + ecx: 0x0, + edx: 0x6d, + } + ) +)] +fn test_convert_cpuid_entry( + #[case] value: KvmCpuidEntry2, + #[case] expected: (CpuidIn, CpuidResult), +) { + let got: (CpuidIn, CpuidResult) = value.into(); + assert_eq!(got, expected) +} diff --git a/alioth/src/hv/kvm/vm/vm.rs b/alioth/src/hv/kvm/vm/vm.rs index ee84684b..6c134d54 100644 --- a/alioth/src/hv/kvm/vm/vm.rs +++ b/alioth/src/hv/kvm/vm/vm.rs @@ -19,6 +19,8 @@ mod aarch64; #[path = "vm_x86_64/vm_x86_64.rs"] mod x86_64; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::CpuidResult; use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; use std::io::ErrorKind; @@ -35,8 +37,12 @@ use libc::{EFD_CLOEXEC, EFD_NONBLOCK, SIGRTMIN, eventfd}; use parking_lot::{Mutex, RwLock}; use snafu::ResultExt; +#[cfg(target_arch = "x86_64")] +use crate::arch::cpuid::CpuidIn; #[cfg(target_arch = "x86_64")] use crate::arch::sev::{SevPolicy, SnpPageType, SnpPolicy}; +#[cfg(target_arch = "x86_64")] +use crate::arch::tdx::TdAttr; use crate::ffi; #[cfg(not(target_arch = "x86_64"))] use crate::hv::IrqSender; @@ -755,6 +761,11 @@ impl Vm for KvmVm { KvmVm::snp_launch_finish(self) } + #[cfg(target_arch = "x86_64")] + fn tdx_init_vm(&self, attr: TdAttr, cpuids: &HashMap) -> Result<()> { + KvmVm::tdx_init_vm(self, attr, cpuids) + } + #[cfg(target_arch = "aarch64")] fn create_gic_v2(&self, distributor_base: u64, cpu_interface_base: u64) -> Result { aarch64::KvmGicV2::new(self, distributor_base, cpu_interface_base) diff --git a/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs index 28846f54..586ec84b 100644 --- a/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs +++ b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs @@ -12,9 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::arch::x86_64::CpuidResult; +use std::collections::HashMap; +use std::mem::MaybeUninit; + +use crate::arch::cpuid::CpuidIn; +use crate::arch::tdx::TdAttr; use crate::hv::Result; +use crate::hv::kvm::tdx::tdx_op; use crate::hv::kvm::vm::KvmVm; -use crate::sys::kvm::{KvmCap, KvmHypercall}; +use crate::sys::kvm::{KvmCap, KvmCpuid2Flag, KvmCpuidEntry2, KvmHypercall}; +use crate::sys::tdx::{KvmTdxCapabilities, KvmTdxCmdId, KvmTdxInitVm}; impl KvmVm { pub fn tdx_init(&self) -> Result<()> { @@ -23,4 +31,55 @@ impl KvmVm { self.vm.enable_cap(KvmCap::X86_APIC_BUS_CYCLES_NS, 40)?; Ok(()) } + + fn tdx_get_capabilities(&self) -> Result> { + let mut caps: Box = + Box::new(unsafe { MaybeUninit::zeroed().assume_init() }); + caps.cpuid.nent = caps.cpuid.entries.len() as u32; + tdx_op(&self.vm.fd, KvmTdxCmdId::CAPABILITIES, 0, Some(&mut *caps))?; + Ok(caps) + } + + pub fn tdx_init_vm(&self, attr: TdAttr, cpuids: &HashMap) -> Result<()> { + let mut init: Box = Box::new(unsafe { MaybeUninit::zeroed().assume_init() }); + init.attributes = attr; + + let caps = self.tdx_get_capabilities()?; + let convert = |e: &KvmCpuidEntry2| { + let (mut in_, out) = From::from(*e); + if in_.index.is_none() { + in_.index = Some(0) + } + (in_, out) + }; + let caps_cpuid = caps.cpuid.entries.iter().take(caps.cpuid.nent as usize); + let caps_cpuid: HashMap<_, _> = caps_cpuid.map(convert).collect(); + for (in_, out) in cpuids { + let cap_cpuid_in = CpuidIn { + func: in_.func, + index: in_.index.or(Some(0)), + }; + let Some(cap_out) = caps_cpuid.get(&cap_cpuid_in) else { + continue; + }; + + let entry = &mut init.cpuid.entries[init.cpuid.nent as usize]; + entry.function = in_.func; + entry.index = in_.index.unwrap_or(0); + entry.flags = if in_.index.is_some() { + KvmCpuid2Flag::SIGNIFCANT_INDEX + } else { + KvmCpuid2Flag::empty() + }; + entry.eax = out.eax & cap_out.eax; + entry.ebx = out.ebx & cap_out.ebx; + entry.ecx = out.ecx & cap_out.ecx; + entry.edx = out.edx & cap_out.edx; + + init.cpuid.nent += 1; + } + + tdx_op(&self.vm.fd, KvmTdxCmdId::INIT_VM, 0, Some(&mut *init))?; + Ok(()) + } } diff --git a/alioth/src/sys/linux/tdx.rs b/alioth/src/sys/linux/tdx.rs index 45ea0e6d..aedebcfc 100644 --- a/alioth/src/sys/linux/tdx.rs +++ b/alioth/src/sys/linux/tdx.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::arch::tdx::TdAttr; -use crate::sys::kvm::KvmCpuid2; +use crate::sys::kvm::{KVM_MAX_CPUID_ENTRIES, KvmCpuid2}; use crate::{bitflags, consts}; consts! { @@ -39,7 +39,7 @@ pub struct KvmTdxCmd { #[repr(C)] #[derive(Debug, Clone)] -pub struct KvmTdxCapabilities { +pub struct KvmTdxCapabilities { pub supported_attrs: TdAttr, pub supported_xfam: u64, pub kernel_tdvmcallinfo_1_r11: u64, @@ -52,8 +52,8 @@ pub struct KvmTdxCapabilities { #[repr(C)] #[derive(Debug, Clone)] -pub struct KvmTdxInitVm { - pub attributes: u64, +pub struct KvmTdxInitVm { + pub attributes: TdAttr, pub xfam: u64, pub mrconfigid: [u8; 48], pub mrowner: [u8; 48], From 7e0d03553058901fcada9d57160027862e710aca Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 09:42:54 -0800 Subject: [PATCH 10/14] feat(tdx): add UEFI HOB definitions for TDX virtual machines Signed-off-by: Changyuan Lyu --- alioth/src/firmware/firmware.rs | 2 + alioth/src/firmware/uefi/uefi.rs | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 alioth/src/firmware/uefi/uefi.rs diff --git a/alioth/src/firmware/firmware.rs b/alioth/src/firmware/firmware.rs index 8237e857..fa2a50b4 100644 --- a/alioth/src/firmware/firmware.rs +++ b/alioth/src/firmware/firmware.rs @@ -19,6 +19,8 @@ pub mod acpi; pub mod dt; #[path = "ovmf/ovmf.rs"] pub mod ovmf; +#[path = "uefi/uefi.rs"] +pub mod uefi; use snafu::Snafu; diff --git a/alioth/src/firmware/uefi/uefi.rs b/alioth/src/firmware/uefi/uefi.rs new file mode 100644 index 00000000..90afd189 --- /dev/null +++ b/alioth/src/firmware/uefi/uefi.rs @@ -0,0 +1,77 @@ +// 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 zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::{bitflags, consts}; + +consts! { + #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)] + pub struct HobType(u16) { + HANDOFF = 0x0001; + RESOURCE_DESCRIPTOR = 0x0003; + END_OF_HOB_LIST = 0xffff; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct HobGenericHeader { + pub r#type: HobType, + pub length: u16, + pub reserved: u32, +} + +pub const HOB_HANDOFF_TABLE_VERSION: u32 = 0x9; + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct HobHandoffInfoTable { + pub hdr: HobGenericHeader, + pub version: u32, + pub boot_mode: u32, + pub memory_top: u64, + pub memory_bottom: u64, + pub free_memory_top: u64, + pub free_memory_bottom: u64, + pub end_of_hob_list: u64, +} + +consts! { + #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)] + pub struct HobResourceType(u32) { + SYSTEM_MEMORY = 0x00000000; + MEMORY_UNACCEPTED = 0x00000007; + } +} + +bitflags! { + #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)] + pub struct ResourceAttr(u32) { + PRESENT = 1 << 0; + INIT = 1 << 1; + TESTED = 1 << 2; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] +pub struct HobResourceDesc { + pub hdr: HobGenericHeader, + pub owner: [u8; 16], + pub r#type: HobResourceType, + pub attr: ResourceAttr, + pub address: u64, + pub len: u64, +} From be6a27dd5c00b9e40b5ff2a25cf36b3269ed0bf4 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 11:54:16 -0800 Subject: [PATCH 11/14] feat(tdx): parse TDVF and generate UEFI HOB Signed-off-by: Changyuan Lyu --- alioth/src/board/board_x86_64/board_x86_64.rs | 6 +- alioth/src/board/board_x86_64/tdx.rs | 47 +++++- alioth/src/firmware/firmware.rs | 4 + alioth/src/firmware/ovmf/ovmf_x86_64/tdx.rs | 154 +++++++++++++++++- .../src/firmware/ovmf/ovmf_x86_64/tdx_test.rs | 145 +++++++++++++++++ 5 files changed, 346 insertions(+), 10 deletions(-) create mode 100644 alioth/src/firmware/ovmf/ovmf_x86_64/tdx_test.rs diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 0e778208..ad352565 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; use std::mem::{offset_of, size_of, size_of_val}; use std::path::Path; use std::sync::Arc; -use std::sync::atomic::AtomicU32; +use std::sync::atomic::{AtomicU32, AtomicU64}; use parking_lot::Mutex; use snafu::ResultExt; @@ -53,6 +53,7 @@ where { cpuids: HashMap, sev_ap_eip: AtomicU32, + tdx_hob: AtomicU64, pub(crate) io_apic: Arc>, } @@ -167,6 +168,7 @@ impl ArchBoard { Ok(Self { cpuids, sev_ap_eip: AtomicU32::new(0), + tdx_hob: AtomicU64::new(0), io_apic: Arc::new(IoApic::new(vm.create_msi_sender()?)), }) } @@ -216,7 +218,7 @@ where match coco { Coco::AmdSev { policy } => self.setup_sev(fw, *policy), Coco::AmdSnp { .. } => self.setup_snp(fw), - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), + Coco::IntelTdx { .. } => self.setup_tdx(fw), } } diff --git a/alioth/src/board/board_x86_64/tdx.rs b/alioth/src/board/board_x86_64/tdx.rs index f1e054be..c5690e0e 100644 --- a/alioth/src/board/board_x86_64/tdx.rs +++ b/alioth/src/board/board_x86_64/tdx.rs @@ -13,11 +13,15 @@ // limitations under the License. use std::sync::Arc; +use std::sync::atomic::Ordering; +use crate::arch::layout::MEM_64_START; use crate::arch::tdx::TdAttr; -use crate::board::{Board, Result}; +use crate::board::{Board, Result, error}; +use crate::firmware::ovmf::tdx::{TdvfSectionType, create_hob, parse_entries}; use crate::hv::{Vm, VmMemory}; use crate::mem::MarkPrivateMemory; +use crate::mem::mapped::ArcMemPages; impl Board where @@ -29,4 +33,45 @@ where self.memory.register_change_callback(mark_private_memory)?; Ok(()) } + + pub(crate) fn create_hob(&self, dst: &mut [u8], mut accepted: Vec<(u64, u64)>) -> Result { + let hob_phys = self.arch.tdx_hob.load(Ordering::Relaxed); + let mut entries = self.memory.mem_region_entries(); + create_hob(dst, hob_phys, &mut entries, &mut accepted)?; + Ok(hob_phys) + } + + pub(crate) fn setup_tdx(&self, fw: &mut ArcMemPages) -> Result<()> { + let data = fw.as_slice(); + let entries = parse_entries(data)?; + + let fw_gpa = MEM_64_START - data.len() as u64; + self.memory + .mark_private_memory(fw_gpa, data.len() as _, true)?; + + let mut accepted = Vec::new(); + let mut hob_ram = None; + for entry in entries { + match entry.r#type { + TdvfSectionType::TD_HOB => { + let p = ArcMemPages::from_anonymous(entry.size as usize, None, None)?; + hob_ram = Some(p); + let tdx_hob = &self.arch.tdx_hob; + tdx_hob.store(entry.address, Ordering::Relaxed); + accepted.push((entry.address, entry.size)); + } + TdvfSectionType::TEMP_MEM => { + accepted.push((entry.address, entry.size)); + } + _ => {} + }; + } + + let Some(hob_ram) = &mut hob_ram else { + return error::MissingPayload.fail(); + }; + self.create_hob(hob_ram.as_slice_mut(), accepted)?; + + todo!() + } } diff --git a/alioth/src/firmware/firmware.rs b/alioth/src/firmware/firmware.rs index fa2a50b4..10c06a67 100644 --- a/alioth/src/firmware/firmware.rs +++ b/alioth/src/firmware/firmware.rs @@ -40,6 +40,10 @@ pub enum Error { MissingTdvfVersion { got: u32 }, #[snafu(display("Invalid firmware data layout"))] InvalidLayout, + #[snafu(display("Uncovered TDVF section"))] + UncoveredTdvfSection, + #[snafu(display("Failed to write HOB"))] + WriteHob { error: std::io::Error }, } type Result = std::result::Result; diff --git a/alioth/src/firmware/ovmf/ovmf_x86_64/tdx.rs b/alioth/src/firmware/ovmf/ovmf_x86_64/tdx.rs index 89a1f7dd..12520525 100644 --- a/alioth/src/firmware/ovmf/ovmf_x86_64/tdx.rs +++ b/alioth/src/firmware/ovmf/ovmf_x86_64/tdx.rs @@ -12,13 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; +use std::cmp::min; +use std::io::Write; -use crate::firmware::ovmf::x86_64::GUID_SIZE; -use crate::{bitflags, consts}; +use snafu::ResultExt; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; -use crate::firmware::ovmf::x86_64::parse_data; +use crate::firmware::ovmf::x86_64::{GUID_SIZE, parse_data}; +use crate::firmware::uefi::{ + HOB_HANDOFF_TABLE_VERSION, HobGenericHeader, HobHandoffInfoTable, HobResourceDesc, + HobResourceType, HobType, ResourceAttr, +}; use crate::firmware::{Result, error}; +use crate::mem::{MemRegionEntry, MemRegionType}; +use crate::{bitflags, consts}; pub const GUID_TDX_METADATA_OFFSET: [u8; GUID_SIZE] = [ 0x35, 0x65, 0x7a, 0xe4, 0x4a, 0x98, 0x98, 0x47, 0x86, 0x5e, 0x46, 0x85, 0xa7, 0xbf, 0x8e, 0xc2, @@ -57,9 +64,9 @@ bitflags! { #[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)] pub struct TdvfSectionEntry { pub data_offset: u32, - pub raw_data_size: u32, - pub memory_address: u64, - pub memory_data_size: u64, + pub data_size: u32, + pub address: u64, + pub size: u64, pub r#type: TdvfSectionType, pub attributes: TdvfSectionAttr, } @@ -98,3 +105,136 @@ pub fn parse_entries(data: &[u8]) -> Result<&[TdvfSectionEntry]> { }; Ok(entries) } + +fn create_hob_mem_resources( + entries: &[(u64, MemRegionEntry)], + accepted: &[(u64, u64)], + mut op: impl FnMut(HobResourceDesc) -> Result<()>, +) -> Result<()> { + let tmpl = HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: size_of::() as u16, + reserved: 0, + }, + owner: [0; 16], + attr: ResourceAttr::PRESENT | ResourceAttr::INIT | ResourceAttr::TESTED, + ..Default::default() + }; + let mut iter_e = entries.iter(); + let mut iter_a = accepted.iter(); + let mut section = iter_a.next().copied(); + let mut entry = iter_e.next().copied(); + loop { + match (&mut entry, &mut section) { + (None, None) => break, + (None, Some((start, size))) => { + log::error!( + "Section [{start:x}, {:x}) is not covered by system memory", + *start + *size + ); + return error::UncoveredTdvfSection.fail(); + } + (Some((_, e)), _) if e.type_ != MemRegionType::Ram => { + entry = iter_e.next().copied(); + continue; + } + (Some((s, e)), None) => { + op(HobResourceDesc { + r#type: HobResourceType::MEMORY_UNACCEPTED, + address: *s, + len: e.size, + ..tmpl.clone() + })?; + entry = iter_e.next().copied(); + } + (Some((s, e)), Some((start, size))) => { + if let Some(len) = min(*s, *start + *size).checked_sub(*start) + && len > 0 + { + *size = len; + entry = None; + // Jump to branch (Some, None) + continue; + } + if let Some(len) = min(*s + e.size, *start).checked_sub(*s) + && len > 0 + { + op(HobResourceDesc { + r#type: HobResourceType::MEMORY_UNACCEPTED, + address: *s, + len, + ..tmpl.clone() + })?; + *s += len; + e.size -= len; + } + if let Some(len) = min(*s + e.size, *start + *size).checked_sub(*start) + && len > 0 + { + op(HobResourceDesc { + r#type: HobResourceType::SYSTEM_MEMORY, + address: *start, + len, + ..tmpl.clone() + })?; + *start += len; + *size -= len; + *s += len; + e.size -= len + }; + if *size == 0 { + section = iter_a.next().copied(); + }; + if e.size == 0 { + entry = iter_e.next().copied(); + } + } + } + } + Ok(()) +} + +pub fn create_hob( + buffer: &mut [u8], + hob_phys: u64, + entries: &mut [(u64, MemRegionEntry)], + accepted: &mut [(u64, u64)], +) -> Result<()> { + entries.sort_by_key(|(s, _)| *s); + accepted.sort(); + + let Ok((table, mut dst)) = HobHandoffInfoTable::mut_from_prefix(buffer) else { + return error::InvalidLayout.fail(); + }; + + let mut desc_size = 0; + create_hob_mem_resources(entries, accepted, |d| { + desc_size += size_of_val(&d); + dst.write_all(d.as_bytes()).context(error::WriteHob) + })?; + + let end = HobGenericHeader { + r#type: HobType::END_OF_HOB_LIST, + length: size_of::() as u16, + reserved: 0, + }; + dst.write_all(end.as_bytes()).context(error::WriteHob)?; + + *table = HobHandoffInfoTable { + hdr: HobGenericHeader { + r#type: HobType::HANDOFF, + length: size_of::() as u16, + reserved: 0, + }, + version: HOB_HANDOFF_TABLE_VERSION, + end_of_hob_list: hob_phys + (size_of_val(table) + desc_size + size_of_val(&end)) as u64, + ..Default::default() + }; + + Ok(()) +} + +#[cfg(test)] +#[path = "tdx_test.rs"] +mod tests; diff --git a/alioth/src/firmware/ovmf/ovmf_x86_64/tdx_test.rs b/alioth/src/firmware/ovmf/ovmf_x86_64/tdx_test.rs new file mode 100644 index 00000000..f14ce521 --- /dev/null +++ b/alioth/src/firmware/ovmf/ovmf_x86_64/tdx_test.rs @@ -0,0 +1,145 @@ +// 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 assert_matches::assert_matches; +use zerocopy::{FromBytes, IntoBytes}; + +use crate::firmware::ovmf::x86_64::tdx::create_hob; +use crate::firmware::uefi::{ + HobGenericHeader, HobHandoffInfoTable, HobResourceDesc, HobResourceType, HobType, +}; +use crate::mem::{MemRegionEntry, MemRegionType}; + +#[test] +fn test_create_hob() { + let mut buffer = [0u64; 512]; + let buffer = buffer.as_mut_bytes(); + let hob_phys = 0x100000; + let mut entries = [ + ( + 0x0, + MemRegionEntry { + size: 0x40000000, + type_: MemRegionType::Ram, + }, + ), + ( + 0xe0000000, + MemRegionEntry { + size: 0x10000000, + type_: MemRegionType::Reserved, + }, + ), + ( + 0xfec00000, + MemRegionEntry { + size: 0x100, + type_: MemRegionType::Hidden, + }, + ), + ( + 0xffe00000, + MemRegionEntry { + size: 0x200000, + type_: MemRegionType::Reserved, + }, + ), + ]; + let mut accepted = [(0, 0xa0000), (0x100000, 0x2000), (0x102000, 0x1000)]; + create_hob(buffer, hob_phys, &mut entries, &mut accepted).unwrap(); + let (table, remain) = HobHandoffInfoTable::ref_from_prefix(buffer).unwrap(); + assert_matches!( + table, + HobHandoffInfoTable { + hdr: HobGenericHeader { + r#type: HobType::HANDOFF, + length: 0x38, + .. + }, + version: 9, + end_of_hob_list: 0x100130, + .. + } + ); + let (resources, remain) = <[HobResourceDesc]>::ref_from_prefix_with_elems(remain, 5).unwrap(); + assert_matches!( + resources, + [ + HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: 0x30, + .. + }, + r#type: HobResourceType::SYSTEM_MEMORY, + address: 0, + len: 0xa0000, + .. + }, + HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: 0x30, + .. + }, + r#type: HobResourceType::MEMORY_UNACCEPTED, + address: 0xa0000, + len: 0x60000, + .. + }, + HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: 0x30, + .. + }, + r#type: HobResourceType::SYSTEM_MEMORY, + address: 0x100000, + len: 0x2000, + .. + }, + HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: 0x30, + .. + }, + r#type: HobResourceType::SYSTEM_MEMORY, + address: 0x102000, + len: 0x1000, + .. + }, + HobResourceDesc { + hdr: HobGenericHeader { + r#type: HobType::RESOURCE_DESCRIPTOR, + length: 0x30, + .. + }, + r#type: HobResourceType::MEMORY_UNACCEPTED, + address: 0x103000, + len: 0x3fefd000, + .. + }, + ] + ); + let (end, _) = HobGenericHeader::ref_from_prefix(remain).unwrap(); + assert_matches!( + end, + HobGenericHeader { + r#type: HobType::END_OF_HOB_LIST, + length: 0x8, + .. + } + ); +} From b8296c408d31db02d6c2b756349dcd90b4296df3 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 11:57:43 -0800 Subject: [PATCH 12/14] feat(tdx): initialize VCPUs with HOB address Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 6 ++--- alioth/src/board/board_aarch64.rs | 2 +- alioth/src/board/board_x86_64/board_x86_64.rs | 26 ++++++++++++++----- alioth/src/board/board_x86_64/sev.rs | 13 ++-------- alioth/src/board/board_x86_64/tdx.rs | 14 +++++++--- alioth/src/hv/hv.rs | 3 +++ alioth/src/hv/kvm/vcpu/vcpu.rs | 5 ++++ alioth/src/hv/kvm/vcpu/vcpu_x86_64/tdx.rs | 4 +++ 8 files changed, 48 insertions(+), 25 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index 691e42f4..abf3afbf 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -312,14 +312,14 @@ where Ok(()) } - fn load_payload(&self) -> Result { + fn load_payload(&self, vcpu: &mut V::Vcpu) -> Result { let payload = self.payload.read(); let Some(payload) = payload.as_ref() else { return error::MissingPayload.fail(); }; if let Some(fw) = payload.firmware.as_ref() { - return self.setup_firmware(fw, payload); + return self.setup_firmware(fw, payload, vcpu); } let Some(exec) = &payload.executable else { @@ -441,7 +441,7 @@ where self.memory.add_mmio_dev(*addr, dev.clone())?; } self.add_pci_devs()?; - let init_state = self.load_payload()?; + let init_state = self.load_payload(vcpu)?; self.init_boot_vcpu(vcpu, &init_state)?; self.create_firmware_data(&init_state)?; } diff --git a/alioth/src/board/board_aarch64.rs b/alioth/src/board/board_aarch64.rs index 83f7fdd7..a37498bc 100644 --- a/alioth/src/board/board_aarch64.rs +++ b/alioth/src/board/board_aarch64.rs @@ -108,7 +108,7 @@ where encode_mpidr(&self.config.cpu.topology, index).0 } - pub fn setup_firmware(&self, _: &Path, _: &Payload) -> Result { + pub fn setup_firmware(&self, _: &Path, _: &Payload, _: &V::Vcpu) -> Result { unimplemented!() } diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index ad352565..74d931b8 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -211,20 +211,25 @@ where Ok(()) } - fn setup_coco(&self, fw: &mut ArcMemPages) -> Result<()> { + fn setup_coco(&self, fw: &mut ArcMemPages, vcpu: &V::Vcpu) -> Result<()> { let Some(coco) = &self.config.coco else { return Ok(()); }; match coco { Coco::AmdSev { policy } => self.setup_sev(fw, *policy), Coco::AmdSnp { .. } => self.setup_snp(fw), - Coco::IntelTdx { .. } => self.setup_tdx(fw), + Coco::IntelTdx { .. } => self.setup_tdx(fw, vcpu), } } - pub fn setup_firmware(&self, fw: &Path, payload: &Payload) -> Result { + pub fn setup_firmware( + &self, + fw: &Path, + payload: &Payload, + vcpu: &V::Vcpu, + ) -> Result { let (init_state, mut rom) = firmware::load(&self.memory, fw)?; - self.setup_coco(&mut rom)?; + self.setup_coco(&mut rom, vcpu)?; self.setup_fw_cfg(payload)?; Ok(init_state) } @@ -233,19 +238,26 @@ where let Some(coco) = &self.config.coco else { return Ok(()); }; + self.sync_vcpus(vcpus)?; + if index == 0 { + return Ok(()); + } match coco { Coco::AmdSev { policy } => { if policy.es() { - self.sev_init_ap(index, vcpu, vcpus)?; + self.sev_init_ap(vcpu)?; } } - Coco::AmdSnp { .. } => self.sev_init_ap(index, vcpu, vcpus)?, - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), + Coco::AmdSnp { .. } => self.sev_init_ap(vcpu)?, + Coco::IntelTdx { .. } => self.tdx_init_ap(vcpu)?, } Ok(()) } pub fn init_boot_vcpu(&self, vcpu: &mut V::Vcpu, init_state: &InitState) -> Result<()> { + if matches!(self.config.coco, Some(Coco::IntelTdx { .. })) { + return Ok(()); + } vcpu.set_sregs(&init_state.sregs, &init_state.seg_regs, &init_state.dt_regs)?; vcpu.set_regs(&init_state.regs)?; Ok(()) diff --git a/alioth/src/board/board_x86_64/sev.rs b/alioth/src/board/board_x86_64/sev.rs index b48eb816..a5f88dce 100644 --- a/alioth/src/board/board_x86_64/sev.rs +++ b/alioth/src/board/board_x86_64/sev.rs @@ -21,7 +21,7 @@ use zerocopy::FromZeros; use crate::arch::layout::MEM_64_START; use crate::arch::reg::{Reg, SegAccess, SegReg, SegRegVal}; use crate::arch::sev::{SevPolicy, SnpPageType, SnpPolicy}; -use crate::board::{Board, Result, VcpuGuard}; +use crate::board::{Board, Result}; use crate::firmware::ovmf::sev::{ SevDescType, SevMetadataDesc, SnpCpuidFunc, SnpCpuidInfo, parse_desc, parse_sev_ap_eip, }; @@ -137,16 +137,7 @@ where Ok(()) } - pub(crate) fn sev_init_ap( - &self, - index: u16, - vcpu: &mut V::Vcpu, - vcpus: &VcpuGuard, - ) -> Result<()> { - self.sync_vcpus(vcpus)?; - if index == 0 { - return Ok(()); - } + pub(crate) fn sev_init_ap(&self, vcpu: &mut V::Vcpu) -> Result<()> { let eip = self.arch.sev_ap_eip.load(Ordering::Acquire); vcpu.set_regs(&[(Reg::Rip, eip as u64 & 0xffff)])?; vcpu.set_sregs( diff --git a/alioth/src/board/board_x86_64/tdx.rs b/alioth/src/board/board_x86_64/tdx.rs index c5690e0e..756958f8 100644 --- a/alioth/src/board/board_x86_64/tdx.rs +++ b/alioth/src/board/board_x86_64/tdx.rs @@ -19,7 +19,7 @@ use crate::arch::layout::MEM_64_START; use crate::arch::tdx::TdAttr; use crate::board::{Board, Result, error}; use crate::firmware::ovmf::tdx::{TdvfSectionType, create_hob, parse_entries}; -use crate::hv::{Vm, VmMemory}; +use crate::hv::{Vcpu, Vm, VmMemory}; use crate::mem::MarkPrivateMemory; use crate::mem::mapped::ArcMemPages; @@ -41,7 +41,7 @@ where Ok(hob_phys) } - pub(crate) fn setup_tdx(&self, fw: &mut ArcMemPages) -> Result<()> { + pub(crate) fn setup_tdx(&self, fw: &mut ArcMemPages, vcpu: &V::Vcpu) -> Result<()> { let data = fw.as_slice(); let entries = parse_entries(data)?; @@ -70,8 +70,16 @@ where let Some(hob_ram) = &mut hob_ram else { return error::MissingPayload.fail(); }; - self.create_hob(hob_ram.as_slice_mut(), accepted)?; + let hob_phys = self.create_hob(hob_ram.as_slice_mut(), accepted)?; + + vcpu.tdx_init_vcpu(hob_phys)?; todo!() } + + pub(crate) fn tdx_init_ap(&self, vcpu: &mut V::Vcpu) -> Result<()> { + let hob = self.arch.tdx_hob.load(Ordering::Relaxed); + vcpu.tdx_init_vcpu(hob)?; + Ok(()) + } } diff --git a/alioth/src/hv/hv.rs b/alioth/src/hv/hv.rs index 727ae600..7c642b6c 100644 --- a/alioth/src/hv/hv.rs +++ b/alioth/src/hv/hv.rs @@ -218,6 +218,9 @@ pub trait Vcpu { self.set_regs(&[(Reg::Pc, pc + 4)]) } + #[cfg(target_arch = "x86_64")] + fn tdx_init_vcpu(&self, hob: u64) -> Result<()>; + #[cfg(target_arch = "x86_64")] fn tdx_init_mem_region(&self, data: &[u8], gpa: u64, measure: bool) -> Result<()>; } diff --git a/alioth/src/hv/kvm/vcpu/vcpu.rs b/alioth/src/hv/kvm/vcpu/vcpu.rs index f29b0587..07de7090 100644 --- a/alioth/src/hv/kvm/vcpu/vcpu.rs +++ b/alioth/src/hv/kvm/vcpu/vcpu.rs @@ -235,6 +235,11 @@ impl Vcpu for KvmVcpu { Ok(()) } + #[cfg(target_arch = "x86_64")] + fn tdx_init_vcpu(&self, hob: u64) -> Result<()> { + KvmVcpu::tdx_init_vcpu(self, hob) + } + #[cfg(target_arch = "x86_64")] fn tdx_init_mem_region(&self, data: &[u8], gpa: u64, measure: bool) -> Result<()> { KvmVcpu::tdx_init_mem_region(self, data, gpa, measure) diff --git a/alioth/src/hv/kvm/vcpu/vcpu_x86_64/tdx.rs b/alioth/src/hv/kvm/vcpu/vcpu_x86_64/tdx.rs index 8a3785f8..26345aa3 100644 --- a/alioth/src/hv/kvm/vcpu/vcpu_x86_64/tdx.rs +++ b/alioth/src/hv/kvm/vcpu/vcpu_x86_64/tdx.rs @@ -36,4 +36,8 @@ impl KvmVcpu { Some(&mut region), ) } + + pub fn tdx_init_vcpu(&self, mut hob: u64) -> Result<()> { + tdx_op(&self.fd, KvmTdxCmdId::INIT_VCPU, 0, Some(&mut hob)) + } } From 87265f0446bde479e74e238133416f42927f632b Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 11:58:58 -0800 Subject: [PATCH 13/14] feat(tdx): initialize memory regions for TDX VMs Signed-off-by: Changyuan Lyu --- alioth/src/board/board.rs | 2 ++ alioth/src/board/board_x86_64/tdx.rs | 29 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index abf3afbf..b5fff052 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -106,6 +106,8 @@ pub enum Error { MissingCpuid { leaf: CpuidIn }, #[snafu(display("Firmware error"), context(false))] Firmware { source: Box }, + #[snafu(display("Unknown firmware metadata"))] + UnknownFirmwareMetadata, } type Result = std::result::Result; diff --git a/alioth/src/board/board_x86_64/tdx.rs b/alioth/src/board/board_x86_64/tdx.rs index 756958f8..7155ed00 100644 --- a/alioth/src/board/board_x86_64/tdx.rs +++ b/alioth/src/board/board_x86_64/tdx.rs @@ -18,7 +18,7 @@ use std::sync::atomic::Ordering; use crate::arch::layout::MEM_64_START; use crate::arch::tdx::TdAttr; use crate::board::{Board, Result, error}; -use crate::firmware::ovmf::tdx::{TdvfSectionType, create_hob, parse_entries}; +use crate::firmware::ovmf::tdx::{TdvfSectionAttr, TdvfSectionType, create_hob, parse_entries}; use crate::hv::{Vcpu, Vm, VmMemory}; use crate::mem::MarkPrivateMemory; use crate::mem::mapped::ArcMemPages; @@ -74,7 +74,32 @@ where vcpu.tdx_init_vcpu(hob_phys)?; - todo!() + for entry in entries { + let tmp_mem; + let region = match entry.r#type { + TdvfSectionType::TD_HOB => hob_ram.as_slice(), + TdvfSectionType::TEMP_MEM => { + tmp_mem = ArcMemPages::from_anonymous(entry.size as usize, None, None)?; + tmp_mem.as_slice() + } + TdvfSectionType::BFV | TdvfSectionType::CFV => { + let start = entry.data_offset as usize; + let end = start + entry.size as usize; + let Some(d) = data.get(start..end) else { + return error::MissingPayload.fail(); + }; + d + } + t => { + log::error!("Unknown entry type: {t:x?}"); + return error::UnknownFirmwareMetadata.fail(); + } + }; + let measure = entry.attributes.contains(TdvfSectionAttr::MR_EXTEND); + vcpu.tdx_init_mem_region(region, entry.address, measure)?; + } + + Ok(()) } pub(crate) fn tdx_init_ap(&self, vcpu: &mut V::Vcpu) -> Result<()> { From 73c1c2e4e77a78955dffeb6b0a2770a08facf7f4 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sun, 22 Feb 2026 13:42:56 -0800 Subject: [PATCH 14/14] feat(tdx): finalize TDX virtual machine creation Signed-off-by: Changyuan Lyu --- alioth/src/board/board_x86_64/board_x86_64.rs | 2 +- alioth/src/board/board_x86_64/tdx.rs | 5 +++++ alioth/src/hv/hv.rs | 3 +++ alioth/src/hv/kvm/vm/vm.rs | 5 +++++ alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs | 4 ++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/alioth/src/board/board_x86_64/board_x86_64.rs b/alioth/src/board/board_x86_64/board_x86_64.rs index 74d931b8..70c668a7 100644 --- a/alioth/src/board/board_x86_64/board_x86_64.rs +++ b/alioth/src/board/board_x86_64/board_x86_64.rs @@ -352,7 +352,7 @@ where match coco { Coco::AmdSev { policy } => self.sev_finalize(*policy), Coco::AmdSnp { .. } => self.snp_finalize(), - Coco::IntelTdx { attr } => todo!("Intel TDX {attr:?}"), + Coco::IntelTdx { .. } => self.tdx_finalize(), } } diff --git a/alioth/src/board/board_x86_64/tdx.rs b/alioth/src/board/board_x86_64/tdx.rs index 7155ed00..bab2276f 100644 --- a/alioth/src/board/board_x86_64/tdx.rs +++ b/alioth/src/board/board_x86_64/tdx.rs @@ -107,4 +107,9 @@ where vcpu.tdx_init_vcpu(hob)?; Ok(()) } + + pub(crate) fn tdx_finalize(&self) -> Result<()> { + self.vm.tdx_finalize_vm()?; + Ok(()) + } } diff --git a/alioth/src/hv/hv.rs b/alioth/src/hv/hv.rs index 7c642b6c..2128f2ab 100644 --- a/alioth/src/hv/hv.rs +++ b/alioth/src/hv/hv.rs @@ -396,6 +396,9 @@ pub trait Vm { #[cfg(target_arch = "x86_64")] fn tdx_init_vm(&self, attr: TdAttr, cpuids: &HashMap) -> Result<()>; + #[cfg(target_arch = "x86_64")] + fn tdx_finalize_vm(&self) -> Result<()>; + #[cfg(target_arch = "aarch64")] type GicV2: GicV2; #[cfg(target_arch = "aarch64")] diff --git a/alioth/src/hv/kvm/vm/vm.rs b/alioth/src/hv/kvm/vm/vm.rs index 6c134d54..1e9f1719 100644 --- a/alioth/src/hv/kvm/vm/vm.rs +++ b/alioth/src/hv/kvm/vm/vm.rs @@ -766,6 +766,11 @@ impl Vm for KvmVm { KvmVm::tdx_init_vm(self, attr, cpuids) } + #[cfg(target_arch = "x86_64")] + fn tdx_finalize_vm(&self) -> Result<()> { + KvmVm::tdx_finalize_vm(self) + } + #[cfg(target_arch = "aarch64")] fn create_gic_v2(&self, distributor_base: u64, cpu_interface_base: u64) -> Result { aarch64::KvmGicV2::new(self, distributor_base, cpu_interface_base) diff --git a/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs index 586ec84b..a854b493 100644 --- a/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs +++ b/alioth/src/hv/kvm/vm/vm_x86_64/tdx.rs @@ -82,4 +82,8 @@ impl KvmVm { tdx_op(&self.vm.fd, KvmTdxCmdId::INIT_VM, 0, Some(&mut *init))?; Ok(()) } + + pub fn tdx_finalize_vm(&self) -> Result<()> { + tdx_op::<()>(&self.vm.fd, KvmTdxCmdId::FINALIZE_VM, 0, None) + } }