hcl/ioctl/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Backing for SNP partitions.
5
6use super::Hcl;
7use super::HclVp;
8use super::MshvVtl;
9use super::NoRunner;
10use super::ProcessorRunner;
11use super::hcl_pvalidate_pages;
12use super::hcl_rmpadjust_pages;
13use super::hcl_rmpquery_pages;
14use super::mshv_pvalidate;
15use super::mshv_rmpadjust;
16use super::mshv_rmpquery;
17use crate::GuestVtl;
18use crate::vmsa::VmsaWrapper;
19use hv1_structs::VtlArray;
20use hvdef::HV_PAGE_SIZE;
21use hvdef::HvRegisterName;
22use hvdef::HvRegisterValue;
23use memory_range::MemoryRange;
24use sidecar_client::SidecarVp;
25use std::cell::UnsafeCell;
26use std::os::fd::AsRawFd;
27use thiserror::Error;
28use x86defs::snp::SevRmpAdjust;
29use x86defs::snp::SevVmsa;
30
31/// Runner backing for SNP partitions.
32pub struct Snp<'a> {
33    vmsa: VtlArray<&'a UnsafeCell<SevVmsa>, 2>,
34}
35
36/// Error returned by failing SNP operations.
37#[derive(Debug, Error)]
38#[expect(missing_docs)]
39pub enum SnpError {
40    #[error("operating system error")]
41    Os(#[source] nix::Error),
42    #[error("isa error {0:?}")]
43    Isa(u32),
44}
45
46/// Error returned by failing SNP page operations.
47#[derive(Debug, Error)]
48#[expect(missing_docs)]
49pub enum SnpPageError {
50    #[error("pvalidate failed")]
51    Pvalidate(#[source] SnpError),
52    #[error("rmpadjust failed")]
53    Rmpadjust(#[source] SnpError),
54    #[error("rmpquery failed")]
55    Rmpquery(#[source] SnpError),
56}
57
58impl MshvVtl {
59    /// Execute the pvalidate instruction on the specified memory range.
60    ///
61    /// The range must not be mapped in the kernel as RAM.
62    //
63    // TODO SNP: figure out a safer model for this here and in the kernel.
64    pub fn pvalidate_pages(
65        &self,
66        range: MemoryRange,
67        validate: bool,
68        terminate_on_failure: bool,
69    ) -> Result<(), SnpPageError> {
70        tracing::debug!(%range, validate, terminate_on_failure, "pvalidate");
71        // SAFETY: TODO SNP: we are passing parameters as the kernel requires.
72        // But this isn't really safe because it could be used to unaccept a
73        // VTL2 kernel page. Kernel changes are needed to make this safe.
74        let ret = unsafe {
75            hcl_pvalidate_pages(
76                self.file.as_raw_fd(),
77                &mshv_pvalidate {
78                    start_pfn: range.start() / HV_PAGE_SIZE,
79                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
80                    validate: validate as u8,
81                    terminate_on_failure: terminate_on_failure as u8,
82                    ram: 0,
83                    padding: [0; 1],
84                },
85            )
86            .map_err(SnpError::Os)
87            .map_err(SnpPageError::Pvalidate)?
88        };
89
90        if ret != 0 {
91            return Err(SnpPageError::Pvalidate(SnpError::Isa(ret as u32)));
92        }
93
94        Ok(())
95    }
96
97    /// Execute the rmpadjust instruction on the specified memory range.
98    ///
99    /// The range must not be mapped in the kernel as RAM.
100    //
101    // TODO SNP: figure out a safer model for this here and in the kernel.
102    pub fn rmpadjust_pages(
103        &self,
104        range: MemoryRange,
105        value: SevRmpAdjust,
106        terminate_on_failure: bool,
107    ) -> Result<(), SnpPageError> {
108        if value.vmsa() {
109            // TODO SNP: VMSA conversion does not work.
110            return Ok(());
111        }
112
113        #[expect(clippy::undocumented_unsafe_blocks)] // TODO SNP
114        let ret = unsafe {
115            hcl_rmpadjust_pages(
116                self.file.as_raw_fd(),
117                &mshv_rmpadjust {
118                    start_pfn: range.start() / HV_PAGE_SIZE,
119                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
120                    value: value.into(),
121                    terminate_on_failure: terminate_on_failure as u8,
122                    ram: 0,
123                    padding: Default::default(),
124                },
125            )
126            .map_err(SnpError::Os)
127            .map_err(SnpPageError::Rmpadjust)?
128        };
129
130        if ret != 0 {
131            return Err(SnpPageError::Rmpadjust(SnpError::Isa(ret as u32)));
132        }
133
134        Ok(())
135    }
136
137    /// Gets the current vtl permissions for a page.
138    /// Note: only supported on Genoa+
139    pub fn rmpquery_page(&self, gpa: u64, vtl: GuestVtl) -> Result<SevRmpAdjust, SnpPageError> {
140        let page_count = 1u64;
141        let mut flags = [u64::from(SevRmpAdjust::new().with_target_vmpl(match vtl {
142            GuestVtl::Vtl0 => 2,
143            GuestVtl::Vtl1 => 1,
144        })); 1];
145
146        let mut page_size = [0; 1];
147        let mut pages_processed = 0u64;
148
149        debug_assert!(flags.len() == page_count as usize);
150        debug_assert!(page_size.len() == page_count as usize);
151
152        let query = mshv_rmpquery {
153            start_pfn: gpa / HV_PAGE_SIZE,
154            page_count,
155            terminate_on_failure: 0,
156            ram: 0,
157            padding: Default::default(),
158            flags: flags.as_mut_ptr(),
159            page_size: page_size.as_mut_ptr(),
160            pages_processed: &mut pages_processed,
161        };
162
163        // SAFETY: the input query is the correct type for this ioctl
164        unsafe {
165            hcl_rmpquery_pages(self.file.as_raw_fd(), &query)
166                .map_err(SnpError::Os)
167                .map_err(SnpPageError::Rmpquery)?;
168        }
169
170        assert!(pages_processed <= page_count);
171
172        Ok(SevRmpAdjust::from(flags[0]))
173    }
174}
175
176impl<'a> super::private::BackingPrivate<'a> for Snp<'a> {
177    fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'_>>, _hcl: &Hcl) -> Result<Self, NoRunner> {
178        assert!(sidecar.is_none());
179        let super::BackingState::Snp { vmsa } = &vp.backing else {
180            return Err(NoRunner::MismatchedIsolation);
181        };
182
183        Ok(Self {
184            vmsa: vmsa.each_ref().map(|mp| mp.as_ref()),
185        })
186    }
187
188    fn try_set_reg(
189        _runner: &mut ProcessorRunner<'a, Self>,
190        _vtl: GuestVtl,
191        _name: HvRegisterName,
192        _value: HvRegisterValue,
193    ) -> Result<bool, super::Error> {
194        Ok(false)
195    }
196
197    fn must_flush_regs_on(_runner: &ProcessorRunner<'a, Self>, _name: HvRegisterName) -> bool {
198        false
199    }
200
201    fn try_get_reg(
202        _runner: &ProcessorRunner<'a, Self>,
203        _vtl: GuestVtl,
204        _name: HvRegisterName,
205    ) -> Result<Option<HvRegisterValue>, super::Error> {
206        Ok(None)
207    }
208
209    fn flush_register_page(_runner: &mut ProcessorRunner<'a, Self>) {}
210}
211
212impl<'a> ProcessorRunner<'a, Snp<'a>> {
213    /// Gets a reference to the VMSA and backing state of a VTL
214    pub fn vmsa(&self, vtl: GuestVtl) -> VmsaWrapper<'_, &SevVmsa> {
215        // SAFETY: the VMSA will not be concurrently accessed by the processor
216        // while this VP is in VTL2.
217        let vmsa = unsafe { &*self.state.vmsa[vtl].get() };
218
219        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
220    }
221
222    /// Gets a mutable reference to the VMSA and backing state of a VTL.
223    pub fn vmsa_mut(&mut self, vtl: GuestVtl) -> VmsaWrapper<'_, &mut SevVmsa> {
224        // SAFETY: the VMSA will not be concurrently accessed by the processor
225        // while this VP is in VTL2.
226        let vmsa = unsafe { &mut *self.state.vmsa[vtl].get() };
227
228        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
229    }
230
231    /// Returns the VMSAs for [VTL0, VTL1].
232    pub fn vmsas_mut(&mut self) -> [VmsaWrapper<'_, &mut SevVmsa>; 2] {
233        self.state
234            .vmsa
235            .each_mut()
236            .map(|vmsa| {
237                // SAFETY: the VMSA will not be concurrently accessed by the processor
238                // while this VP is in VTL2.
239                let vmsa = unsafe { &mut *vmsa.get() };
240
241                VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
242            })
243            .into_inner()
244    }
245}