Skip to main content

sidecar_defs/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Definitions for the sidecar kernel.
5
6#![no_std]
7#![forbid(unsafe_code)]
8
9use core::sync::atomic::AtomicU8;
10use core::sync::atomic::AtomicU32;
11use hvdef::HvMessage;
12use hvdef::HvStatus;
13use hvdef::hypercall::HvInputVtl;
14use open_enum::open_enum;
15use zerocopy::FromBytes;
16use zerocopy::FromZeros;
17use zerocopy::Immutable;
18use zerocopy::IntoBytes;
19use zerocopy::KnownLayout;
20
21/// Starting state for each CPU, supplied by `openhcl_boot`.
22/// For various reasons, `openhcl_boot` may have decided to
23/// instruct the linux kernel to start additional CPUs (notably,
24/// in the event that we know we're about to spawn tasks on those
25/// CPUs right away to handle device interrupts).
26#[repr(C)]
27#[derive(FromZeros, Immutable, KnownLayout, Debug, Clone)]
28pub struct PerCpuState {
29    /// Whether the per-CPU state is specified, since `NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE`
30    /// is less than the maximum number of CPUs supported by OpenHCL and also by sidecar.
31    pub per_cpu_state_specified: bool,
32
33    /// Whether the CPU should be started by the sidecar kernel. If false,
34    /// the CPU will be started by (or remain with) the main kernel instead.
35    pub sidecar_starts_cpu: [bool; NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE],
36}
37
38/// Sidecar start input parameters.
39#[repr(C, align(4096))]
40#[derive(FromZeros, Immutable, KnownLayout)]
41pub struct SidecarParams {
42    /// The physical address of the x86-64 hypercall page.
43    pub hypercall_page: u64,
44    /// If true, enabling serial logging.
45    pub enable_logging: bool,
46    /// The number of valid nodes in `nodes`.
47    pub node_count: u32,
48    /// The node-specific input parameters.
49    pub nodes: [SidecarNodeParams; MAX_NODES],
50    /// The initial CPU state for each CPU.
51    pub initial_state: PerCpuState,
52}
53
54/// Node-specific input parameters.
55#[repr(C)]
56#[derive(FromZeros, Immutable, KnownLayout)]
57pub struct SidecarNodeParams {
58    /// The physical address of the beginning of the reserved memory for this
59    /// node. Must be page aligned.
60    pub memory_base: u64,
61    /// The size of the reserved memory region. Must be a page multiple.
62    pub memory_size: u64,
63    /// The base VP for this node.
64    pub base_vp: u32,
65    /// The number of VPs in the node.
66    pub vp_count: u32,
67}
68
69/// The maximum number of supported sidecar nodes.
70pub const MAX_NODES: usize = 128;
71/// The maximum number of supported sidecar CPUs.
72/// Keep small to leave space on the SidecarParams page for future fields.
73/// VMs with more CPUs fall back to disabling sidecar on restore.
74pub const NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE: usize = 400;
75
76const _: () = assert!(size_of::<SidecarParams>() <= PAGE_SIZE);
77
78/// The output of the sidecar kernel boot process.
79#[repr(C)]
80#[derive(FromZeros, Immutable, KnownLayout)]
81pub struct SidecarOutput {
82    /// The boot error. This is only set if the entry point returns false.
83    pub error: CommandError,
84    /// The per-node output information.
85    pub nodes: [SidecarNodeOutput; MAX_NODES],
86}
87
88/// The per-node output of the sidecar kernel boot process.
89#[repr(C)]
90#[derive(FromZeros, Immutable, KnownLayout)]
91pub struct SidecarNodeOutput {
92    /// The physical address of the control page for the node.
93    pub control_page: u64,
94    /// The base physical address of the per-VP pages for the node.
95    pub shmem_pages_base: u64,
96    /// The size of the VP page region.
97    pub shmem_pages_size: u64,
98}
99
100const _: () = assert!(size_of::<SidecarOutput>() <= PAGE_SIZE);
101
102/// The page size for all sidecar objects.
103pub const PAGE_SIZE: usize = 4096;
104
105/// The per-node control page, which is used to communicate between the sidecar
106/// kernel and the main kernel sidecar kernel driver.
107#[repr(C, align(4096))]
108pub struct ControlPage {
109    /// The node index.
110    pub index: AtomicU32,
111    /// The base CPU of the node.
112    pub base_cpu: AtomicU32,
113    /// The number of CPUs in the node.
114    pub cpu_count: AtomicU32,
115    /// The vector the driver should IPI to wake up a sidecar CPU.
116    pub request_vector: AtomicU32,
117    /// The APIC ID of the CPU that the sidecar CPU should IPI to wake up the
118    /// driver.
119    pub response_cpu: AtomicU32,
120    /// The vector the sidecar CPU should IPI to wake up the driver.
121    pub response_vector: AtomicU32,
122    /// If non-zero, then a sidecar CPU has a message for the driver.
123    pub needs_attention: AtomicU32,
124    /// Reserved.
125    pub reserved: [u8; 36],
126    /// The per-CPU status.
127    pub cpu_status: [AtomicU8; 4032],
128}
129
130const _: () = assert!(size_of::<ControlPage>() == PAGE_SIZE);
131
132open_enum::open_enum! {
133    /// The CPU status.
134    pub enum CpuStatus: u8 {
135        /// The CPU is not running in the sidecar kernel.
136        REMOVED = 0,
137        /// The CPU is idle, having completed any previous commands.
138        IDLE = 1,
139        /// The CPU is running a command.
140        RUN = 2,
141        /// The CPU is being asked to stop running a command.
142        STOP = 3,
143        /// The CPU is being asked to terminate.
144        REMOVE = 4,
145    }
146}
147
148/// The number of reserved pages required for each VP.
149// 1. pml4
150// 2. pdpt
151// 3. pd
152// 4. pt
153// 5. globals
154// 6. vp assist page
155// 7. hypercall input page
156// 8. hypercall output page
157pub const PER_VP_PAGES: usize = 8 + STACK_PAGES;
158
159/// The number of per-VP shared-memory pages.
160// 1. command page
161// 2. register page
162pub const PER_VP_SHMEM_PAGES: usize = 2;
163
164/// The number of pages in the per-VP stack.
165pub const STACK_PAGES: usize = 3;
166
167/// The required memory (in bytes) for a node.
168pub const fn required_memory(vp_count: u32) -> usize {
169    // Control page + per-VP pages.
170    (1 + (PER_VP_SHMEM_PAGES + PER_VP_PAGES) * vp_count as usize) * PAGE_SIZE
171}
172
173/// The sidecar command page, containing command requests and responses.
174#[repr(C)]
175#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
176pub struct CommandPage {
177    /// The command to run.
178    pub command: SidecarCommand,
179    /// If non-zero, the command failed.
180    pub has_error: u8,
181    /// Padding bytes.
182    pub padding: [u8; 11],
183    /// The current CPU register state.
184    pub cpu_context: CpuContextX64,
185    /// The intercept message from the last VP exit.
186    pub intercept_message: HvMessage,
187    /// The error, if `has_error` is non-zero.
188    pub error: CommandError,
189    /// The request data for the command.
190    pub request_data: [u128; REQUEST_DATA_SIZE / size_of::<u128>()],
191    /// Reserved.
192    pub reserved: [u64; 190],
193}
194
195const REQUEST_DATA_SIZE: usize = 64 * size_of::<u128>();
196
197/// A string error.
198#[repr(C)]
199#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
200pub struct CommandError {
201    /// The length of the error string, in bytes.
202    pub len: u8,
203    /// The error string, encoded as UTF-8, containing `len` bytes.
204    pub buf: [u8; 255],
205}
206
207const _: () = assert!(size_of::<CommandPage>() == PAGE_SIZE);
208
209open_enum! {
210    /// The sidecar command.
211    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
212    pub enum SidecarCommand: u32 {
213        /// No command.
214        NONE = 0,
215        /// Run the VP until cancelled or an intercept occurs.
216        RUN_VP = 1,
217        /// Gets VP registers.
218        GET_VP_REGISTERS = 2,
219        /// Sets VP registers.
220        SET_VP_REGISTERS = 3,
221        /// Translates a guest virtual address.
222        TRANSLATE_GVA = 4,
223    }
224}
225
226/// A request and response for [`SidecarCommand::GET_VP_REGISTERS`] or
227/// [`SidecarCommand::SET_VP_REGISTERS`].
228///
229/// Followed by an array of [`hvdef::hypercall::HvRegisterAssoc`], which are
230/// updated in place for the get request.
231#[repr(C)]
232#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
233pub struct GetSetVpRegisterRequest {
234    /// The number of registers to get.
235    pub count: u16,
236    /// The target VTL.
237    pub target_vtl: HvInputVtl,
238    /// Reserved.
239    pub rsvd: u8,
240    /// The hypervisor result.
241    pub status: HvStatus,
242    /// Reserved.
243    pub rsvd2: [u8; 10],
244    /// Alignment field.
245    pub regs: [hvdef::hypercall::HvRegisterAssoc; 0],
246}
247
248/// The maximum number of registers that can be requested in a single
249/// [`SidecarCommand::GET_VP_REGISTERS`] or
250/// [`SidecarCommand::SET_VP_REGISTERS`].
251pub const MAX_GET_SET_VP_REGISTERS: usize = (REQUEST_DATA_SIZE
252    - size_of::<GetSetVpRegisterRequest>())
253    / size_of::<hvdef::hypercall::HvRegisterAssoc>();
254
255/// A request for [`SidecarCommand::TRANSLATE_GVA`].
256#[repr(C)]
257#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
258pub struct TranslateGvaRequest {
259    /// The guest virtual address page number.
260    pub gvn: u64,
261    /// The control flags.
262    pub control_flags: hvdef::hypercall::TranslateGvaControlFlagsX64,
263}
264
265/// A response for [`SidecarCommand::TRANSLATE_GVA`].
266#[repr(C)]
267#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
268pub struct TranslateGvaResponse {
269    /// The hypervisor result.
270    pub status: HvStatus,
271    /// Reserved.
272    pub rsvd: [u16; 7],
273    /// The output of the translation.
274    pub output: hvdef::hypercall::TranslateVirtualAddressExOutputX64,
275}
276
277/// A response for [`SidecarCommand::RUN_VP`].
278#[repr(C)]
279#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
280pub struct RunVpResponse {
281    /// If true, the VP was stopped due to an intercept.
282    pub intercept: u8,
283}
284
285/// The CPU context for x86-64.
286#[repr(C, align(16))]
287#[derive(Debug, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
288pub struct CpuContextX64 {
289    /// The general purpose registers, in the usual order, except CR2 is in
290    /// RSP's position.
291    pub gps: [u64; 16],
292    /// The `fxsave` state.
293    pub fx_state: x86defs::xsave::Fxsave,
294    /// Reserved.
295    pub reserved: [u8; 384],
296}
297
298impl CpuContextX64 {
299    /// The index of the RAX register.
300    pub const RAX: usize = 0;
301    /// The index of the RCX register.
302    pub const RCX: usize = 1;
303    /// The index of the RDX register.
304    pub const RDX: usize = 2;
305    /// The index of the RBX register.
306    pub const RBX: usize = 3;
307    /// The index of the CR2 register.
308    pub const CR2: usize = 4;
309    /// The index of the RBP register.
310    pub const RBP: usize = 5;
311    /// The index of the RSI register.
312    pub const RSI: usize = 6;
313    /// The index of the RDI register.
314    pub const RDI: usize = 7;
315    /// The index of the R8 register.
316    pub const R8: usize = 8;
317    /// The index of the R9 register.
318    pub const R9: usize = 9;
319    /// The index of the R10 register.
320    pub const R10: usize = 10;
321    /// The index of the R11 register.
322    pub const R11: usize = 11;
323    /// The index of the R12 register.
324    pub const R12: usize = 12;
325    /// The index of the R13 register.
326    pub const R13: usize = 13;
327    /// The index of the R14 register.
328    pub const R14: usize = 14;
329    /// The index of the R15 register.
330    pub const R15: usize = 15;
331}