Skip to main content

openhcl_boot/arch/x86_64/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! SNP support for the bootshim.
5
6use super::address_space::LocalMap;
7use core::arch::asm;
8use core::sync::atomic::Ordering;
9use core::sync::atomic::fence;
10use memory_range::MemoryRange;
11use minimal_rt::arch::msr::read_msr;
12use minimal_rt::arch::msr::write_msr;
13use x86defs::X64_PAGE_SIZE;
14use x86defs::X86X_AMD_MSR_GHCB;
15use x86defs::snp::GhcbInfo;
16use x86defs::snp::GhcbMsr;
17
18#[cfg(feature = "cvm_boot_log")]
19use {
20    super::address_space::PAGE_TABLE_ENTRY_COUNT, super::address_space::X64_PAGE_SHIFT,
21    super::address_space::X64_PTE_ACCESSED, super::address_space::X64_PTE_PRESENT,
22    super::address_space::X64_PTE_READ_WRITE,
23    crate::arch::x86_64::address_space::X64_PTE_CONFIDENTIAL,
24    crate::single_threaded::SingleThreaded, bitfield_struct::bitfield, core::cell::Cell,
25    core::cell::UnsafeCell, core::sync::atomic::compiler_fence, hvdef::HvRegisterValue,
26    hvdef::HvX64RegisterName, hvdef::hypercall::HvInputVtl, hvdef::hypercall::HypercallOutput,
27    x86defs::snp::GhcbProtocolVersion, x86defs::snp::GhcbUsage, x86defs::snp::SevExitCode,
28    x86defs::snp::SevIoAccessInfo, zerocopy::IntoBytes,
29};
30
31/// Flush all cache lines covering a single page-sized region starting at the
32/// given virtual address.
33///
34/// On AMD SEV-SNP, the C-bit is part of the cache-line tag for a physical
35/// address. When transitioning a page between shared (C=0) and private (C=1)
36/// state, any cache lines tagged with the old C-bit setting must be evicted
37/// before the new mapping is accessed; otherwise stale cache lines can lead
38/// to data corruption observed via the new mapping. Callers must invoke this
39/// using a VA whose PTE has the C-bit setting that is being torn down.
40pub(super) fn cache_lines_flush_page(addr: u64) {
41    const FLUSH_SIZE: u64 = 64; // NOTE: hardcoded cache line size.
42    let start = addr & !(X64_PAGE_SIZE - 1);
43    let end = start + X64_PAGE_SIZE;
44
45    // Make sure there are no pending writes on the cache lines.
46    fence(Ordering::SeqCst);
47
48    for addr in (start..end).step_by(FLUSH_SIZE as usize) {
49        // SAFETY: No concurrency issues.
50        unsafe {
51            asm!("clflush [{0}]", in(reg) addr, options(nostack));
52        }
53    }
54}
55
56#[cfg(feature = "cvm_boot_log")]
57static GHCB_PREVIOUS: SingleThreaded<Cell<u64>> = SingleThreaded(Cell::new(0));
58
59pub struct Ghcb;
60
61#[derive(Debug)]
62pub enum AcceptGpaStatus {
63    Success,
64    Retry,
65}
66
67#[expect(dead_code)] // Printed via Debug in the error case.
68#[derive(Debug)]
69pub enum AcceptGpaError {
70    MemorySecurityViolation {
71        error_code: u32,
72        carry_flag: u32,
73        page_number: u64,
74        large_page: bool,
75        validate: bool,
76    },
77    Unknown,
78}
79
80struct GhcbCall {
81    extra_data: u64,
82    page_number: u64,
83    info: GhcbInfo,
84}
85
86// The memory mapping bits likely don't belong to this module, but
87// no centralized facility seems to exist for them yet.
88
89#[cfg(feature = "cvm_boot_log")]
90mod ghcb_page_mapping {
91    use super::*;
92
93    /// 4-level virtual address. The number of bits used in the VA
94    /// ought to be requested through CPUID. Here it is "hardcoded"
95    /// to 48 bits, which is the most common case.
96    #[bitfield(u64)]
97    pub struct VirtAddr4Level {
98        /// Offset inside the page.
99        #[bits(12)]
100        offset: usize,
101        /// PT index.
102        #[bits(9)]
103        pt_index: usize,
104        /// PD index.
105        #[bits(9)]
106        pd_index: usize,
107        /// PDP index.
108        #[bits(9)]
109        pdp_index: usize,
110        /// PML4 index.
111        #[bits(9)]
112        pml4_index: usize,
113        /// Reserved bits.
114        #[bits(16)]
115        reserved: usize,
116    }
117
118    impl VirtAddr4Level {
119        pub const fn canonicalize(&self) -> VirtAddr4Level {
120            // If PML4 is greater than 255, make it upper-half canonical
121            // by sign extending the PML4 index.
122            Self::from_bits((self.into_bits().wrapping_shl(16) as i64).wrapping_shr(16) as u64)
123        }
124    }
125
126    /// Page table.
127    #[repr(C, align(4096))]
128    pub struct PageTable {
129        pub entries: [u64; PAGE_TABLE_ENTRY_COUNT],
130    }
131
132    // Would be great to allocate these pages dynamically as otherwise they go
133    // into the IGVM file and require measurement through the PSP.
134
135    /// PDP table to map the GHCB
136    pub static PDP_TABLE: SingleThreaded<UnsafeCell<PageTable>> =
137        SingleThreaded(UnsafeCell::new(PageTable {
138            entries: [0; PAGE_TABLE_ENTRY_COUNT],
139        }));
140
141    /// PD table to map the GHCB
142    pub static PD_TABLE: SingleThreaded<UnsafeCell<PageTable>> =
143        SingleThreaded(UnsafeCell::new(PageTable {
144            entries: [0; PAGE_TABLE_ENTRY_COUNT],
145        }));
146
147    /// Page table to map the GHCB
148    pub static PAGE_TABLE: SingleThreaded<UnsafeCell<PageTable>> =
149        SingleThreaded(UnsafeCell::new(PageTable {
150            entries: [0; PAGE_TABLE_ENTRY_COUNT],
151        }));
152
153    pub const PML4_INDEX: usize = 0x1d0; // upper half mapping
154    pub const PDP_INDEX: usize = 0;
155    pub const PD_INDEX: usize = 0;
156    pub const PT_INDEX: usize = 0;
157    pub const GHCB_GVA: VirtAddr4Level = VirtAddr4Level::new()
158        .with_pt_index(PT_INDEX)
159        .with_pd_index(PD_INDEX)
160        .with_pdp_index(PDP_INDEX)
161        .with_pml4_index(PML4_INDEX)
162        .canonicalize();
163
164    pub fn get_cr3() -> u64 {
165        let mut cr3: u64;
166
167        // SAFETY: No access to the memory.
168        unsafe {
169            asm!("mov {0}, cr3", out(reg) cr3, options(nostack));
170        }
171        cr3
172    }
173
174    pub fn flush_tlb() {
175        fence(Ordering::SeqCst);
176        // NOTE: no flush for the global pages.
177        // SAFETY: No concurrency issues.
178        unsafe {
179            asm!("mov cr3, {0}", in(reg) get_cr3(), options(nostack));
180        }
181        compiler_fence(Ordering::SeqCst);
182    }
183
184    pub fn page_table(pfn: u64) -> &'static mut [u64] {
185        // SAFETY: The next page address must be set, identical mapping.
186        unsafe {
187            core::slice::from_raw_parts_mut(
188                (pfn << X64_PAGE_SHIFT) as *mut u64,
189                PAGE_TABLE_ENTRY_COUNT,
190            )
191        }
192    }
193
194    pub fn pte_for_pfn(pfn: u64, confidential: bool) -> u64 {
195        let common =
196            X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE | (pfn << X64_PAGE_SHIFT);
197        if confidential {
198            common | X64_PTE_CONFIDENTIAL
199        } else {
200            common
201        }
202    }
203} // mod ghcb_page_mapping
204
205#[cfg(feature = "cvm_boot_log")]
206use ghcb_page_mapping::*;
207
208/// GHCB page access. The GHCB page is statically allocated and
209/// initialized. The GHCB page might be accessed and modified
210/// concurrently by the (malicious) host, and the atomic accesses
211/// mitigate the possibility of torn reads/writes.
212#[cfg(feature = "cvm_boot_log")]
213mod ghcb_access {
214    use super::GHCB_GVA;
215    use crate::PageAlign;
216    use crate::arch::x86_64::address_space::X64_PAGE_SHIFT;
217    use crate::zeroed;
218    use core::mem::offset_of;
219    use core::sync::atomic::AtomicU8;
220    use core::sync::atomic::AtomicU16;
221    use core::sync::atomic::AtomicU32;
222    use core::sync::atomic::AtomicU64;
223    use core::sync::atomic::Ordering;
224    use x86defs::snp::GHCB_PAGE_HV_HYPERCALL_DATA_SIZE;
225    use x86defs::snp::GhcbPage;
226    use x86defs::snp::GhcbPageHvHypercall;
227    use x86defs::snp::GhcbProtocolVersion;
228    use x86defs::snp::GhcbSaveArea;
229    use x86defs::snp::GhcbUsage;
230
231    /// The GHCB page itself. Must not be *ever* accessed directly
232    /// using the static. It might be unaccepted at any time, and
233    /// the VA below is mapped with the C-bit set.
234    ///
235    /// The declaration is just a means to get the page statically
236    /// allocated and aligned.
237    static GHCB: PageAlign<[u8; size_of::<GhcbPage>()]> = zeroed();
238
239    pub fn page_number() -> u64 {
240        // Identical mapping, the GVA is the same as the GPA.
241        let gva = GHCB.0.as_ptr() as u64;
242        gva >> X64_PAGE_SHIFT
243    }
244
245    /// # Safety
246    ///
247    /// The caller must ensure that the GHCB page is properly mapped.
248    /// The host may concurrently modify the shared GHCB page.
249    unsafe fn ghcb_data<T>() -> &'static mut [T] {
250        // SAFETY: The GHCB page is statically allocated and initialized.
251        // It is either mapped by the time of access, or the code won't
252        // be executed at all due to the hardware fault.
253        unsafe {
254            core::slice::from_raw_parts_mut(
255                GHCB_GVA.into_bits() as *mut T,
256                size_of::<GhcbPage>() / size_of::<T>(),
257            )
258        }
259    }
260
261    // These macros provide atomic field access to the GHCB page.
262    //
263    // The GHCB page is shared memory between the guest and host. Because
264    // the host can modify it concurrently, all accesses use atomic
265    // operations.
266
267    macro_rules! ghcb_field_set {
268        ($field:ident, $type:ty, $val:expr) => {{
269            // SAFETY: Atomic access to the GHCB page.
270            let ghcb_data = unsafe { ghcb_data::<$type>() };
271            let pos = offset_of!(GhcbPage, $field) / size_of::<$type>();
272            ghcb_data[pos].store($val, Ordering::SeqCst);
273        }};
274    }
275
276    macro_rules! ghcb_save_field_set {
277        ($field:ident, $type:ty, $func:ident, $val:expr) => {{
278            // SAFETY: Atomic access to the GHCB page.
279            let ghcb_data = unsafe { ghcb_data::<$type>() };
280            // Save area is at the beginning of the GHCB page.
281            let pos = offset_of!(GhcbSaveArea, $field) / size_of::<$type>();
282            ghcb_data[pos].$func($val, Ordering::SeqCst);
283        }};
284    }
285
286    /// Atomically load a value from a `GhcbSaveArea` field.
287    macro_rules! ghcb_save_field_get {
288        ($field:ident, $type:ty) => {{
289            // SAFETY: Atomic access to the GHCB page.
290            let ghcb_data = unsafe { ghcb_data::<$type>() };
291            // Save area is at the beginning of the GHCB page.
292            let pos = offset_of!(GhcbSaveArea, $field) / size_of::<$type>();
293            ghcb_data[pos].load(Ordering::SeqCst)
294        }};
295    }
296
297    pub fn zero_page() {
298        // SAFETY: Atomic access to the GHCB page.
299        unsafe { ghcb_data::<AtomicU64>() }
300            .iter()
301            .for_each(|x| x.store(0, Ordering::SeqCst));
302    }
303
304    pub fn clear_bitmaps() {
305        ghcb_save_field_set!(valid_bitmap0, AtomicU64, store, 0);
306        ghcb_save_field_set!(valid_bitmap1, AtomicU64, store, 0);
307    }
308
309    macro_rules! ghcb_save_set_valid_bitmap0 {
310        ($save_field:ident) => {{
311            let mask = 1u64 << (offset_of!(GhcbSaveArea, $save_field) / 8);
312            ghcb_save_field_set!(valid_bitmap0, AtomicU64, fetch_or, mask);
313        }};
314    }
315
316    macro_rules! ghcb_save_set_valid_bitmap1 {
317        ($save_field:ident) => {{
318            let mask = 1u64 << (offset_of!(GhcbSaveArea, $save_field) / 8 - 64);
319            ghcb_save_field_set!(valid_bitmap1, AtomicU64, fetch_or, mask);
320        }};
321    }
322
323    /// Assert the valid bit in bitmap0 is set for the given save area field.
324    macro_rules! ghcb_save_assert_valid_bitmap0 {
325        ($save_field:ident) => {{
326            let mask = 1u64 << (offset_of!(GhcbSaveArea, $save_field) / 8);
327            assert_eq!(ghcb_save_field_get!(valid_bitmap0, AtomicU64) & mask, mask);
328        }};
329    }
330
331    /// Assert the valid bit in bitmap1 is set for the given save area field.
332    macro_rules! ghcb_save_assert_valid_bitmap1 {
333        ($save_field:ident) => {{
334            let mask = 1u64 << (offset_of!(GhcbSaveArea, $save_field) / 8 - 64);
335            assert_eq!(ghcb_save_field_get!(valid_bitmap1, AtomicU64) & mask, mask);
336        }};
337    }
338
339    pub fn set_usage(usage: GhcbUsage) {
340        ghcb_field_set!(ghcb_usage, AtomicU32, usage.into_bits());
341    }
342
343    pub fn set_protocol_version(version: GhcbProtocolVersion) {
344        ghcb_field_set!(protocol_version, AtomicU16, version.into_bits());
345    }
346
347    pub fn set_sw_exit_code(code: u64) {
348        ghcb_save_field_set!(sw_exit_code, AtomicU64, store, code);
349        ghcb_save_set_valid_bitmap1!(sw_exit_code);
350    }
351
352    pub fn set_sw_exit_info1(info: u64) {
353        ghcb_save_field_set!(sw_exit_info1, AtomicU64, store, info);
354        ghcb_save_set_valid_bitmap1!(sw_exit_info1);
355    }
356
357    pub fn sw_exit_info1() -> u64 {
358        ghcb_save_assert_valid_bitmap1!(sw_exit_info1);
359        ghcb_save_field_get!(sw_exit_info1, AtomicU64)
360    }
361
362    pub fn set_sw_exit_info2(info: u64) {
363        ghcb_save_field_set!(sw_exit_info2, AtomicU64, store, info);
364        ghcb_save_set_valid_bitmap1!(sw_exit_info2);
365    }
366
367    pub fn set_rax(rax: u64) {
368        ghcb_save_field_set!(rax, AtomicU64, store, rax);
369        ghcb_save_set_valid_bitmap0!(rax);
370    }
371
372    pub fn rax() -> u64 {
373        ghcb_save_assert_valid_bitmap0!(rax);
374        ghcb_save_field_get!(rax, AtomicU64)
375    }
376
377    pub fn set_rcx(rcx: u64) {
378        ghcb_save_field_set!(rcx, AtomicU64, store, rcx);
379        ghcb_save_set_valid_bitmap1!(rcx);
380    }
381
382    pub fn set_rdx(rdx: u64) {
383        ghcb_save_field_set!(rdx, AtomicU64, store, rdx);
384        ghcb_save_set_valid_bitmap1!(rdx);
385    }
386
387    pub fn rdx() -> u64 {
388        ghcb_save_assert_valid_bitmap1!(rdx);
389        ghcb_save_field_get!(rdx, AtomicU64)
390    }
391
392    /// # Safety
393    ///
394    /// The caller must ensure that the GHCB page is properly mapped
395    /// (via `Ghcb::initialize`) and there are no concurrent accesses
396    /// from this VP. The host may concurrently modify the shared page,
397    /// which is why all field accesses use atomic operations.
398    unsafe fn ghcb_hv_hypercall<T>() -> &'static mut [T] {
399        // SAFETY: The GHCB page is statically allocated and initialized.
400        // It is either mapped by the time of access, or the code won't
401        // be executed at all due to the hardware fault.
402        unsafe {
403            core::slice::from_raw_parts_mut(
404                GHCB_GVA.into_bits() as *mut T,
405                size_of::<GhcbPageHvHypercall>() / size_of::<T>(),
406            )
407        }
408    }
409
410    macro_rules! ghcb_hv_hypercall_field_set {
411        ($field:ident, $type:ty, $val:expr) => {{
412            // SAFETY: Atomic access to the GHCB page.
413            let ghcb_data = unsafe { ghcb_hv_hypercall::<$type>() };
414            let pos = offset_of!(GhcbPageHvHypercall, $field) / size_of::<$type>();
415            ghcb_data[pos].store($val, Ordering::SeqCst);
416        }};
417    }
418
419    macro_rules! ghcb_hv_hypercall_field_get {
420        ($field:ident, $type:ty) => {{
421            // SAFETY: Atomic access to the GHCB page.
422            let ghcb_data = unsafe { ghcb_hv_hypercall::<$type>() };
423            let pos = offset_of!(GhcbPageHvHypercall, $field) / size_of::<$type>();
424            ghcb_data[pos].load(Ordering::SeqCst)
425        }};
426    }
427
428    pub fn set_hypercall_data(data: &[u8], start: usize) {
429        // SAFETY: Atomic access to the GHCB page.
430        let ghcb_data = unsafe { ghcb_hv_hypercall::<AtomicU8>() };
431        assert!(start <= GHCB_PAGE_HV_HYPERCALL_DATA_SIZE);
432        assert!(data.len() <= GHCB_PAGE_HV_HYPERCALL_DATA_SIZE - start);
433
434        ghcb_data[start..start + data.len()]
435            .iter()
436            .zip(data.iter())
437            .for_each(|(x, y)| x.store(*y, Ordering::SeqCst));
438    }
439
440    pub fn set_hypercall_input(input: u64) {
441        ghcb_hv_hypercall_field_set!(io, AtomicU64, input);
442    }
443
444    pub fn hypercall_output() -> u64 {
445        ghcb_hv_hypercall_field_get!(io, AtomicU64)
446    }
447
448    pub fn set_hypercall_output_gpa(gpa: u64) {
449        ghcb_hv_hypercall_field_set!(output_gpa, AtomicU64, gpa);
450    }
451}
452
453#[cfg(feature = "cvm_boot_log")]
454#[expect(dead_code)]
455enum IoAccessSize {
456    Byte = 1,
457    Word = 2,
458    Dword = 4,
459}
460
461impl Ghcb {
462    #[inline(always)]
463    fn vmg_exit() {
464        // SAFETY: Using the `vmgexit` instruction forces an exit to the hypervisor but doesn't
465        // directly change program state.
466        unsafe {
467            asm!("rep vmmcall", options(nostack));
468        }
469    }
470
471    /// Perform the GHCB call
472    fn ghcb_call(call_data: GhcbCall) -> GhcbMsr {
473        let GhcbCall {
474            info,
475            extra_data,
476            page_number,
477        } = call_data;
478        let ghcb_control = GhcbMsr::new()
479            .with_pfn(page_number)
480            .with_info(info.0)
481            .with_extra_data(extra_data);
482
483        GhcbMsr::from_bits(
484            // SAFETY: Writing and reading known good value to/from the GHCB MSR, following the GHCB protocol.
485            unsafe {
486                write_msr(X86X_AMD_MSR_GHCB, ghcb_control.into_bits());
487                Self::vmg_exit();
488                read_msr(X86X_AMD_MSR_GHCB)
489            },
490        )
491    }
492
493    pub fn change_page_visibility(range: MemoryRange, host_visible: bool) {
494        for page_number in range.start_4k_gpn()..range.end_4k_gpn() {
495            let extra_data = if host_visible {
496                x86defs::snp::GHCB_DATA_PAGE_STATE_SHARED
497            } else {
498                x86defs::snp::GHCB_DATA_PAGE_STATE_PRIVATE
499            };
500
501            let resp = Self::ghcb_call(GhcbCall {
502                info: GhcbInfo::PAGE_STATE_CHANGE,
503                extra_data,
504                page_number,
505            });
506
507            // High 32 bits are status and should be 0 (HV_STATUS_SUCCESS), Low 32 bits should be
508            // GHCB_INFO_PAGE_STATE_UPDATED. Assert if otherwise.
509
510            assert!(
511                resp.into_bits() == GhcbInfo::PAGE_STATE_UPDATED.0,
512                "GhcbInfo::PAGE_STATE_UPDATED returned msr value {resp:x?}"
513            );
514        }
515    }
516}
517
518/// GHCB page-based protocol methods for serial logging support.
519/// These are only needed in dev builds for CVM boot logging.
520#[cfg(feature = "cvm_boot_log")]
521impl Ghcb {
522    pub fn initialize() {
523        // Make sure page alignment.
524        assert_eq!((PAGE_TABLE.get() as u64) & (X64_PAGE_SIZE - 1), 0);
525        assert_eq!((PD_TABLE.get() as u64) & (X64_PAGE_SIZE - 1), 0);
526        assert_eq!((PDP_TABLE.get() as u64) & (X64_PAGE_SIZE - 1), 0);
527
528        // Map the GHCB page in the guest as non-confidential.
529
530        let page_root = get_cr3() & !(X64_PAGE_SIZE - 1);
531        let pml4table = page_table(page_root >> X64_PAGE_SHIFT);
532        assert!(pml4table[PML4_INDEX] & X64_PTE_PRESENT == 0);
533
534        // Running in identical mapping.
535        let pdp_table_pfn = (PDP_TABLE.get() as u64) >> X64_PAGE_SHIFT;
536        let pd_table_pfn = (PD_TABLE.get() as u64) >> X64_PAGE_SHIFT;
537        let page_table_pfn = (PAGE_TABLE.get() as u64) >> X64_PAGE_SHIFT;
538        let page_number = ghcb_access::page_number();
539
540        let pdp_table = page_table(pdp_table_pfn);
541        let pd_table = page_table(pd_table_pfn);
542        let page_table = page_table(page_table_pfn);
543
544        pml4table[PML4_INDEX] = pte_for_pfn(pdp_table_pfn, true);
545        pdp_table[PDP_INDEX] = pte_for_pfn(pd_table_pfn, true);
546        pd_table[PD_INDEX] = pte_for_pfn(page_table_pfn, true);
547        page_table[PT_INDEX] = pte_for_pfn(page_number, true);
548
549        flush_tlb();
550
551        // Unaccept the page, invalidates page state.
552        pvalidate(page_number, GHCB_GVA.into_bits(), false, false).expect("memory unaccept");
553        // Issue VMG exit to request the hypervisor to update the page state to host visible in RMP.
554        let resp = Ghcb::ghcb_call(GhcbCall {
555            info: GhcbInfo::PAGE_STATE_CHANGE,
556            extra_data: x86defs::snp::GHCB_DATA_PAGE_STATE_SHARED,
557            page_number,
558        });
559        assert!(resp.into_bits() == GhcbInfo::PAGE_STATE_UPDATED.0);
560
561        // Map the page as non-confidential by updating the PTE.
562        page_table[PT_INDEX] = pte_for_pfn(page_number, false);
563        flush_tlb();
564        // Evict the page from the cache before changing the encrypted state.
565        cache_lines_flush_page(GHCB_GVA.into_bits());
566
567        // Flipping the C-bit makes the contents of the GHCB page scrambled,
568        // zero it out.
569        ghcb_access::zero_page();
570        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
571
572        // Register the GHCB page with the hypervisor.
573
574        let resp = Self::ghcb_call(GhcbCall {
575            extra_data: 0,
576            page_number: ghcb_access::page_number(),
577            info: GhcbInfo::REGISTER_REQUEST,
578        });
579        assert!(
580            resp.info() == GhcbInfo::REGISTER_RESPONSE.0
581                && resp.extra_data() == 0
582                && resp.pfn() == ghcb_access::page_number(),
583            "GhcbInfo::REGISTER_RESPONSE returned msr value {resp:x?}"
584        );
585
586        // Register to issue Hyper-V hypercalls.
587        let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new().with_os_id(1);
588        assert!(Self::set_msr(
589            hvdef::HV_X64_MSR_GUEST_OS_ID,
590            guest_os_id.into()
591        ));
592        // and make sure it is set as expected.
593        assert!(
594            Self::get_msr(hvdef::HV_X64_MSR_GUEST_OS_ID).expect("GHCB: Failed to set guest OS ID")
595                == guest_os_id.into()
596        );
597        Self::set_register(HvX64RegisterName::GuestOsId, guest_os_id.into_bits().into())
598            .expect("failed to set guest OS ID");
599
600        // SAFETY: Always safe to read the GHCB MSR, no concurrency issues.
601        GHCB_PREVIOUS.replace(unsafe { read_msr(X86X_AMD_MSR_GHCB) });
602    }
603
604    pub fn uninitialize() {
605        // Unregister from issuing Hyper-V hypercalls.
606        let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new();
607        Self::set_register(HvX64RegisterName::GuestOsId, guest_os_id.into_bits().into())
608            .expect("failed to set guest OS ID");
609        assert!(Self::set_msr(
610            hvdef::HV_X64_MSR_GUEST_OS_ID,
611            guest_os_id.into()
612        ));
613        // and make sure it is set as expected.
614        assert!(
615            Self::get_msr(hvdef::HV_X64_MSR_GUEST_OS_ID).expect("GHCB: Failed to set guest OS ID")
616                == guest_os_id.into()
617        );
618
619        // Tell the hypervisor that the GHCB page is at GPA 0 now.
620        // This causes it to unmap the overlay page and let the `pvalidate`
621        // below succeed.
622        //
623        // Soon after this, the GHCB page will be mapped by the kernel at the
624        // GPA of its choosing. The temporary mapping at GPA 0 poses no
625        // security risk as that page does not contain any sensitive data
626        // in the IGVM file.
627        //
628        // TODO: Once support for unmapping the GHCB page from the latest SEV-ES
629        // specification is added, this will be removed in favor of the standard
630        // unmap operation.
631        let resp = Self::ghcb_call(GhcbCall {
632            extra_data: 0,
633            page_number: 0,
634            info: GhcbInfo::REGISTER_REQUEST,
635        });
636        assert!(
637            resp.info() == GhcbInfo::REGISTER_RESPONSE.0
638                && resp.extra_data() == 0
639                && resp.pfn() == 0,
640            "GhcbInfo::REGISTER_RESPONSE returned msr value {resp:x?}"
641        );
642
643        // Map the GHCB page in the guest as confidential and accept it again
644        // to return to the original state.
645
646        // Evict the page from the cache before changing the encrypted state.
647        cache_lines_flush_page(GHCB_GVA.into_bits());
648
649        // Update the page table entry to make it confidential.
650        // Running in identical mapping.
651        let page_table_pfn = (PAGE_TABLE.get() as u64) >> X64_PAGE_SHIFT;
652        let page_table = page_table(page_table_pfn);
653        let page_number = ghcb_access::page_number();
654
655        page_table[PT_INDEX] |= X64_PTE_CONFIDENTIAL;
656        flush_tlb();
657
658        // Issue VMG exit to request the hypervisor to update the page state to private in RMP.
659        let resp = Ghcb::ghcb_call(GhcbCall {
660            info: GhcbInfo::PAGE_STATE_CHANGE,
661            extra_data: x86defs::snp::GHCB_DATA_PAGE_STATE_PRIVATE,
662            page_number,
663        });
664        assert!(resp.into_bits() == GhcbInfo::PAGE_STATE_UPDATED.0);
665
666        // Accept the page, invalidates page state.
667        pvalidate(page_number, GHCB_GVA.into_bits(), false, true).expect("memory accept");
668
669        flush_tlb();
670
671        ghcb_access::zero_page();
672
673        // SAFETY: Always safe to write the GHCB MSR, no concurrency issues.
674        unsafe { write_msr(X86X_AMD_MSR_GHCB, GHCB_PREVIOUS.get()) };
675    }
676
677    fn io_port_exit(port: u16, access_size: IoAccessSize, is_read: bool, data: Option<u32>) {
678        ghcb_access::set_usage(GhcbUsage::BASE);
679        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
680        ghcb_access::clear_bitmaps();
681
682        let io_exit_info = SevIoAccessInfo::new()
683            .with_port(port)
684            .with_read_access(is_read);
685        let io_exit_info = match access_size {
686            IoAccessSize::Byte => io_exit_info.with_access_size8(true),
687            IoAccessSize::Word => io_exit_info.with_access_size16(true),
688            IoAccessSize::Dword => io_exit_info.with_access_size32(true),
689        };
690
691        ghcb_access::set_sw_exit_code(SevExitCode::IOIO.0);
692        ghcb_access::set_sw_exit_info1(io_exit_info.into_bits().into());
693        ghcb_access::set_sw_exit_info2(0);
694
695        if let Some(data) = data {
696            ghcb_access::set_rax(data as u64);
697        }
698
699        Self::ghcb_call(GhcbCall {
700            info: GhcbInfo::NORMAL,
701            extra_data: 0,
702            page_number: ghcb_access::page_number(),
703        });
704        ghcb_access::set_usage(GhcbUsage::INVALID);
705    }
706
707    #[must_use]
708    fn read_io_port(port: u16, access_size: IoAccessSize) -> Option<u32> {
709        Self::io_port_exit(port, access_size, true, None);
710
711        if ghcb_access::sw_exit_info1() != 0 {
712            None
713        } else {
714            Some(ghcb_access::rax() as u32)
715        }
716    }
717
718    #[must_use]
719    fn write_io_port(port: u16, access_size: IoAccessSize, data: u32) -> bool {
720        Self::io_port_exit(port, access_size, false, Some(data));
721
722        ghcb_access::sw_exit_info1() == 0
723    }
724
725    #[must_use]
726    pub fn set_msr(msr_index: u32, value: u64) -> bool {
727        ghcb_access::set_usage(GhcbUsage::BASE);
728        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
729        ghcb_access::clear_bitmaps();
730
731        ghcb_access::set_sw_exit_code(SevExitCode::MSR.0);
732        ghcb_access::set_sw_exit_info1(1);
733        ghcb_access::set_sw_exit_info2(0);
734
735        ghcb_access::set_rcx(msr_index as u64);
736        ghcb_access::set_rax(value as u32 as u64);
737        ghcb_access::set_rdx((value >> 32) as u32 as u64);
738
739        Self::ghcb_call(GhcbCall {
740            info: GhcbInfo::NORMAL,
741            extra_data: 0,
742            page_number: ghcb_access::page_number(),
743        });
744        ghcb_access::set_usage(GhcbUsage::INVALID);
745
746        ghcb_access::sw_exit_info1() == 0
747    }
748
749    #[must_use]
750    pub fn get_msr(msr_index: u32) -> Option<u64> {
751        ghcb_access::set_usage(GhcbUsage::BASE);
752        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
753        ghcb_access::clear_bitmaps();
754
755        ghcb_access::set_sw_exit_code(SevExitCode::MSR.0);
756        ghcb_access::set_sw_exit_info1(0);
757        ghcb_access::set_sw_exit_info2(0);
758
759        ghcb_access::set_rcx(msr_index as u64);
760
761        Self::ghcb_call(GhcbCall {
762            info: GhcbInfo::NORMAL,
763            extra_data: 0,
764            page_number: ghcb_access::page_number(),
765        });
766        ghcb_access::set_usage(GhcbUsage::INVALID);
767
768        if ghcb_access::sw_exit_info1() != 0 {
769            None
770        } else {
771            Some(ghcb_access::rax() | (ghcb_access::rdx() << 32))
772        }
773    }
774
775    pub fn set_register(
776        name: HvX64RegisterName,
777        value: HvRegisterValue,
778    ) -> Result<(), hvdef::HvError> {
779        let header = hvdef::hypercall::GetSetVpRegisters {
780            partition_id: hvdef::HV_PARTITION_ID_SELF,
781            vp_index: hvdef::HV_VP_INDEX_SELF,
782            target_vtl: HvInputVtl::CURRENT_VTL,
783            rsvd: [0; 3],
784        };
785        let reg_assoc = hvdef::hypercall::HvRegisterAssoc {
786            name: name.into(),
787            pad: Default::default(),
788            value,
789        };
790        let control = hvdef::hypercall::Control::new()
791            .with_code(hvdef::HypercallCode::HvCallSetVpRegisters.0)
792            .with_rep_count(1);
793
794        ghcb_access::set_usage(GhcbUsage::HYPERCALL);
795        ghcb_access::set_hypercall_data(header.as_bytes(), 0);
796        ghcb_access::set_hypercall_data(reg_assoc.as_bytes(), size_of_val(&header));
797        ghcb_access::set_hypercall_input(control.into_bits());
798        ghcb_access::set_hypercall_output_gpa(0);
799
800        Self::ghcb_call(GhcbCall {
801            info: GhcbInfo::NORMAL,
802            extra_data: 0,
803            page_number: ghcb_access::page_number(),
804        });
805        ghcb_access::set_usage(GhcbUsage::INVALID);
806
807        HypercallOutput::from_bits(ghcb_access::hypercall_output()).result()
808    }
809}
810
811/// Wrapper around the pvalidate assembly instruction.
812fn pvalidate(
813    page_number: u64,
814    va: u64,
815    large_page: bool,
816    validate: bool,
817) -> Result<AcceptGpaStatus, AcceptGpaError> {
818    if large_page {
819        assert!(va.is_multiple_of(x86defs::X64_LARGE_PAGE_SIZE));
820    } else {
821        assert!(va.is_multiple_of(hvdef::HV_PAGE_SIZE))
822    }
823
824    let validate_page = validate as u32;
825    let page_size = large_page as u32;
826    let mut error_code: u32;
827    let mut carry_flag: u32 = 0;
828
829    // SAFETY: Issuing pvalidate according to specification.
830    unsafe {
831        asm!(r#"
832        pvalidate
833        jnc 2f
834        inc {carry_flag:e}
835        2:
836        "#,
837        in("rax") va,
838        in("ecx") page_size,
839        in("edx") validate_page,
840        lateout("eax") error_code,
841        carry_flag = inout(reg) carry_flag);
842    }
843
844    const SEV_SUCCESS: u32 = 0;
845    const SEV_FAIL_SIZEMISMATCH: u32 = 6;
846
847    match (error_code, carry_flag) {
848        (SEV_SUCCESS, 0) => Ok(AcceptGpaStatus::Success),
849        (SEV_FAIL_SIZEMISMATCH, _) => Ok(AcceptGpaStatus::Retry),
850        _ => Err(AcceptGpaError::MemorySecurityViolation {
851            error_code,
852            carry_flag,
853            page_number,
854            large_page,
855            validate,
856        }),
857    }
858}
859
860/// Accepts or unaccepts a specific gpa range. On SNP systems, this corresponds to issuing a
861/// pvalidate over the GPA range with the desired value of the validate bit.
862pub fn set_page_acceptance(
863    local_map: &mut LocalMap<'_>,
864    range: MemoryRange,
865    validate: bool,
866) -> Result<(), AcceptGpaError> {
867    let pages_per_large_page = x86defs::X64_LARGE_PAGE_SIZE / X64_PAGE_SIZE;
868    let mut page_count = range.page_count_4k();
869    let mut page_base = range.start_4k_gpn();
870
871    while page_count != 0 {
872        // Attempt to validate a large page.
873        // Even when pvalidating a large page, the processor only does a 1 byte read. As a result
874        // mapping a single page is sufficient.
875        let mapping = local_map.map_pages(
876            MemoryRange::from_4k_gpn_range(page_base..page_base + 1),
877            true,
878        );
879        if page_base.is_multiple_of(pages_per_large_page) && page_count >= pages_per_large_page {
880            let res = pvalidate(page_base, mapping.data.as_ptr() as u64, true, validate)?;
881            match res {
882                AcceptGpaStatus::Success => {
883                    page_count -= pages_per_large_page;
884                    page_base += pages_per_large_page;
885                    continue;
886                }
887                AcceptGpaStatus::Retry => (),
888            }
889        }
890
891        // Attempt to validate a regular sized page.
892        let res = pvalidate(page_base, mapping.data.as_ptr() as u64, false, validate)?;
893        match res {
894            AcceptGpaStatus::Success => {
895                page_count -= 1;
896                page_base += 1;
897            }
898            AcceptGpaStatus::Retry => {
899                // Cannot retry on a regular sized page.
900                return Err(AcceptGpaError::Unknown);
901            }
902        }
903    }
904
905    Ok(())
906}
907
908/// GHCB based io port access.
909#[cfg(feature = "cvm_boot_log")]
910pub struct SnpIoAccess;
911
912#[cfg(feature = "cvm_boot_log")]
913impl minimal_rt::arch::IoAccess for SnpIoAccess {
914    unsafe fn inb(&self, port: u16) -> u8 {
915        // Best effort
916        Ghcb::read_io_port(port, IoAccessSize::Byte).unwrap_or(!0) as u8
917    }
918
919    unsafe fn outb(&self, port: u16, data: u8) {
920        // Best effort
921        let _ = Ghcb::write_io_port(port, IoAccessSize::Byte, data as u32);
922    }
923}