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        // Evict the page from the cache before changing the encrypted state.
551        cache_lines_flush_page(GHCB_GVA.into_bits());
552
553        // Unaccept the page, invalidates page state.
554        pvalidate(page_number, GHCB_GVA.into_bits(), false, false).expect("memory unaccept");
555        // Issue VMG exit to request the hypervisor to update the page state to host visible in RMP.
556        let resp = Ghcb::ghcb_call(GhcbCall {
557            info: GhcbInfo::PAGE_STATE_CHANGE,
558            extra_data: x86defs::snp::GHCB_DATA_PAGE_STATE_SHARED,
559            page_number,
560        });
561        assert!(resp.into_bits() == GhcbInfo::PAGE_STATE_UPDATED.0);
562
563        // Map the page as non-confidential by updating the PTE.
564        page_table[PT_INDEX] = pte_for_pfn(page_number, false);
565        flush_tlb();
566        // Evict the page from the cache before changing the encrypted state.
567        cache_lines_flush_page(GHCB_GVA.into_bits());
568
569        // Flipping the C-bit makes the contents of the GHCB page scrambled,
570        // zero it out.
571        ghcb_access::zero_page();
572        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
573
574        // Register the GHCB page with the hypervisor.
575
576        let resp = Self::ghcb_call(GhcbCall {
577            extra_data: 0,
578            page_number: ghcb_access::page_number(),
579            info: GhcbInfo::REGISTER_REQUEST,
580        });
581        assert!(
582            resp.info() == GhcbInfo::REGISTER_RESPONSE.0
583                && resp.extra_data() == 0
584                && resp.pfn() == ghcb_access::page_number(),
585            "GhcbInfo::REGISTER_RESPONSE returned msr value {resp:x?}"
586        );
587
588        // Register to issue Hyper-V hypercalls.
589        let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new().with_os_id(1);
590        assert!(Self::set_msr(
591            hvdef::HV_X64_MSR_GUEST_OS_ID,
592            guest_os_id.into()
593        ));
594        // and make sure it is set as expected.
595        assert!(
596            Self::get_msr(hvdef::HV_X64_MSR_GUEST_OS_ID).expect("GHCB: Failed to set guest OS ID")
597                == guest_os_id.into()
598        );
599        Self::set_register(HvX64RegisterName::GuestOsId, guest_os_id.into_bits().into())
600            .expect("failed to set guest OS ID");
601
602        // SAFETY: Always safe to read the GHCB MSR, no concurrency issues.
603        GHCB_PREVIOUS.replace(unsafe { read_msr(X86X_AMD_MSR_GHCB) });
604    }
605
606    pub fn uninitialize() {
607        // Unregister from issuing Hyper-V hypercalls.
608        let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new();
609        Self::set_register(HvX64RegisterName::GuestOsId, guest_os_id.into_bits().into())
610            .expect("failed to set guest OS ID");
611        assert!(Self::set_msr(
612            hvdef::HV_X64_MSR_GUEST_OS_ID,
613            guest_os_id.into()
614        ));
615        // and make sure it is set as expected.
616        assert!(
617            Self::get_msr(hvdef::HV_X64_MSR_GUEST_OS_ID).expect("GHCB: Failed to set guest OS ID")
618                == guest_os_id.into()
619        );
620
621        // Tell the hypervisor that the GHCB page is at GPA 0 now.
622        // This causes it to unmap the overlay page and let the `pvalidate`
623        // below succeed.
624        //
625        // Soon after this, the GHCB page will be mapped by the kernel at the
626        // GPA of its choosing. The temporary mapping at GPA 0 poses no
627        // security risk as that page does not contain any sensitive data
628        // in the IGVM file.
629        //
630        // TODO: Once support for unmapping the GHCB page from the latest SEV-ES
631        // specification is added, this will be removed in favor of the standard
632        // unmap operation.
633        let resp = Self::ghcb_call(GhcbCall {
634            extra_data: 0,
635            page_number: 0,
636            info: GhcbInfo::REGISTER_REQUEST,
637        });
638        assert!(
639            resp.info() == GhcbInfo::REGISTER_RESPONSE.0
640                && resp.extra_data() == 0
641                && resp.pfn() == 0,
642            "GhcbInfo::REGISTER_RESPONSE returned msr value {resp:x?}"
643        );
644
645        // Map the GHCB page in the guest as confidential and accept it again
646        // to return to the original state.
647
648        // Evict the page from the cache before changing the encrypted state.
649        cache_lines_flush_page(GHCB_GVA.into_bits());
650
651        // Update the page table entry to make it confidential.
652        // Running in identical mapping.
653        let page_table_pfn = (PAGE_TABLE.get() as u64) >> X64_PAGE_SHIFT;
654        let page_table = page_table(page_table_pfn);
655        let page_number = ghcb_access::page_number();
656
657        page_table[PT_INDEX] |= X64_PTE_CONFIDENTIAL;
658        flush_tlb();
659
660        // Issue VMG exit to request the hypervisor to update the page state to private in RMP.
661        let resp = Ghcb::ghcb_call(GhcbCall {
662            info: GhcbInfo::PAGE_STATE_CHANGE,
663            extra_data: x86defs::snp::GHCB_DATA_PAGE_STATE_PRIVATE,
664            page_number,
665        });
666        assert!(resp.into_bits() == GhcbInfo::PAGE_STATE_UPDATED.0);
667
668        // Accept the page, invalidates page state.
669        pvalidate(page_number, GHCB_GVA.into_bits(), false, true).expect("memory accept");
670
671        flush_tlb();
672
673        ghcb_access::zero_page();
674
675        // SAFETY: Always safe to write the GHCB MSR, no concurrency issues.
676        unsafe { write_msr(X86X_AMD_MSR_GHCB, GHCB_PREVIOUS.get()) };
677    }
678
679    fn io_port_exit(port: u16, access_size: IoAccessSize, is_read: bool, data: Option<u32>) {
680        ghcb_access::set_usage(GhcbUsage::BASE);
681        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
682        ghcb_access::clear_bitmaps();
683
684        let io_exit_info = SevIoAccessInfo::new()
685            .with_port(port)
686            .with_read_access(is_read);
687        let io_exit_info = match access_size {
688            IoAccessSize::Byte => io_exit_info.with_access_size8(true),
689            IoAccessSize::Word => io_exit_info.with_access_size16(true),
690            IoAccessSize::Dword => io_exit_info.with_access_size32(true),
691        };
692
693        ghcb_access::set_sw_exit_code(SevExitCode::IOIO.0);
694        ghcb_access::set_sw_exit_info1(io_exit_info.into_bits().into());
695        ghcb_access::set_sw_exit_info2(0);
696
697        if let Some(data) = data {
698            ghcb_access::set_rax(data as u64);
699        }
700
701        Self::ghcb_call(GhcbCall {
702            info: GhcbInfo::NORMAL,
703            extra_data: 0,
704            page_number: ghcb_access::page_number(),
705        });
706        ghcb_access::set_usage(GhcbUsage::INVALID);
707    }
708
709    #[must_use]
710    fn read_io_port(port: u16, access_size: IoAccessSize) -> Option<u32> {
711        Self::io_port_exit(port, access_size, true, None);
712
713        if ghcb_access::sw_exit_info1() != 0 {
714            None
715        } else {
716            Some(ghcb_access::rax() as u32)
717        }
718    }
719
720    #[must_use]
721    fn write_io_port(port: u16, access_size: IoAccessSize, data: u32) -> bool {
722        Self::io_port_exit(port, access_size, false, Some(data));
723
724        ghcb_access::sw_exit_info1() == 0
725    }
726
727    #[must_use]
728    pub fn set_msr(msr_index: u32, value: u64) -> bool {
729        ghcb_access::set_usage(GhcbUsage::BASE);
730        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
731        ghcb_access::clear_bitmaps();
732
733        ghcb_access::set_sw_exit_code(SevExitCode::MSR.0);
734        ghcb_access::set_sw_exit_info1(1);
735        ghcb_access::set_sw_exit_info2(0);
736
737        ghcb_access::set_rcx(msr_index as u64);
738        ghcb_access::set_rax(value as u32 as u64);
739        ghcb_access::set_rdx((value >> 32) as u32 as u64);
740
741        Self::ghcb_call(GhcbCall {
742            info: GhcbInfo::NORMAL,
743            extra_data: 0,
744            page_number: ghcb_access::page_number(),
745        });
746        ghcb_access::set_usage(GhcbUsage::INVALID);
747
748        ghcb_access::sw_exit_info1() == 0
749    }
750
751    #[must_use]
752    pub fn get_msr(msr_index: u32) -> Option<u64> {
753        ghcb_access::set_usage(GhcbUsage::BASE);
754        ghcb_access::set_protocol_version(GhcbProtocolVersion::V2);
755        ghcb_access::clear_bitmaps();
756
757        ghcb_access::set_sw_exit_code(SevExitCode::MSR.0);
758        ghcb_access::set_sw_exit_info1(0);
759        ghcb_access::set_sw_exit_info2(0);
760
761        ghcb_access::set_rcx(msr_index as u64);
762
763        Self::ghcb_call(GhcbCall {
764            info: GhcbInfo::NORMAL,
765            extra_data: 0,
766            page_number: ghcb_access::page_number(),
767        });
768        ghcb_access::set_usage(GhcbUsage::INVALID);
769
770        if ghcb_access::sw_exit_info1() != 0 {
771            None
772        } else {
773            Some(ghcb_access::rax() | (ghcb_access::rdx() << 32))
774        }
775    }
776
777    pub fn set_register(
778        name: HvX64RegisterName,
779        value: HvRegisterValue,
780    ) -> Result<(), hvdef::HvError> {
781        let header = hvdef::hypercall::GetSetVpRegisters {
782            partition_id: hvdef::HV_PARTITION_ID_SELF,
783            vp_index: hvdef::HV_VP_INDEX_SELF,
784            target_vtl: HvInputVtl::CURRENT_VTL,
785            rsvd: [0; 3],
786        };
787        let reg_assoc = hvdef::hypercall::HvRegisterAssoc {
788            name: name.into(),
789            pad: Default::default(),
790            value,
791        };
792        let control = hvdef::hypercall::Control::new()
793            .with_code(hvdef::HypercallCode::HvCallSetVpRegisters.0)
794            .with_rep_count(1);
795
796        ghcb_access::set_usage(GhcbUsage::HYPERCALL);
797        ghcb_access::set_hypercall_data(header.as_bytes(), 0);
798        ghcb_access::set_hypercall_data(reg_assoc.as_bytes(), size_of_val(&header));
799        ghcb_access::set_hypercall_input(control.into_bits());
800        ghcb_access::set_hypercall_output_gpa(0);
801
802        Self::ghcb_call(GhcbCall {
803            info: GhcbInfo::NORMAL,
804            extra_data: 0,
805            page_number: ghcb_access::page_number(),
806        });
807        ghcb_access::set_usage(GhcbUsage::INVALID);
808
809        HypercallOutput::from_bits(ghcb_access::hypercall_output()).result()
810    }
811}
812
813/// Wrapper around the pvalidate assembly instruction.
814fn pvalidate(
815    page_number: u64,
816    va: u64,
817    large_page: bool,
818    validate: bool,
819) -> Result<AcceptGpaStatus, AcceptGpaError> {
820    if large_page {
821        assert!(va.is_multiple_of(x86defs::X64_LARGE_PAGE_SIZE));
822    } else {
823        assert!(va.is_multiple_of(hvdef::HV_PAGE_SIZE))
824    }
825
826    let validate_page = validate as u32;
827    let page_size = large_page as u32;
828    let mut error_code: u32;
829    let mut carry_flag: u32 = 0;
830
831    // SAFETY: Issuing pvalidate according to specification.
832    unsafe {
833        asm!(r#"
834        pvalidate
835        jnc 2f
836        inc {carry_flag:e}
837        2:
838        "#,
839        in("rax") va,
840        in("ecx") page_size,
841        in("edx") validate_page,
842        lateout("eax") error_code,
843        carry_flag = inout(reg) carry_flag);
844    }
845
846    const SEV_SUCCESS: u32 = 0;
847    const SEV_FAIL_SIZEMISMATCH: u32 = 6;
848
849    match (error_code, carry_flag) {
850        (SEV_SUCCESS, 0) => Ok(AcceptGpaStatus::Success),
851        (SEV_FAIL_SIZEMISMATCH, _) => Ok(AcceptGpaStatus::Retry),
852        _ => Err(AcceptGpaError::MemorySecurityViolation {
853            error_code,
854            carry_flag,
855            page_number,
856            large_page,
857            validate,
858        }),
859    }
860}
861
862/// Accepts or unaccepts a specific gpa range. On SNP systems, this corresponds to issuing a
863/// pvalidate over the GPA range with the desired value of the validate bit.
864pub fn set_page_acceptance(
865    local_map: &mut LocalMap<'_>,
866    range: MemoryRange,
867    validate: bool,
868) -> Result<(), AcceptGpaError> {
869    let pages_per_large_page = x86defs::X64_LARGE_PAGE_SIZE / X64_PAGE_SIZE;
870    let mut page_count = range.page_count_4k();
871    let mut page_base = range.start_4k_gpn();
872
873    while page_count != 0 {
874        // Attempt to validate a large page.
875        // Even when pvalidating a large page, the processor only does a 1 byte read. As a result
876        // mapping a single page is sufficient.
877        let mapping = local_map.map_pages(
878            MemoryRange::from_4k_gpn_range(page_base..page_base + 1),
879            true,
880        );
881        if page_base.is_multiple_of(pages_per_large_page) && page_count >= pages_per_large_page {
882            let res = pvalidate(page_base, mapping.data.as_ptr() as u64, true, validate)?;
883            match res {
884                AcceptGpaStatus::Success => {
885                    page_count -= pages_per_large_page;
886                    page_base += pages_per_large_page;
887                    continue;
888                }
889                AcceptGpaStatus::Retry => (),
890            }
891        }
892
893        // Attempt to validate a regular sized page.
894        let res = pvalidate(page_base, mapping.data.as_ptr() as u64, false, validate)?;
895        match res {
896            AcceptGpaStatus::Success => {
897                page_count -= 1;
898                page_base += 1;
899            }
900            AcceptGpaStatus::Retry => {
901                // Cannot retry on a regular sized page.
902                return Err(AcceptGpaError::Unknown);
903            }
904        }
905    }
906
907    Ok(())
908}
909
910/// GHCB based io port access.
911#[cfg(feature = "cvm_boot_log")]
912pub struct SnpIoAccess;
913
914#[cfg(feature = "cvm_boot_log")]
915impl minimal_rt::arch::IoAccess for SnpIoAccess {
916    unsafe fn inb(&self, port: u16) -> u8 {
917        // Best effort
918        Ghcb::read_io_port(port, IoAccessSize::Byte).unwrap_or(!0) as u8
919    }
920
921    unsafe fn outb(&self, port: u16, data: u8) {
922        // Best effort
923        let _ = Ghcb::write_io_port(port, IoAccessSize::Byte, data as u32);
924    }
925}