Skip to main content

sidecar/arch/x86_64/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! x86_64-specific sidecar code.
5
6#![cfg(target_arch = "x86_64")]
7// UNSAFETY: Interacting with low level hardware and memory primitives.
8#![expect(unsafe_code)]
9
10mod init;
11mod temporary_map;
12mod vp;
13
14use core::fmt::Write;
15use core::sync::atomic::AtomicBool;
16use core::sync::atomic::Ordering::Acquire;
17use hvdef::HvError;
18use hvdef::HypercallCode;
19use minimal_rt::arch::com3;
20use minimal_rt::arch::msr::write_msr;
21use uart_16550::Uart16550Tty;
22use uart_16550::backend::PioBackend;
23use x86defs::Exception;
24use zerocopy::FromBytes;
25use zerocopy::IntoBytes;
26
27mod addr_space {
28    use super::VpGlobals;
29    use memory_range::MemoryRange;
30
31    // These must match their use in entry.S.
32    const PTE_SELF: usize = 0;
33    const PTE_HYPERCALL_INPUT: usize = 1;
34    const PTE_HYPERCALL_OUTPUT: usize = 2;
35    const PTE_COMMAND_PAGE: usize = 3;
36    const PTE_GLOBALS: usize = 4;
37    const PTE_ASSIST_PAGE: usize = 5;
38    const PTE_CONTROL_PAGE: usize = 6;
39    const PTE_REGISTER_PAGE: usize = 7;
40    const PTE_TEMPORARY_MAP: usize = 256;
41    const PTE_STACK: usize = PTE_STACK_END - sidecar_defs::STACK_PAGES;
42    const PTE_STACK_END: usize = 512;
43
44    const PAGE_SIZE: u64 = 0x1000;
45
46    unsafe extern "C" {
47        static __ehdr_start: u8;
48    }
49
50    fn pte_data(addr: u64) -> x86defs::Pte {
51        x86defs::Pte::new()
52            .with_address(addr)
53            .with_read_write(true)
54            .with_present(true)
55            .with_no_execute(true)
56    }
57
58    /// Returns the physical address of the globals.
59    pub fn init_ap(
60        pt: &mut [x86defs::Pte; 512],
61        pt_pa: u64,
62        control_page_pa: u64,
63        command_page_pa: u64,
64        reg_page_pa: u64,
65        memory: &mut impl Iterator<Item = u64>,
66    ) -> u64 {
67        pt.fill(x86defs::Pte::new());
68        for i in 0..sidecar_defs::STACK_PAGES {
69            pt[PTE_STACK + i] = pte_data(memory.next().unwrap());
70        }
71        pt[PTE_SELF] = pte_data(pt_pa);
72        pt[PTE_COMMAND_PAGE] = pte_data(command_page_pa);
73        let globals_pa = memory.next().unwrap();
74        pt[PTE_GLOBALS] = pte_data(globals_pa);
75        pt[PTE_ASSIST_PAGE] = pte_data(memory.next().unwrap());
76        pt[PTE_HYPERCALL_INPUT] = pte_data(memory.next().unwrap());
77        pt[PTE_HYPERCALL_OUTPUT] = pte_data(memory.next().unwrap());
78        pt[PTE_CONTROL_PAGE] = pte_data(control_page_pa);
79        pt[PTE_REGISTER_PAGE] = pte_data(reg_page_pa);
80        globals_pa
81    }
82
83    fn base_address() -> usize {
84        core::ptr::addr_of!(__ehdr_start) as usize
85    }
86
87    fn per_vp(page: usize) -> usize {
88        base_address() + 0x200000 + page * PAGE_SIZE as usize
89    }
90
91    fn pte(page: usize) -> *mut x86defs::Pte {
92        (per_vp(PTE_SELF) as *mut x86defs::Pte).wrapping_add(page)
93    }
94
95    pub fn temporary_map() -> usize {
96        per_vp(PTE_TEMPORARY_MAP)
97    }
98
99    pub fn temp_ptes() -> *mut x86defs::Pte {
100        pte(PTE_TEMPORARY_MAP)
101    }
102
103    pub fn stack() -> MemoryRange {
104        MemoryRange::new(per_vp(PTE_STACK) as u64..per_vp(PTE_STACK_END) as u64)
105    }
106
107    pub fn stack_base_pa() -> usize {
108        // SAFETY: the stack PTE is not changing concurrently.
109        unsafe { pte(PTE_STACK).read() }.address() as usize
110    }
111
112    pub fn command_page() -> *mut sidecar_defs::CommandPage {
113        per_vp(PTE_COMMAND_PAGE) as *mut _
114    }
115
116    pub fn globals() -> *mut VpGlobals {
117        (per_vp(PTE_GLOBALS)) as *mut _
118    }
119
120    pub fn assist_page() -> *mut hvdef::HvVpAssistPage {
121        (per_vp(PTE_ASSIST_PAGE)) as *mut _
122    }
123
124    pub fn assist_page_pa() -> u64 {
125        // SAFETY: the assist page PTE is not changing concurrently.
126        unsafe { pte(PTE_ASSIST_PAGE).read() }.address()
127    }
128
129    pub fn register_page() -> *mut hvdef::HvX64RegisterPage {
130        (per_vp(PTE_REGISTER_PAGE)) as *mut _
131    }
132
133    pub fn register_page_pa() -> u64 {
134        // SAFETY: the register page PTE is not changing concurrently.
135        unsafe { pte(PTE_REGISTER_PAGE).read() }.address()
136    }
137
138    pub fn hypercall_input() -> *mut [u8; 4096] {
139        (per_vp(PTE_HYPERCALL_INPUT)) as *mut [u8; 4096]
140    }
141
142    pub fn hypercall_input_pa() -> u64 {
143        // SAFETY: the hypercall input PTE is not changing concurrently.
144        unsafe { pte(PTE_HYPERCALL_INPUT).read() }.address()
145    }
146
147    pub fn hypercall_output() -> *mut [u8; 4096] {
148        (per_vp(PTE_HYPERCALL_OUTPUT)) as *mut [u8; 4096]
149    }
150
151    pub fn hypercall_output_pa() -> u64 {
152        // SAFETY: the hypercall output PTE is not changing concurrently.
153        unsafe { pte(PTE_HYPERCALL_OUTPUT).read() }.address()
154    }
155
156    pub fn control_page() -> *const sidecar_defs::ControlPage {
157        (per_vp(PTE_CONTROL_PAGE)) as *const _
158    }
159}
160
161struct VpGlobals {
162    hv_vp_index: u32,
163    node_cpu_index: u32,
164    overlays_mapped: bool,
165    register_page_mapped: bool,
166}
167
168const _: () = assert!(size_of::<VpGlobals>() <= 0x1000);
169
170static mut VTL_RETURN_OFFSET: u16 = 0;
171static mut VSM_CAPABILITIES: hvdef::HvRegisterVsmCapabilities =
172    hvdef::HvRegisterVsmCapabilities::new();
173static AFTER_INIT: AtomicBool = AtomicBool::new(false);
174static ENABLE_LOG: AtomicBool = AtomicBool::new(false);
175// The boot shim already initialized the device.
176static SERIAL: spin::Mutex<Uart16550Tty<PioBackend>> =
177    spin::Mutex::new(Uart16550Tty::from_inner(com3()));
178
179macro_rules! log {
180    () => {};
181    ($($arg:tt)*) => {
182        if $crate::arch::x86_64::ENABLE_LOG.load(core::sync::atomic::Ordering::Relaxed) {
183            $crate::arch::x86_64::log_fmt(format_args!($($arg)*));
184        }
185    };
186}
187use core::mem::size_of;
188use hvdef::HvRegisterName;
189use hvdef::HvRegisterValue;
190use hvdef::hypercall::HvInputVtl;
191pub(crate) use log;
192
193fn log_fmt(args: core::fmt::Arguments<'_>) {
194    if ENABLE_LOG.load(Acquire) {
195        let mut serial = SERIAL.lock();
196        if AFTER_INIT.load(Acquire) {
197            // SAFETY: `hv_vp_index` is not being concurrently modified.
198            // TODO: improve how per-VP globals work.
199            let vp_index = unsafe { &*addr_space::globals() }.hv_vp_index;
200            let _ = writeln!(serial, "sidecar#{vp_index}: {}", args);
201        } else {
202            let _ = writeln!(serial, "sidecar: {}", args);
203        }
204    }
205}
206
207#[cfg_attr(minimal_rt, panic_handler)]
208#[cfg_attr(not(minimal_rt), expect(dead_code))]
209fn panic(panic: &core::panic::PanicInfo<'_>) -> ! {
210    let stack_va_to_pa = |ptr| {
211        addr_space::stack()
212            .offset_of(ptr as u64)
213            .map(|offset| addr_space::stack_base_pa() + offset as usize)
214    };
215    minimal_rt::enlightened_panic::report(*b"SIDECARK", panic, stack_va_to_pa);
216    if !AFTER_INIT.load(Acquire) {
217        if let Some(mut serial) = SERIAL.try_lock() {
218            let _ = writeln!(serial, "{panic}");
219        } else {
220            // We may have panicked while holding the lock, so trying to acquire it may deadlock.
221            // Best effort, we can't guarantee no tearing but print the message anyways.
222            let _ = writeln!(Uart16550Tty::from_inner(com3()), "{panic}");
223        }
224    }
225    minimal_rt::arch::fault();
226}
227
228struct CommandErrorWriter<'a>(&'a mut sidecar_defs::CommandError);
229
230impl Write for CommandErrorWriter<'_> {
231    fn write_str(&mut self, s: &str) -> core::fmt::Result {
232        let s = s.as_bytes();
233        let buf = &mut self.0.buf[self.0.len as usize..];
234        let n = buf.len().min(s.len());
235        buf[..n].copy_from_slice(&s[..n]);
236        self.0.len += n as u8;
237        Ok(())
238    }
239}
240
241fn hypercall(code: HypercallCode, rep_count: usize) -> Result<(), HvError> {
242    let control = hvdef::hypercall::Control::new()
243        .with_code(code.0)
244        .with_rep_count(rep_count);
245
246    // SAFETY: the caller guarantees the safety of the hypercall, including that
247    // the input and output pages are not concurrently accessed.
248    unsafe {
249        minimal_rt::arch::hypercall::invoke_hypercall(
250            control,
251            addr_space::hypercall_input_pa(),
252            addr_space::hypercall_output_pa(),
253        )
254        .result()
255    }
256}
257
258fn get_hv_vp_register(
259    target_vtl: HvInputVtl,
260    name: HvRegisterName,
261) -> Result<HvRegisterValue, HvError> {
262    {
263        // SAFETY: the input page is not concurrently accessed.
264        let input = unsafe { &mut *addr_space::hypercall_input() };
265
266        hvdef::hypercall::GetSetVpRegisters {
267            partition_id: hvdef::HV_PARTITION_ID_SELF,
268            vp_index: hvdef::HV_VP_INDEX_SELF,
269            target_vtl,
270            rsvd: [0; 3],
271        }
272        .write_to_prefix(input)
273        .unwrap();
274
275        name.write_to_prefix(&mut input[size_of::<hvdef::hypercall::GetSetVpRegisters>()..])
276            .unwrap();
277    }
278
279    hypercall(HypercallCode::HvCallGetVpRegisters, 1)?;
280    // SAFETY: the output is not concurrently accessed.
281    let output = unsafe { &*addr_space::hypercall_output() };
282    Ok(HvRegisterValue::read_from_prefix(output).unwrap().0) // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
283}
284
285fn set_hv_vp_register(
286    target_vtl: HvInputVtl,
287    name: HvRegisterName,
288    value: HvRegisterValue,
289) -> Result<(), HvError> {
290    {
291        // SAFETY: the input page is not concurrently accessed.
292        let input = unsafe { &mut *addr_space::hypercall_input() };
293
294        hvdef::hypercall::GetSetVpRegisters {
295            partition_id: hvdef::HV_PARTITION_ID_SELF,
296            vp_index: hvdef::HV_VP_INDEX_SELF,
297            target_vtl,
298            rsvd: [0; 3],
299        }
300        .write_to_prefix(input)
301        .unwrap();
302
303        hvdef::hypercall::HvRegisterAssoc {
304            name,
305            pad: [0; 3],
306            value,
307        }
308        .write_to_prefix(&mut input[size_of::<hvdef::hypercall::GetSetVpRegisters>()..])
309        .unwrap();
310    }
311
312    hypercall(HypercallCode::HvCallSetVpRegisters, 1)?;
313    Ok(())
314}
315
316fn eoi() {
317    // SAFETY: no safety requirements for EOI.
318    unsafe {
319        write_msr(x86defs::apic::ApicRegister::EOI.x2apic_msr(), 0);
320    }
321}
322
323#[cfg_attr(not(minimal_rt), expect(dead_code))]
324extern "C" fn irq_handler() {
325    eoi();
326}
327
328#[cfg_attr(not(minimal_rt), expect(dead_code))]
329extern "C" fn exception_handler(exception: Exception, rsp: u64) -> ! {
330    // SAFETY: reading cr2 has no safety requirements.
331    let cr2 = unsafe {
332        let cr2: u64;
333        core::arch::asm!("mov {}, cr2", out(reg) cr2);
334        cr2
335    };
336    panic!("unexpected exception {exception:?} cr2 = {cr2:#x} rsp = {rsp:#x}");
337}
338
339#[cfg(minimal_rt)]
340core::arch::global_asm! {
341    include_str!("entry.S"),
342    start = sym init::start,
343    relocate = sym minimal_rt::reloc::relocate,
344    irq_handler = sym irq_handler,
345    exception_handler = sym exception_handler,
346}