microsoft/openvmm
Publicmirrored fromhttps://github.com/microsoft/openvmmAvailable
openhcl/hcl/src/ioctl/deferred.rs
192lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | //! Support routines for deferred actions. |
| 5 | |
| 6 | use super::Hcl; |
| 7 | use crate::protocol; |
| 8 | use crate::protocol::hcl_run; |
| 9 | use cvm_tracing::CVM_ALLOWED; |
| 10 | use std::cell::Cell; |
| 11 | use std::cell::UnsafeCell; |
| 12 | use std::marker::PhantomData; |
| 13 | use zerocopy::IntoBytes; |
| 14 | |
| 15 | thread_local! { |
| 16 | static DEFERRED_ACTIONS: DeferredActions = const { DeferredActions::new() }; |
| 17 | } |
| 18 | |
| 19 | struct DeferredActions { |
| 20 | actions: [Cell<DeferredAction>; MAX_ACTIONS as usize], |
| 21 | used: Cell<u8>, |
| 22 | } |
| 23 | |
| 24 | const MAX_ACTIONS: u8 = 8; |
| 25 | const DISABLED: u8 = !0; |
| 26 | |
| 27 | impl DeferredActions { |
| 28 | const fn new() -> Self { |
| 29 | Self { |
| 30 | actions: [const { Cell::new(DeferredAction::Noop) }; MAX_ACTIONS as usize], |
| 31 | used: Cell::new(DISABLED), |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | fn drain(&self) -> &[Cell<DeferredAction>] { |
| 36 | let used = self.used.replace(0); |
| 37 | &self.actions[..used as usize] |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | /// Pushes an action to the current thread's list of deferred actions. If the |
| 42 | /// list is full or there is no list for the current thread, the action will be |
| 43 | /// run immediately. |
| 44 | pub fn push_deferred_action(hcl: &Hcl, action: DeferredAction) { |
| 45 | DEFERRED_ACTIONS.with(|deferred| { |
| 46 | let used = deferred.used.get(); |
| 47 | if used < MAX_ACTIONS { |
| 48 | deferred.actions[used as usize].set(action); |
| 49 | deferred.used.set(used + 1); |
| 50 | } else { |
| 51 | // The action couldn't be deferred, so run it immediately. |
| 52 | action.run(hcl) |
| 53 | } |
| 54 | }); |
| 55 | } |
| 56 | |
| 57 | /// A token representing that a deferred actions list has been registered for |
| 58 | /// the current thread. |
| 59 | /// |
| 60 | /// When dropped, this will flush any deferred actions that were registered. The |
| 61 | /// owner can also call `flush` to run the actions immediately, if desired, and |
| 62 | /// `move_to_slots` to copy the actions to the HCL run structure's action slots |
| 63 | /// before running the VP. |
| 64 | // |
| 65 | // DEVNOTE: Use a PhantomData to ensure this isn't `Sync` or `Send`, so that it |
| 66 | // doesn't move to another thread. |
| 67 | pub struct RegisteredDeferredActions<'a>(&'a Hcl, PhantomData<*const ()>); |
| 68 | |
| 69 | /// Registers a deferred actions list for the current thread. |
| 70 | pub fn register_deferred_actions(hcl: &Hcl) -> RegisteredDeferredActions<'_> { |
| 71 | DEFERRED_ACTIONS.with(|deferred| { |
| 72 | assert_eq!(deferred.used.replace(0), DISABLED); |
| 73 | }); |
| 74 | RegisteredDeferredActions(hcl, PhantomData) |
| 75 | } |
| 76 | |
| 77 | impl RegisteredDeferredActions<'_> { |
| 78 | /// Moves the queued actions to the slots in the run page. Issues any |
| 79 | /// immediately that won't fit in the run page. |
| 80 | pub fn move_to_slots(&mut self, slots: &mut DeferredActionSlots<'_>) { |
| 81 | self.with(|deferred, hcl| { |
| 82 | for action in deferred.drain() { |
| 83 | let action = action.get(); |
| 84 | if !action.post(slots) { |
| 85 | action.run(hcl); |
| 86 | } |
| 87 | } |
| 88 | }) |
| 89 | } |
| 90 | |
| 91 | /// Runs actions immediately without deferring them to VTL return. |
| 92 | pub fn flush(&mut self) { |
| 93 | self.with(|deferred, hcl| { |
| 94 | for action in deferred.drain() { |
| 95 | action.get().run(hcl); |
| 96 | } |
| 97 | }); |
| 98 | } |
| 99 | |
| 100 | fn with(&mut self, f: impl FnOnce(&DeferredActions, &Hcl)) { |
| 101 | DEFERRED_ACTIONS.with(|deferred| { |
| 102 | debug_assert!(deferred.used.get() <= MAX_ACTIONS); |
| 103 | f(deferred, self.0); |
| 104 | }); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | impl Drop for RegisteredDeferredActions<'_> { |
| 109 | fn drop(&mut self) { |
| 110 | self.flush(); |
| 111 | DEFERRED_ACTIONS.with(|deferred| { |
| 112 | assert_eq!(deferred.used.replace(DISABLED), 0); |
| 113 | }) |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /// A deferred action that can be handled by the hypervisor as part of switching |
| 118 | /// VTLs. |
| 119 | #[derive(Debug, Copy, Clone)] |
| 120 | pub(crate) enum DeferredAction { |
| 121 | Noop, |
| 122 | SignalEvent { vp: u32, sint: u8, flag: u16 }, |
| 123 | } |
| 124 | |
| 125 | impl DeferredAction { |
| 126 | /// Run the action via a hypercall. |
| 127 | fn run(&self, hcl: &Hcl) { |
| 128 | match *self { |
| 129 | DeferredAction::Noop => {} |
| 130 | DeferredAction::SignalEvent { vp, sint, flag } => { |
| 131 | if let Err(err) = hcl.hvcall_signal_event_direct(vp, sint, flag) { |
| 132 | tracelimit::warn_ratelimited!( |
| 133 | CVM_ALLOWED, |
| 134 | error = &err as &dyn std::error::Error, |
| 135 | vp, |
| 136 | sint, |
| 137 | flag, |
| 138 | "failed to signal event" |
| 139 | ); |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /// Post the action to the HCL. |
| 146 | fn post(&self, slots: &mut DeferredActionSlots<'_>) -> bool { |
| 147 | match *self { |
| 148 | DeferredAction::Noop => true, |
| 149 | DeferredAction::SignalEvent { vp, sint, flag } => slots.push( |
| 150 | protocol::hv_vp_assist_page_signal_event { |
| 151 | action_type: protocol::HV_VP_ASSIST_PAGE_ACTION_TYPE_SIGNAL_EVENT, |
| 152 | vp, |
| 153 | vtl: 0, |
| 154 | sint, |
| 155 | flag, |
| 156 | } |
| 157 | .as_bytes(), |
| 158 | ), |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /// A reference to the HCL run data structure's deferred action slots. |
| 164 | pub(crate) struct DeferredActionSlots<'a>(&'a UnsafeCell<hcl_run>); |
| 165 | |
| 166 | impl<'a> DeferredActionSlots<'a> { |
| 167 | /// # Safety |
| 168 | /// The caller must ensure that the return action fields in `run` remain |
| 169 | /// valid and unaliased for the lifetime of this object. |
| 170 | pub unsafe fn new(run: &'a UnsafeCell<hcl_run>) -> Self { |
| 171 | Self(run) |
| 172 | } |
| 173 | |
| 174 | fn push(&mut self, action: &[u8]) -> bool { |
| 175 | let (used, buffer); |
| 176 | // SAFETY: this thread is the only one concurrently accessing the |
| 177 | // action-related portions of the run structure. |
| 178 | unsafe { |
| 179 | used = &mut (*self.0.get()).vtl_ret_action_size; |
| 180 | buffer = &mut (*self.0.get()).vtl_ret_actions; |
| 181 | } |
| 182 | let offset = *used as usize; |
| 183 | if let Some(buffer) = buffer.get_mut(offset..offset + action.len()) { |
| 184 | buffer.copy_from_slice(action); |
| 185 | *used += action.len() as u32; |
| 186 | true |
| 187 | } else { |
| 188 | // The action buffer is full. |
| 189 | false |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | |