Skip to main content

hcl/ioctl/
cca.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Backing for CCA partitions.
5
6use std::os::fd::AsRawFd;
7
8use super::Hcl;
9use super::HclVp;
10use super::MshvVtl;
11use super::NoRunner;
12use super::ProcessorRunner;
13use crate::GuestVtl;
14use crate::ioctl::Error;
15use crate::ioctl::GetRegError;
16use crate::ioctl::HvError;
17use crate::ioctl::SetRegError;
18use crate::ioctl::ioctls::hcl_realm_config;
19use crate::ioctl::ioctls::hcl_rsi_ipa_state_read;
20use crate::ioctl::ioctls::hcl_rsi_set_mem_perm;
21use crate::ioctl::ioctls::hcl_rsi_sysreg_read;
22use crate::ioctl::ioctls::hcl_rsi_sysreg_write;
23use aarch64defs::SystemReg;
24use aarch64defs::rsi::RSI_PLANE_ENTER_FLAGS_TRAP_SIMD;
25use aarch64defs::rsi::RSI_PLANE_GIC_NUM_LRS;
26use aarch64defs::rsi::RSI_PLANE_NR_GPRS;
27use aarch64defs::rsi::cca_rsi_plane_entry;
28use aarch64defs::rsi::cca_rsi_plane_exit;
29use aarch64defs::rsi::cca_rsi_plane_run;
30use hvdef::HV_PAGE_SIZE;
31use hvdef::HvArm64RegisterName;
32use hvdef::HvRegisterName;
33use hvdef::HvRegisterValue;
34use memory_range::MemoryRange;
35use sidecar_client::SidecarVp;
36use user_driver::memory::MemoryBlock;
37
38#[derive(Debug, Error)]
39#[expect(missing_docs)]
40pub enum GetIpaStateError {
41    #[error("RSI IPA state read ioctl failed")]
42    Ioctl(#[source] nix::Error),
43}
44
45/// CCA: Structure mirroring the data returned by RMM in the RSI_REALM_CONFIG call.
46#[repr(C, align(0x1000))]
47#[derive(Clone, Copy, Default)]
48#[expect(missing_docs)]
49pub struct mshv_realm_config {
50    pub ipa_width: u64,
51    pub algorithm: u64,
52    pub num_aux_planes: u64,
53    pub gicv3_vtr: u64,
54}
55
56/// CCA: Structure mirroring the data taken by RMM in the RSI_PLANE_SYSREG_WRITE.
57/// `vtl` is converted into plane number in kernel driver.
58#[repr(C)]
59#[derive(Clone, Copy, Default)]
60#[expect(missing_docs)]
61pub struct mshv_rsi_sysreg_rw {
62    pub vtl: u8,
63    pub _pad: [u8; 7],
64    pub sysreg: u64,
65    pub value: u64,
66}
67
68/// CCA: Structure mirroring the data taken by RMM in the RSI_SET_MEM_PERM.
69/// NOTE: we hand over the plane number here, we should probably stay consistent with
70///       `sysreg_write`.
71#[repr(C)]
72#[derive(Clone, Copy, Default)]
73#[expect(missing_docs)]
74pub struct mshv_rsi_set_mem_perm {
75    pub plane: u8,
76    pub _pad: [u8; 7],
77    pub base_addr: u64,
78    pub top_addr: u64,
79}
80
81/// CCA: Structure used by the hcl_rsi_ipa_state_read ioctl.
82/// Caller sets fipa to the faulting IPA. On return the state
83/// contains the corresponding RIPAS state.
84#[repr(C)]
85#[derive(Clone, Copy, Default)]
86pub struct mshv_rsi_get_ipa_state {
87    /// Faulting ipa to have its state queried
88    pub fipa: u64,
89    /// RIPAS state returned for fipa
90    pub state: u64,
91}
92
93/// SystemReg is following encoding used by MSR/MRS which is different with
94/// the encoding RSI is using. The latter doesn't left shift the register
95/// number.
96const fn encode_rsi_sysreg(sysreg: SystemReg) -> u64 {
97    ((sysreg.0.op0() as u64) << 14)
98        | ((sysreg.0.op1() as u64) << 11)
99        | ((sysreg.0.crn() as u64) << 7)
100        | ((sysreg.0.crm() as u64) << 3)
101        | (sysreg.0.op2() as u64)
102}
103
104/// Runner backing for CCA partitions.
105pub struct Cca {
106    plane_run: MemoryBlock,
107}
108
109impl Cca {
110    /// Create new CCA runner backing.
111    pub fn new(plane_run: &MemoryBlock) -> Self {
112        assert_eq!(plane_run.offset_in_page(), 0);
113        assert!(plane_run.len() >= size_of::<cca_rsi_plane_run>());
114
115        Self {
116            plane_run: plane_run.clone(),
117        }
118    }
119
120    fn plane_run_ref(&self) -> &cca_rsi_plane_run {
121        // SAFETY: the DMA allocation remains mapped for the lifetime of the backing
122        // and is page-aligned, so it can be viewed as a `cca_rsi_plane_run`. Also,
123        // 'new' validates that the allocation size is >= sizeof cca_rsi_plane_run.
124        unsafe { &*self.plane_run.base().cast::<cca_rsi_plane_run>() }
125    }
126
127    fn plane_run_mut(&mut self) -> &mut cca_rsi_plane_run {
128        // SAFETY: the DMA allocation remains mapped for the lifetime of the backing
129        // and `&mut self` guarantees exclusive access to the mapped page contents.
130        unsafe { &mut *self.plane_run.base().cast_mut().cast::<cca_rsi_plane_run>() }
131    }
132
133    fn plane_run_phys(&self) -> u64 {
134        self.plane_run.pfns()[0] * HV_PAGE_SIZE
135    }
136}
137
138impl ProcessorRunner<'_, Cca> {
139    /// Returns a reference to the current VTL's CPU context.
140    pub fn cpu_context(&self) -> &u64 {
141        // SAFETY: the cpu context will not be concurrently accessed by the
142        // hypervisor while this VP is in VTL2.
143        unsafe { &*(&raw mut (*self.run.get()).context).cast() }
144    }
145
146    /// Returns a mutable reference to the current VTL's CPU context.
147    pub fn cpu_context_mut(&mut self) -> &mut u64 {
148        // SAFETY: the cpu context will not be concurrently accessed by the
149        // hypervisor while this VP is in VTL2.
150        unsafe { &mut *(&raw mut (*self.run.get()).context).cast() }
151    }
152
153    /// Returns a mutable reference to the current VTL's CCA RSI plane run structure.
154    pub fn cca_rsi_plane_run_mut(&mut self) -> &mut cca_rsi_plane_run {
155        self.state.plane_run_mut()
156    }
157
158    /// Returns a mutable reference to the current VTL's plane entry structure.
159    pub fn cca_rsi_plane_entry(&mut self) -> &mut cca_rsi_plane_entry {
160        &mut self.state.plane_run_mut().entry
161    }
162
163    /// Returns a mutable reference to the current VTL's plane exit structure.
164    pub fn cca_rsi_plane_exit(&self) -> &cca_rsi_plane_exit {
165        &self.state.plane_run_ref().exit
166    }
167
168    /// Set the value of the plane entry flags.
169    pub fn cca_set_entry_flags(&mut self, value: u64) {
170        self.cca_rsi_plane_entry().flags = value;
171    }
172
173    /// Set the value of the plane entry PC.
174    pub fn cca_set_entry_pc(&mut self, value: u64) {
175        self.cca_rsi_plane_entry().pc = value;
176    }
177
178    /// Set the value of the plane entry GPRs.
179    pub fn cca_set_entry_gprs(&mut self, values: [u64; RSI_PLANE_NR_GPRS]) {
180        self.cca_rsi_plane_entry().gprs = values;
181    }
182
183    /// Set the value of the plane entry gicv3_hcr register.
184    pub fn cca_set_entry_gicv3_hcr(&mut self, value: u64) {
185        self.cca_rsi_plane_entry().gicv3_hcr = value;
186    }
187
188    /// Set the value of the plane entry GIC v3 LRs.
189    pub fn cca_set_entry_gicv3_lrs(&mut self, values: [u64; RSI_PLANE_GIC_NUM_LRS]) {
190        self.cca_rsi_plane_entry().gicv3_lrs = values;
191    }
192
193    /// Set the value of a single plane entry GPR.
194    fn cca_set_entry_gpr(&mut self, register: usize, value: u64) {
195        assert!(register < RSI_PLANE_NR_GPRS);
196        self.cca_rsi_plane_entry().gprs[register] = value;
197    }
198
199    /// Get the value of a single plane entry GPR.
200    fn cca_get_entry_gpr(&self, register: usize) -> u64 {
201        assert!(register < RSI_PLANE_NR_GPRS);
202        self.cca_rsi_plane_exit().gprs[register]
203    }
204
205    /// Flush the given value for a system register to the RMM.
206    pub fn cca_sysreg_write(
207        &mut self,
208        vtl: GuestVtl,
209        name: SystemReg,
210        value: u64,
211    ) -> Result<(), SetRegError> {
212        self.hcl
213            .rsi_sysreg_write(vtl, encode_rsi_sysreg(name), value)
214    }
215
216    /// Read the value of a system register from the RMM.
217    pub fn cca_sysreg_read(
218        &mut self,
219        vtl: GuestVtl,
220        name: SystemReg,
221        value: &mut u64,
222    ) -> Result<(), GetRegError> {
223        self.hcl
224            .rsi_sysreg_read(vtl, encode_rsi_sysreg(name), value)
225    }
226
227    /// Get the ipa ripas state from the RMM
228    pub fn cca_ipa_state_read(&self, fipa: u64) -> Result<Option<u64>, GetIpaStateError> {
229        self.hcl.rsi_get_ipa_state(fipa)
230    }
231
232    /// Update the address of the `plane_run` structure in `mshv_vtl_run.context`.
233    pub fn cca_set_plane_enter(&mut self) {
234        // SAFETY: the run page is exclusively accessed through `&mut self` while
235        // this VP is in VTL2, and the CCA runner uses `context` as a u64
236        // physical address slot for the plane run page.
237        let plane_run: &mut u64 = unsafe { &mut *(&raw mut (*self.run.get()).context).cast() };
238        *plane_run = self.state.plane_run_phys();
239    }
240
241    /// Set flag to enable trapping of SIMD operations in the lower VTL.
242    pub fn cca_plane_trap_simd(&mut self) {
243        let plane_run: &mut cca_rsi_plane_run = self.state.plane_run_mut();
244        plane_run.entry.flags |= RSI_PLANE_ENTER_FLAGS_TRAP_SIMD;
245    }
246
247    /// Unset flag that enables trapping of SIMD operations in lower VTL
248    /// (i.e., SIMD operations are not trapped).
249    pub fn cca_plane_no_trap_simd(&mut self) {
250        let plane_run: &mut cca_rsi_plane_run = self.state.plane_run_mut();
251        plane_run.entry.flags &= !RSI_PLANE_ENTER_FLAGS_TRAP_SIMD;
252    }
253
254    /// Set the default value for PSTATE for the lower VTL.
255    pub fn cca_set_default_pstate(&mut self) {
256        // SPSR_EL2_MODE_EL1h | SPSR_EL2_nRW_AARCH64 | SPSR_EL2_F_BIT | SPSR_EL2_I_BIT | SPSR_EL2_A_BIT | SPSR_EL2_D_BIT
257        self.cca_rsi_plane_entry().pstate = 0x3c5;
258    }
259}
260
261// TODO CCA: this implementation is lifted from the aarch64 VBS implementation
262// and might need more work to make it CCA-aligned.
263impl<'a> super::BackingPrivate<'a> for Cca {
264    fn new(vp: &HclVp, sidecar: Option<&SidecarVp<'_>>, _hcl: &Hcl) -> Result<Self, NoRunner> {
265        assert!(sidecar.is_none());
266        let super::BackingState::Cca { plane_run } = &vp.backing else {
267            unreachable!()
268        };
269        let cca = Cca::new(plane_run);
270
271        Ok(cca)
272    }
273
274    fn try_set_reg(
275        runner: &mut ProcessorRunner<'a, Self>,
276        _vtl: GuestVtl,
277        name: HvRegisterName,
278        value: HvRegisterValue,
279    ) -> bool {
280        // Try to set the register in the CPU context, the fastest path. Only
281        // VTL-shared registers can be set this way: the CPU context only
282        // exposes the last VTL, and if we entered VTL2 on an interrupt,
283        // OpenHCL doesn't know what the last VTL is.
284        match name.into() {
285            HvArm64RegisterName::X0
286            | HvArm64RegisterName::X1
287            | HvArm64RegisterName::X2
288            | HvArm64RegisterName::X3
289            | HvArm64RegisterName::X4
290            | HvArm64RegisterName::X5
291            | HvArm64RegisterName::X6
292            | HvArm64RegisterName::X7
293            | HvArm64RegisterName::X8
294            | HvArm64RegisterName::X9
295            | HvArm64RegisterName::X10
296            | HvArm64RegisterName::X11
297            | HvArm64RegisterName::X12
298            | HvArm64RegisterName::X13
299            | HvArm64RegisterName::X14
300            | HvArm64RegisterName::X15
301            | HvArm64RegisterName::X16
302            | HvArm64RegisterName::X17
303            | HvArm64RegisterName::X18
304            | HvArm64RegisterName::X19
305            | HvArm64RegisterName::X20
306            | HvArm64RegisterName::X21
307            | HvArm64RegisterName::X22
308            | HvArm64RegisterName::X23
309            | HvArm64RegisterName::X24
310            | HvArm64RegisterName::X25
311            | HvArm64RegisterName::X26
312            | HvArm64RegisterName::X27
313            | HvArm64RegisterName::X28
314            | HvArm64RegisterName::XFp
315            | HvArm64RegisterName::XLr => {
316                runner.cca_set_entry_gpr(
317                    (name.0 - HvArm64RegisterName::X0.0) as usize,
318                    value.as_u64(),
319                );
320                true
321            }
322            _ => false,
323        }
324    }
325
326    fn must_flush_regs_on(_runner: &ProcessorRunner<'a, Self>, _name: HvRegisterName) -> bool {
327        false
328    }
329
330    fn try_get_reg(
331        runner: &ProcessorRunner<'a, Self>,
332        _vtl: GuestVtl,
333        name: HvRegisterName,
334    ) -> Option<HvRegisterValue> {
335        // Try to get the register from the CPU context, the fastest path.
336        // NOTE: for VBS x18 is omitted here as it is managed by the hypervisor,
337        //       do we need to do the same here?
338        match name.into() {
339            HvArm64RegisterName::X0
340            | HvArm64RegisterName::X1
341            | HvArm64RegisterName::X2
342            | HvArm64RegisterName::X3
343            | HvArm64RegisterName::X4
344            | HvArm64RegisterName::X5
345            | HvArm64RegisterName::X6
346            | HvArm64RegisterName::X7
347            | HvArm64RegisterName::X8
348            | HvArm64RegisterName::X9
349            | HvArm64RegisterName::X10
350            | HvArm64RegisterName::X11
351            | HvArm64RegisterName::X12
352            | HvArm64RegisterName::X13
353            | HvArm64RegisterName::X14
354            | HvArm64RegisterName::X15
355            | HvArm64RegisterName::X16
356            | HvArm64RegisterName::X17
357            | HvArm64RegisterName::X18
358            | HvArm64RegisterName::X19
359            | HvArm64RegisterName::X20
360            | HvArm64RegisterName::X21
361            | HvArm64RegisterName::X22
362            | HvArm64RegisterName::X23
363            | HvArm64RegisterName::X24
364            | HvArm64RegisterName::X25
365            | HvArm64RegisterName::X26
366            | HvArm64RegisterName::X27
367            | HvArm64RegisterName::X28
368            | HvArm64RegisterName::XFp
369            | HvArm64RegisterName::XLr => Some(
370                runner
371                    .cca_get_entry_gpr((name.0 - HvArm64RegisterName::X0.0) as usize)
372                    .into(),
373            ),
374            _ => None,
375        }
376    }
377
378    fn flush_register_page(_runner: &mut ProcessorRunner<'a, Self>) {}
379}
380
381/// Representation of the Realm config data available to Plane 0.
382///
383/// * ipa_width is the size of the realm protected memory space
384/// * hash_algo is the hash alg used for measurements
385/// * num_aux_planes indicates how many low-privilege planes exist
386/// * gicv3_vtr shows part of the GICv3 configuration for the realm,
387///   needed for GIC virtualisation
388#[derive(Debug, Clone, Copy)]
389pub struct RsiRealmConfig {
390    ipa_width: u64,
391    #[expect(unused)]
392    hash_algo: u64,
393    #[expect(unused)]
394    num_aux_planes: u64,
395    #[expect(unused)]
396    gicv3_vtr: u64,
397}
398
399impl RsiRealmConfig {
400    /// Get the IPA width of the realm
401    pub fn ipa_width(&self) -> u64 {
402        self.ipa_width
403    }
404}
405
406impl From<mshv_realm_config> for RsiRealmConfig {
407    fn from(value: mshv_realm_config) -> Self {
408        RsiRealmConfig {
409            ipa_width: value.ipa_width,
410            hash_algo: value.algorithm,
411            num_aux_planes: value.num_aux_planes,
412            gicv3_vtr: value.gicv3_vtr,
413        }
414    }
415}
416
417impl MshvVtl {
418    /// Get the realm-specific parameters from the RMM
419    pub fn get_realm_config(&self) -> Result<RsiRealmConfig, Error> {
420        let mut config = mshv_realm_config::default();
421
422        // SAFETY: Calling hcl_realm_config ioctl with the correct arguments.
423        unsafe {
424            hcl_realm_config(self.file.as_raw_fd(), &mut config)
425                .map_err(|_| Error::InvalidRegisterValue)?;
426        }
427
428        Ok(config.into())
429    }
430
431    /// Write the value of a system register for the given VTL
432    pub fn rsi_sysreg_write(
433        &self,
434        vtl: GuestVtl,
435        sysreg: u64,
436        value: u64,
437    ) -> Result<(), SetRegError> {
438        let sysreg_write = mshv_rsi_sysreg_rw {
439            vtl: vtl.into(),
440            sysreg,
441            value,
442            ..Default::default()
443        };
444
445        // SAFETY: Calling hcl_rsi_sysreg_write ioctl with the correct arguments.
446        unsafe {
447            hcl_rsi_sysreg_write(self.file.as_raw_fd(), &sysreg_write)
448                .map_err(SetRegError::Ioctl)?;
449        }
450        Ok(())
451    }
452
453    /// Read the value of a system register for the given VTL.
454    pub fn rsi_sysreg_read(
455        &self,
456        vtl: GuestVtl,
457        sysreg: u64,
458        value: &mut u64,
459    ) -> Result<(), GetRegError> {
460        let mut sysreg_read = mshv_rsi_sysreg_rw {
461            vtl: vtl.into(),
462            sysreg,
463            ..Default::default()
464        };
465
466        // SAFETY: Calling hcl_rsi_sysreg_read ioctl with the correct arguments.
467        unsafe {
468            hcl_rsi_sysreg_read(self.file.as_raw_fd(), &mut sysreg_read)
469                .map_err(GetRegError::Ioctl)?;
470        }
471
472        *value = sysreg_read.value;
473        Ok(())
474    }
475
476    /// Assign given memory range to the VTL.
477    pub fn rsi_set_mem_perm(&self, vtl: GuestVtl, range: &MemoryRange) -> Result<(), HvError> {
478        let plane = match vtl {
479            GuestVtl::Vtl0 => 1,
480            _ => return Err(HvError::InvalidRegisterValue),
481        };
482
483        let set_mem_perm = mshv_rsi_set_mem_perm {
484            plane,
485            _pad: [0; 7],
486            base_addr: range.start(),
487            top_addr: range.end(),
488        };
489
490        // SAFETY: Calling hcl_rsi_set_mem_perm ioctl with the correct arguments.
491        unsafe {
492            hcl_rsi_set_mem_perm(self.file.as_raw_fd(), &set_mem_perm)
493                .map_err(|_| HvError::InvalidRegisterValue)?;
494        }
495        Ok(())
496    }
497
498    /// Get the ipa RIPAS state
499    pub fn rsi_get_ipa_state(&self, fipa: u64) -> Result<Option<u64>, GetIpaStateError> {
500        let mut plane_state = mshv_rsi_get_ipa_state {
501            fipa,
502            state: u64::MAX,
503        };
504
505        // SAFETY: Calling hcl_rsi_ipa_state_read ioctl with the correct arguments.
506        unsafe {
507            hcl_rsi_ipa_state_read(self.file.as_raw_fd(), &mut plane_state)
508                .map_err(GetIpaStateError::Ioctl)?;
509        }
510
511        if plane_state.state >= u8::MAX as u64 {
512            return Ok(None);
513        }
514
515        Ok(Some(plane_state.state))
516    }
517}
518
519impl Hcl {
520    /// Gets Realm config
521    pub fn get_realm_config(&self) -> Result<RsiRealmConfig, Error> {
522        self.mshv_vtl.get_realm_config()
523    }
524
525    /// sets system registers through rsi calls
526    pub fn rsi_sysreg_write(
527        &self,
528        vtl: GuestVtl,
529        sysreg: u64,
530        value: u64,
531    ) -> Result<(), SetRegError> {
532        self.mshv_vtl.rsi_sysreg_write(vtl, sysreg, value)
533    }
534
535    /// Read a system register through RSI.
536    pub fn rsi_sysreg_read(
537        &self,
538        vtl: GuestVtl,
539        sysreg: u64,
540        value: &mut u64,
541    ) -> Result<(), GetRegError> {
542        self.mshv_vtl.rsi_sysreg_read(vtl, sysreg, value)
543    }
544
545    /// setting memory permissions
546    pub fn rsi_set_mem_perm(&self, vtl: GuestVtl, range: MemoryRange) -> Result<(), HvError> {
547        self.mshv_vtl.rsi_set_mem_perm(vtl, &range)
548    }
549
550    /// getting ipa RIPAS state
551    pub fn rsi_get_ipa_state(&self, fipa: u64) -> Result<Option<u64>, GetIpaStateError> {
552        self.mshv_vtl.rsi_get_ipa_state(fipa)
553    }
554}