Skip to main content

islet_rmm/rmi/rec/
handlers.rs

1use super::mpidr::MPIDR;
2use super::params::Params;
3use super::run::{EntryFlag, Run};
4use super::vtcr::{activate_stage2_mmu, prepare_vtcr};
5use crate::event::RmiHandle;
6#[cfg(feature = "gst_page_table")]
7use crate::granule::{set_granule, set_granule_with_parent, GranuleState};
8#[cfg(not(feature = "gst_page_table"))]
9use crate::granule::{set_granule, GranuleState};
10use crate::host;
11use crate::listen;
12use crate::measurement::HashContext;
13use crate::realm::rd::{Rd, State};
14use crate::rec::context::{set_reg, RegOffset};
15use crate::rec::State as RecState;
16use crate::rec::{max_recs_order, Rec, RmmRecEmulatableAbort::NotEmulatableAbort};
17use crate::rmi;
18use crate::rmi::error::Error;
19use crate::rsi::do_host_call;
20use crate::rsi::psci::complete_psci;
21use crate::{get_granule, get_granule_if};
22
23use aarch64_cpu::registers::*;
24use armv9a::bits_in_reg;
25
26extern crate alloc;
27
28fn prepare_args(rd: &mut Rd, mpidr: u64) -> Result<(usize, u64, u64), Error> {
29    let page_table = rd.s2_table().lock().get_base_address() as u64;
30    let vttbr = bits_in_reg(
31        VTTBR_EL2::VMID.mask << VTTBR_EL2::VMID.shift,
32        rd.id() as u64,
33    ) | bits_in_reg(
34        VTTBR_EL2::BADDR.mask << VTTBR_EL2::BADDR.shift,
35        page_table >> 1,
36    );
37    let vmpidr = mpidr | (MPIDR_EL1::RES1.mask << MPIDR_EL1::RES1.shift);
38    let vcpuid = rd.vcpu_index;
39    rd.vcpu_index += 1;
40    Ok((vcpuid, vttbr, vmpidr))
41}
42
43pub fn set_event_handler(rmi: &mut RmiHandle) {
44    #[cfg(not(kani))]
45    listen!(rmi, rmi::REC_CREATE, |arg, ret, rmm| {
46        let rd = arg[0];
47        let rec = arg[1];
48        let params_ptr = arg[2];
49        let owner = rd;
50
51        if rec == rd {
52            return Err(Error::RmiErrorInput);
53        }
54
55        rmm.page_table.map(params_ptr, false);
56        let params = host::copy_from::<Params>(params_ptr).ok_or(Error::RmiErrorInput)?;
57        rmm.page_table.unmap(params_ptr);
58        params.verify_compliance(rec, rd, params_ptr)?;
59
60        let rec_index = MPIDR::from(params.mpidr).index();
61        let mut rd_granule = get_granule_if!(rd, GranuleState::RD)?;
62        let mut rd = rd_granule.content_mut::<Rd>()?;
63        if !rd.at_state(State::New) {
64            return Err(Error::RmiErrorRealm(0));
65        }
66        if rd.num_recs() == (1 << max_recs_order()) - 1 {
67            return Err(Error::RmiErrorRealm(0));
68        }
69
70        if rec_index != rd.rec_index() {
71            return Err(Error::RmiErrorInput);
72        }
73        // set Rec_state and grab the lock for Rec granule
74        let mut rec_granule = get_granule_if!(rec, GranuleState::Delegated)?;
75        #[cfg(not(kani))]
76        // `page_table` is currently not reachable in model checking harnesses
77        rmm.page_table.map(rec, true);
78        let mut rec = rec_granule.new_uninit_with::<Rec<'_>>(Rec::new())?;
79        match prepare_args(&mut rd, params.mpidr) {
80            Ok((vcpuid, vttbr, vmpidr)) => {
81                ret[1] = vcpuid;
82
83                // Transit granule status of param.aux to RecAux from Delegated
84                // before access within rec.init().
85                for i in 0..rmi::MAX_REC_AUX_GRANULES {
86                    let aux = params.aux[i] as usize;
87                    rmm.page_table.map(aux, true);
88                    let mut aux_granule = get_granule_if!(aux, GranuleState::Delegated)?;
89                    set_granule(&mut aux_granule, GranuleState::RecAux)?;
90                }
91                rec.init(owner, vcpuid, params.flags, params.aux, vttbr, vmpidr)?;
92            }
93            Err(_) => return Err(Error::RmiErrorInput),
94        }
95
96        for (idx, gpr) in params.gprs.iter().enumerate() {
97            if set_reg(&mut rec, idx, *gpr as usize).is_err() {
98                return Err(Error::RmiErrorInput);
99            }
100        }
101        if set_reg(&mut rec, RegOffset::PC, params.pc as usize).is_err() {
102            return Err(Error::RmiErrorInput);
103        }
104        rec.set_vtcr(prepare_vtcr(&rd)?);
105
106        rd.inc_recs();
107        #[cfg(not(kani))]
108        // `rsi` is currently not reachable in model checking harnesses
109        HashContext::new(&mut rd)?.measure_rec_params(&params)?;
110
111        #[cfg(not(feature = "gst_page_table"))]
112        rd_granule.inc_count();
113
114        #[cfg(feature = "gst_page_table")]
115        return set_granule_with_parent(rd_granule.clone(), &mut rec_granule, GranuleState::Rec);
116        #[cfg(not(feature = "gst_page_table"))]
117        return set_granule(&mut rec_granule, GranuleState::Rec);
118    });
119
120    #[cfg(any(not(kani), feature = "mc_rmi_rec_destroy"))]
121    listen!(rmi, rmi::REC_DESTROY, |arg, _ret, rmm| {
122        let mut rec_granule = get_granule_if!(arg[0], GranuleState::Rec)?;
123
124        let rec = rec_granule.content::<Rec<'_>>()?;
125        if rec.get_state() == RecState::Running {
126            return Err(Error::RmiErrorRec);
127        }
128
129        #[cfg(not(kani))]
130        for i in 0..rmi::MAX_REC_AUX_GRANULES {
131            let rec_aux = rec.aux(i) as usize;
132            let mut rec_aux_granule = get_granule_if!(rec_aux, GranuleState::RecAux)?;
133            set_granule(&mut rec_aux_granule, GranuleState::Delegated)?;
134            rmm.page_table.unmap(rec_aux);
135        }
136        #[cfg(kani)]
137        {
138            // XXX: we check only the first aux to reduce the overall
139            //      verification time
140            let rec_aux = rec.aux(0) as usize;
141            // XXX: the below can be guaranteed by Rec's invariants instead
142            kani::assume(crate::granule::validate_addr(rec_aux));
143            let mut rec_aux_granule = get_granule!(rec_aux)?;
144            set_granule(&mut rec_aux_granule, GranuleState::Delegated)?;
145        }
146
147        let rd = rec.owner()?;
148        #[cfg(kani)]
149        {
150            // XXX: the below can be guaranteed by Rec's invariants instead
151            kani::assume(crate::granule::validate_addr(rd));
152            let rd_granule = get_granule!(rd)?;
153            kani::assume(rd_granule.state() == GranuleState::RD);
154        }
155        let mut rd_granule = get_granule_if!(rd, GranuleState::RD)?;
156        #[cfg(not(feature = "gst_page_table"))]
157        rd_granule.dec_count();
158        let mut rd = rd_granule.content::<Rd>()?;
159        rd.dec_recs();
160
161        set_granule(&mut rec_granule, GranuleState::Delegated).inspect_err(|_| {
162            #[cfg(not(kani))]
163            // `page_table` is currently not reachable in model checking harnesses
164            rmm.page_table.unmap(arg[0]);
165        })?;
166        #[cfg(not(kani))]
167        // `page_table` is currently not reachable in model checking harnesses
168        rmm.page_table.unmap(arg[0]);
169        Ok(())
170    });
171
172    #[cfg(not(kani))]
173    listen!(rmi, rmi::REC_ENTER, |arg, ret, rmm| {
174        let run_pa = arg[1];
175
176        // grab the lock for Rec
177        let mut rec_granule = get_granule_if!(arg[0], GranuleState::Rec)?;
178        let mut rec = rec_granule.content_mut::<Rec<'_>>()?;
179
180        // read Run
181        rmm.page_table.map(run_pa, false);
182        let mut run = host::copy_from::<Run>(run_pa).ok_or(Error::RmiErrorInput)?;
183        rmm.page_table.unmap(run_pa);
184        run.verify_compliance()?;
185        trace!("{:?}", run);
186
187        let rd_granule = get_granule_if!(rec.owner()?, GranuleState::RD)?;
188        let rd = rd_granule.content::<Rd>()?;
189        match rd.state() {
190            State::Active => {}
191            State::New => {
192                return Err(Error::RmiErrorRealm(0));
193            }
194            State::SystemOff => {
195                return Err(Error::RmiErrorRealm(1));
196            }
197            _ => {
198                panic!("Unexpected realm state");
199            }
200        }
201        // XXX: we explicitly release Rd's lock here to avoid a deadlock
202        core::mem::drop(rd_granule);
203
204        // runnable oder is lower
205        if !rec.runnable() {
206            return Err(Error::RmiErrorRec);
207        }
208
209        if let RecState::Running = rec.get_state() {
210            error!("Rec is already running: {:?}", *rec);
211            return Err(Error::RmiErrorRec);
212        }
213
214        if rec.psci_pending() {
215            return Err(Error::RmiErrorRec);
216        }
217
218        #[cfg(not(any(miri, test, fuzzing)))]
219        if !crate::rec::gic::validate_state(&run) {
220            return Err(Error::RmiErrorRec);
221        }
222
223        if rec.host_call_pending() {
224            // The below should be called without holding rd's lock
225            do_host_call(arg, ret, rmm, &mut rec, &mut run)?;
226        }
227
228        #[cfg(not(any(miri, test, fuzzing)))]
229        crate::rec::gic::receive_state_from_host(&mut rec, &run)?;
230        crate::rec::mmio::emulate_mmio(&mut rec, &run)?;
231        crate::rec::sea::host_sea_inject(&mut rec, &run)?;
232
233        crate::rsi::ripas::complete_ripas(&mut rec, &run)?;
234
235        let wfx_flag = run.entry_flags();
236        #[cfg(not(any(miri, test, fuzzing)))]
237        {
238            let hcr_el2 = HCR_EL2.get();
239            let mut wfx = 0;
240            let wfx_mask = !(3 << 13);
241            if wfx_flag.get_masked(EntryFlag::TRAP_WFI) != 0 {
242                wfx |= 1 << 13;
243            }
244            if wfx_flag.get_masked(EntryFlag::TRAP_WFE) != 0 {
245                wfx |= 1 << 14;
246            }
247            HCR_EL2.set((hcr_el2 & wfx_mask) | wfx);
248        }
249
250        #[cfg(not(any(miri, test, fuzzing)))]
251        activate_stage2_mmu(&rec);
252
253        crate::rec::save_host_state(&rec);
254        let mut ret_ns;
255        loop {
256            ret_ns = true;
257            run.set_imm(0);
258
259            rec.set_state(RecState::Running);
260
261            #[cfg(not(any(miri, test, fuzzing)))]
262            {
263                use crate::rmi::rec::exit::handle_realm_exit;
264
265                let rd_granule = get_granule_if!(rec.owner()?, GranuleState::RD)?;
266                let rd = rd_granule.content::<Rd>()?;
267
268                rec.set_emulatable_abort(NotEmulatableAbort);
269                crate::rec::run_prepare(&rd, rec.vcpuid(), &mut rec, 0)?;
270                // XXX: we explicitly release Rd's lock here, because RSI calls
271                //      would acquire the same lock again (deadlock).
272                core::mem::drop(rd_granule);
273                match crate::rec::run() {
274                    Ok(realm_exit_res) => {
275                        (ret_ns, ret[0]) =
276                            handle_realm_exit(realm_exit_res, rmm, &mut rec, &mut run)?
277                    }
278                    Err(_) => ret[0] = rmi::ERROR_REC,
279                }
280            }
281
282            #[cfg(any(miri, test))]
283            {
284                use crate::test_utils::mock;
285                mock::realm::setup_psci_complete(&mut rec, &mut run);
286                mock::realm::setup_ripas_state(&mut rec, &mut run);
287            }
288
289            #[cfg(fuzzing)]
290            {
291                use crate::test_utils::mock;
292
293                // In fuzzing contexts, unused register x3 denotes either RSI command or
294                // pseudo RSI command, REC_ENTER_EXIT_CMD for simulating realm exit conditions.
295                // Unused registers x4 and above serve as arguments for the RSI command.
296                if arg.len() >= 3 {
297                    let cmd = arg[2];
298                    let args = &arg[3..];
299
300                    rec.set_emulatable_abort(NotEmulatableAbort);
301
302                    (_, ret[0]) = mock::realm::emulate_realm(rmm, &mut rec, &mut run, cmd, args)?;
303                }
304            }
305
306            rec.set_state(RecState::Ready);
307
308            if ret_ns {
309                break;
310            }
311        }
312
313        #[cfg(not(any(miri, test, fuzzing)))]
314        crate::rec::gic::send_state_to_host(&rec, &mut run)?;
315        crate::rec::timer::send_state_to_host(&rec, &mut run)?;
316        crate::rec::pmu::send_state_to_host(&rec, &mut run)?;
317        crate::rec::restore_host_state(&rec);
318
319        // NOTICE: do not modify `run` after copy_to_ptr(). it won't have any effect.
320        rmm.page_table.map(run_pa, false);
321        let ret = host::copy_to_ptr::<Run>(&run, run_pa).ok_or(Error::RmiErrorInput);
322        rmm.page_table.unmap(run_pa);
323        ret
324    });
325
326    #[cfg(not(kani))]
327    listen!(rmi, rmi::PSCI_COMPLETE, |arg, _ret, _rmm| {
328        let caller_pa = arg[0];
329        let target_pa = arg[1];
330
331        if caller_pa == target_pa {
332            return Err(Error::RmiErrorInput);
333        }
334        let mut caller_granule = get_granule_if!(caller_pa, GranuleState::Rec)?;
335        let mut caller = caller_granule.content_mut::<Rec<'_>>()?;
336
337        let mut target_granule = get_granule_if!(target_pa, GranuleState::Rec)?;
338        let mut target = target_granule.content_mut::<Rec<'_>>()?;
339
340        let status = arg[2];
341
342        if !caller.psci_pending() {
343            return Err(Error::RmiErrorInput);
344        }
345
346        if caller.realmid()? != target.realmid()? {
347            return Err(Error::RmiErrorInput);
348        }
349
350        complete_psci(&mut caller, &mut target, status)
351    });
352}
353
354#[cfg(test)]
355mod test {
356    use crate::event::realmexit::RecExitReason;
357    use crate::rmi::rec::run::Run;
358    use crate::rmi::*;
359    use crate::rsi::PSCI_CPU_ON;
360    use crate::test_utils::*;
361
362    // Source: https://github.com/ARM-software/cca-rmm-acs
363    // Test Case: cmd_rec_create
364    // Covered RMIs: REC_CREATE, REC_DESTROY, REC_AUX_COUNT
365    // Related Spec: D1.2.4 REC creation flow
366    #[test]
367    fn rmi_rec_create_positive() {
368        let rd = realm_create();
369        rec_create(rd, IDX_REC1, IDX_REC1_PARAMS, IDX_REC1_AUX);
370        rec_destroy(IDX_REC1, IDX_REC1_AUX);
371        realm_destroy(rd);
372
373        miri_teardown();
374    }
375
376    // Source: https://github.com/ARM-software/cca-rmm-acs
377    // Test Case: cmd_psci_complete
378    // Covered RMIs: REC_ENTER, PSCI_COMPLETE
379    #[test]
380    fn rmi_rec_enter_positive() {
381        let rd = mock::host::realm_setup();
382
383        let (rec1, run1) = (granule_addr(IDX_REC1), granule_addr(IDX_REC1_RUN));
384        let ret = rmi::<REC_ENTER>(&[rec1, run1]);
385        assert_eq!(ret[0], SUCCESS);
386
387        unsafe {
388            let run = &*(run1 as *const Run);
389            let reason: u64 = RecExitReason::PSCI.into();
390            assert_eq!(run.exit_reason(), reason as u8);
391            assert_eq!(run.gpr(0).unwrap(), PSCI_CPU_ON as u64);
392        }
393
394        let rec2 = granule_addr(IDX_REC2);
395        const PSCI_E_SUCCESS: usize = 0;
396        let ret = rmi::<PSCI_COMPLETE>(&[rec1, rec2, PSCI_E_SUCCESS]);
397        assert_eq!(ret[0], SUCCESS);
398
399        mock::host::realm_teardown(rd);
400
401        miri_teardown();
402    }
403}