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