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::X86X_AMD_MSR_GHCB;
12use x86defs::snp::GhcbInfo;
13
14pub struct Ghcb;
15
16#[derive(Debug)]
17pub enum AcceptGpaStatus {
18    Success,
19    Retry,
20}
21
22#[expect(dead_code)] // Printed via Debug in the error case.
23#[derive(Debug)]
24pub enum AcceptGpaError {
25    MemorySecurityViolation {
26        error_code: u32,
27        carry_flag: u32,
28        page_number: u64,
29        large_page: bool,
30        validate: bool,
31    },
32    Unknown,
33}
34
35impl Ghcb {
36    /// # Safety
37    ///
38    /// Regardless of the content of the GHCB page or MSR, this instruction should not be able
39    /// to cause memory safety issues.
40    fn sev_vmgexit() {
41        // SAFETY: Using the `vmgexit` instruction forces an exit to the hypervisor but doesn't
42        // directly change program state.
43        unsafe {
44            asm! {r#"
45            rep vmmcall
46            "#
47            }
48        }
49    }
50
51    pub fn change_page_visibility(range: MemoryRange, host_visible: bool) {
52        // SAFETY: Always safe to read the GHCB MSR.
53        let previous_value = unsafe { read_msr(X86X_AMD_MSR_GHCB) };
54        for page_number in range.start_4k_gpn()..range.end_4k_gpn() {
55            let extra_data = if host_visible {
56                x86defs::snp::GHCB_DATA_PAGE_STATE_SHARED
57            } else {
58                x86defs::snp::GHCB_DATA_PAGE_STATE_PRIVATE
59            };
60
61            let val = (extra_data << 52) | (page_number << 12) | GhcbInfo::PAGE_STATE_CHANGE.0;
62
63            // SAFETY: Writing known good value to the GHCB MSR.
64            let val = unsafe {
65                write_msr(X86X_AMD_MSR_GHCB, val);
66                Self::sev_vmgexit();
67                read_msr(X86X_AMD_MSR_GHCB)
68            };
69
70            // High 32 bits are status and should be 0 (HV_STATUS_SUCCESS), Low 32 bits should be
71            // GHCB_INFO_PAGE_STATE_UPDATED. Assert if otherwise.
72
73            assert!(
74                val == GhcbInfo::PAGE_STATE_UPDATED.0,
75                "GhcbInfo::PAGE_STATE_UPDATED returned msr value {val}"
76            );
77        }
78
79        // SAFETY: Restoring previous GHCB value is safe.
80        unsafe { write_msr(X86X_AMD_MSR_GHCB, previous_value) };
81    }
82}
83
84/// Wrapper around the pvalidate assembly instruction.
85fn pvalidate(
86    page_number: u64,
87    va: u64,
88    large_page: bool,
89    validate: bool,
90) -> Result<AcceptGpaStatus, AcceptGpaError> {
91    if large_page {
92        assert!(va % x86defs::X64_LARGE_PAGE_SIZE == 0);
93    } else {
94        assert!(va % hvdef::HV_PAGE_SIZE == 0)
95    }
96
97    let validate_page = validate as u32;
98    let page_size = large_page as u32;
99    let mut error_code: u32;
100    let mut carry_flag: u32 = 0;
101
102    // SAFETY: Issuing pvalidate according to specification.
103    unsafe {
104        asm!(r#"
105        pvalidate
106        jnc 2f
107        inc {carry_flag:e}
108        2:
109        "#,
110        in("rax") va,
111        in("ecx") page_size,
112        in("edx") validate_page,
113        lateout("eax") error_code,
114        carry_flag = inout(reg) carry_flag);
115    }
116
117    const SEV_SUCCESS: u32 = 0;
118    const SEV_FAIL_SIZEMISMATCH: u32 = 6;
119
120    match (error_code, carry_flag) {
121        (SEV_SUCCESS, 0) => Ok(AcceptGpaStatus::Success),
122        (SEV_FAIL_SIZEMISMATCH, _) => Ok(AcceptGpaStatus::Retry),
123        _ => Err(AcceptGpaError::MemorySecurityViolation {
124            error_code,
125            carry_flag,
126            page_number,
127            large_page,
128            validate,
129        }),
130    }
131}
132
133/// Accepts or unaccepts a specific gpa range. On SNP systems, this corresponds to issuing a
134/// pvalidate over the GPA range with the desired value of the validate bit.
135pub fn set_page_acceptance(
136    local_map: &mut LocalMap<'_>,
137    range: MemoryRange,
138    validate: bool,
139) -> Result<(), AcceptGpaError> {
140    let pages_per_large_page = x86defs::X64_LARGE_PAGE_SIZE / hvdef::HV_PAGE_SIZE;
141    let mut page_count = range.page_count_4k();
142    let mut page_base = range.start_4k_gpn();
143
144    while page_count != 0 {
145        // Attempt to validate a large page.
146        // Even when pvalidating a large page, the processor only does a 1 byte read. As a result
147        // mapping a single page is sufficient.
148        let mapping = local_map.map_pages(
149            MemoryRange::from_4k_gpn_range(page_base..page_base + 1),
150            true,
151        );
152        if page_base % pages_per_large_page == 0 && page_count >= pages_per_large_page {
153            let res = pvalidate(page_base, mapping.data.as_ptr() as u64, true, validate)?;
154            match res {
155                AcceptGpaStatus::Success => {
156                    page_count -= pages_per_large_page;
157                    page_base += pages_per_large_page;
158                    continue;
159                }
160                AcceptGpaStatus::Retry => (),
161            }
162        }
163
164        // Attempt to validate a regular sized page.
165        let res = pvalidate(page_base, mapping.data.as_ptr() as u64, false, validate)?;
166        match res {
167            AcceptGpaStatus::Success => {
168                page_count -= 1;
169                page_base += 1;
170            }
171            AcceptGpaStatus::Retry => {
172                // Cannot retry on a regular sized page.
173                return Err(AcceptGpaError::Unknown);
174            }
175        }
176    }
177
178    Ok(())
179}