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