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}