Skip to main content

sidecar/arch/x86_64/
init.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Sidecar initialization code. This code runs once, on the BSP, before the
5//! main kernel boots.
6
7use super::AFTER_INIT;
8use super::CommandErrorWriter;
9use super::ENABLE_LOG;
10use super::VSM_CAPABILITIES;
11use super::VTL_RETURN_OFFSET;
12use super::VpGlobals;
13use super::addr_space;
14use super::temporary_map;
15use crate::arch::x86_64::get_hv_vp_register;
16use crate::arch::x86_64::hypercall;
17use crate::arch::x86_64::log;
18use arrayvec::ArrayVec;
19use core::fmt::Display;
20use core::fmt::Write;
21use core::hint::spin_loop;
22use core::mem::MaybeUninit;
23use core::ptr::addr_of;
24use core::ptr::addr_of_mut;
25use core::sync::atomic::AtomicU32;
26use core::sync::atomic::Ordering::Acquire;
27use core::sync::atomic::Ordering::Relaxed;
28use core::sync::atomic::Ordering::Release;
29use hvdef::HvError;
30use hvdef::HvRegisterVsmCodePageOffsets;
31use hvdef::HvX64RegisterName;
32use hvdef::HvX64SegmentRegister;
33use hvdef::HypercallCode;
34use hvdef::hypercall::EnableVpVtlX64;
35use hvdef::hypercall::HvInputVtl;
36use hvdef::hypercall::StartVirtualProcessorX64;
37use memory_range::AlignedSubranges;
38use memory_range::MemoryRange;
39use minimal_rt::arch::hypercall::HYPERCALL_PAGE;
40use minimal_rt::enlightened_panic;
41use sidecar_defs::ControlPage;
42use sidecar_defs::CpuStatus;
43use sidecar_defs::PAGE_SIZE;
44use sidecar_defs::PER_VP_PAGES;
45use sidecar_defs::PER_VP_SHMEM_PAGES;
46use sidecar_defs::SidecarNodeOutput;
47use sidecar_defs::SidecarNodeParams;
48use sidecar_defs::SidecarOutput;
49use sidecar_defs::SidecarParams;
50use sidecar_defs::required_memory;
51use x86defs::Exception;
52use x86defs::GdtEntry;
53use x86defs::IdtAttributes;
54use x86defs::IdtEntry64;
55use x86defs::Pte;
56use zerocopy::FromZeros;
57
58unsafe extern "C" {
59    static IMAGE_PDE: Pte;
60    fn irq_entry();
61    fn exc_gpf();
62    fn exc_pf();
63}
64
65static GDT: [GdtEntry; 4] = {
66    let default_data_attributes = x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.as_bits();
67    let default_code_attributes = x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.as_bits();
68    let zero = GdtEntry {
69        limit_low: 0,
70        base_low: 0,
71        base_middle: 0,
72        attr_low: 0,
73        attr_high: 0,
74        base_high: 0,
75    };
76
77    [
78        zero,
79        zero,
80        GdtEntry {
81            limit_low: 0xffff,
82            attr_low: default_code_attributes as u8,
83            attr_high: (default_code_attributes >> 8) as u8,
84            ..zero
85        },
86        GdtEntry {
87            limit_low: 0xffff,
88            attr_low: default_data_attributes as u8,
89            attr_high: (default_data_attributes >> 8) as u8,
90            ..zero
91        },
92    ]
93};
94
95const IRQ: u8 = 0x20;
96
97static mut IDT: [IdtEntry64; IRQ as usize + 1] = {
98    let zero = IdtEntry64 {
99        offset_low: 0,
100        selector: 0,
101        attributes: IdtAttributes::new(),
102        offset_middle: 0,
103        offset_high: 0,
104        reserved: 0,
105    };
106    [zero; IRQ as usize + 1]
107};
108
109enum InitError {
110    RequiredMemory { required: u64, actual: u64 },
111    GetVsmCodePageOffset(HvError),
112    GetVsmCapabilities(HvError),
113}
114
115impl Display for InitError {
116    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
117        match self {
118            InitError::RequiredMemory { required, actual } => {
119                write!(
120                    f,
121                    "failed to provide required memory: {:#x}, actual: {:#x}",
122                    required, actual
123                )
124            }
125            InitError::GetVsmCodePageOffset(err) => {
126                write!(f, "failed to get vsm code page offset: {err}")
127            }
128            InitError::GetVsmCapabilities(err) => {
129                write!(f, "failed to get vsm capabilities: {err}")
130            }
131        }
132    }
133}
134
135enum InitVpError {
136    EnableVtl2(HvError),
137    StartVp(HvError),
138}
139
140impl Display for InitVpError {
141    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142        match self {
143            InitVpError::EnableVtl2(err) => write!(f, "failed to enable vtl2: {err}"),
144            InitVpError::StartVp(err) => write!(f, "failed to start vp: {err}"),
145        }
146    }
147}
148
149/// BSP entry point from entry.S. Called with BSS, stack, and page tables
150/// initialized, and relocations applied.
151#[cfg_attr(not(minimal_rt), expect(dead_code))]
152pub extern "C" fn start(params: u64, output: u64) -> bool {
153    enlightened_panic::enable_enlightened_panic();
154
155    let [mut params_mapper, mut output_mapper, mut temp_mapper] = [0, 1, 2].map(|i| {
156        // SAFETY: no concurrent accessors to the same index.
157        unsafe { temporary_map::Mapper::new(i) }
158    });
159    // SAFETY: The page is not being concurrently accessed, and it has no
160    // invariant requirements.
161    let params = unsafe { params_mapper.map::<SidecarParams>(params) };
162    // SAFETY: The page is not being concurrently accessed, and it has no
163    // invariant requirements.
164    let mut output = unsafe { output_mapper.map::<SidecarOutput>(output) };
165    match init(&mut temp_mapper, &params, &mut output) {
166        Ok(()) => {
167            AFTER_INIT.store(true, Release);
168            true
169        }
170        Err(err) => {
171            let _ = write!(CommandErrorWriter(&mut output.error), "{err}");
172            false
173        }
174    }
175}
176
177/// Called on the BSP to initialize all the APs.
178fn init(
179    mapper: &mut temporary_map::Mapper,
180    params: &SidecarParams,
181    output: &mut SidecarOutput,
182) -> Result<(), InitError> {
183    let &SidecarParams {
184        hypercall_page,
185        enable_logging,
186        node_count,
187        ref nodes,
188        ref initial_state,
189    } = params;
190
191    ENABLE_LOG.store(enable_logging, Relaxed);
192    let nodes = &nodes[..node_count as usize];
193
194    // Copy the hypercall page locally since the main kernel will move it after
195    // this function returns.
196    {
197        // SAFETY: The page is not being concurrently accessed, and it has
198        // no invariant requirements.
199        let hypercall_page = unsafe { mapper.map::<[u8; 4096]>(hypercall_page) };
200        // SAFETY: no concurrent accessors to the page.
201        unsafe { (&raw mut HYPERCALL_PAGE).copy_from_nonoverlapping(&*hypercall_page, 1) };
202    }
203
204    // Initialize the IDT.
205    {
206        // SAFETY: no concurrent accessors.
207        let idt = unsafe { &mut *addr_of_mut!(IDT) };
208
209        let offset = exc_pf as *const () as u64;
210        idt[Exception::PAGE_FAULT.0 as usize] = IdtEntry64 {
211            offset_low: offset as u16,
212            selector: 2 * 8,
213            attributes: IdtAttributes::new().with_present(false).with_gate_type(0xf),
214            offset_middle: (offset >> 16) as u16,
215            offset_high: (offset >> 32) as u32,
216            reserved: 0,
217        };
218
219        let offset = exc_gpf as *const () as u64;
220        idt[Exception::GENERAL_PROTECTION_FAULT.0 as usize] = IdtEntry64 {
221            offset_low: offset as u16,
222            selector: 2 * 8,
223            attributes: IdtAttributes::new().with_present(false).with_gate_type(0xf),
224            offset_middle: (offset >> 16) as u16,
225            offset_high: (offset >> 32) as u32,
226            reserved: 0,
227        };
228
229        let offset = irq_entry as *const () as u64;
230        idt[IRQ as usize] = IdtEntry64 {
231            offset_low: offset as u16,
232            selector: 2 * 8,
233            attributes: IdtAttributes::new().with_present(true).with_gate_type(0xe),
234            offset_middle: (offset >> 16) as u16,
235            offset_high: (offset >> 32) as u32,
236            reserved: 0,
237        };
238    }
239
240    // Get the byte offset in the hypercall page of the VTL return function.
241    {
242        let value = HvRegisterVsmCodePageOffsets::from(
243            get_hv_vp_register(
244                HvInputVtl::CURRENT_VTL,
245                HvX64RegisterName::VsmCodePageOffsets.into(),
246            )
247            .map_err(InitError::GetVsmCodePageOffset)?
248            .as_u64(),
249        );
250        // SAFETY: no concurrent accessors.
251        unsafe { VTL_RETURN_OFFSET = value.return_offset() }
252    }
253
254    // Get the reported VSM capabilities.
255    {
256        let value = get_hv_vp_register(
257            HvInputVtl::CURRENT_VTL,
258            HvX64RegisterName::VsmCapabilities.into(),
259        )
260        .map_err(InitError::GetVsmCapabilities)?;
261        // SAFETY: no concurrent accessors.
262        unsafe { VSM_CAPABILITIES = value.as_u64().into() }
263    }
264
265    // SAFETY: no concurrent accesses yet.
266    let node_init = unsafe { &mut *addr_of_mut!(NODE_INIT) };
267
268    // Process each node, building the `node_init` array.
269    for (node_index, (node, node_output)) in nodes.iter().zip(&mut output.nodes).enumerate() {
270        let &SidecarNodeParams {
271            memory_base,
272            memory_size,
273            base_vp,
274            vp_count,
275        } = node;
276        let memory = MemoryRange::new(memory_base..memory_base + memory_size);
277
278        log!("node {node_index}: {vp_count} VPs starting at VP {base_vp}, memory {memory}");
279
280        let required = required_memory(vp_count) as u64;
281        if memory_size < required {
282            return Err(InitError::RequiredMemory {
283                required,
284                actual: memory_size,
285            });
286        }
287
288        let (control_page_range, memory) = memory.split_at_offset(PAGE_SIZE as u64);
289        let (shmem_pages, memory) =
290            memory.split_at_offset(vp_count as u64 * PER_VP_SHMEM_PAGES as u64 * PAGE_SIZE as u64);
291
292        *node_output = SidecarNodeOutput {
293            control_page: control_page_range.start(),
294            shmem_pages_base: shmem_pages.start(),
295            shmem_pages_size: shmem_pages.len(),
296        };
297
298        // Initialize the control page.
299        {
300            // SAFETY: The page is not being concurrently accessed, and it has
301            // no invariant requirements.
302            let mut control = unsafe { mapper.map::<ControlPage>(control_page_range.start()) };
303            let ControlPage {
304                index,
305                base_cpu,
306                cpu_count,
307                request_vector,
308                response_cpu,
309                response_vector,
310                needs_attention,
311                reserved,
312                cpu_status,
313            } = &mut *control;
314            *index = (node_index as u32).into();
315            *base_cpu = base_vp.into();
316            *cpu_count = vp_count.into();
317            *request_vector = (IRQ as u32).into();
318            *response_cpu = 0.into();
319            *response_vector = 0.into();
320            *needs_attention = 0.into();
321            reserved.fill(0);
322            // Default: base VP -> REMOVED (kernel starts it), other VPs -> RUN,
323            // beyond vp_count -> REMOVED.
324            cpu_status[0] = CpuStatus::REMOVED.0.into();
325            cpu_status[1..vp_count as usize].fill_with(|| CpuStatus::RUN.0.into());
326            cpu_status[vp_count as usize..].fill_with(|| CpuStatus::REMOVED.0.into());
327
328            // Apply per-CPU overrides from openhcl_boot when restoring from
329            // servicing with outstanding IO. CPUs marked false in
330            // sidecar_starts_cpu are set to REMOVED so the kernel starts them
331            // directly for immediate interrupt handling.
332            if initial_state.per_cpu_state_specified {
333                log!(
334                    "node {node_index}: applying per-cpu overrides, base_vp={base_vp}, vp_count={vp_count}"
335                );
336                let overrides = &initial_state.sidecar_starts_cpu
337                    [base_vp as usize..(base_vp + vp_count) as usize];
338                for (i, &should_start) in overrides.iter().enumerate() {
339                    cpu_status[i] = if should_start {
340                        CpuStatus::RUN.0.into()
341                    } else {
342                        let vp = base_vp + i as u32;
343                        log!("node {node_index}: VP {vp} (idx {i}) -> REMOVED");
344                        CpuStatus::REMOVED.0.into()
345                    };
346                }
347            }
348
349            // Sidecar must never be run for a node whose application processors
350            // are all kernel-started (REMOVED). Advertising one to the Linux
351            // sidecar driver via the device tree while its VPs are also
352            // kernel-started via `boot_cpus=` makes the driver and the
353            // kernel-start path contend over the same VP and hang AP bring-up.
354            // If one ever reaches sidecar, fail loudly here.
355            if !cpu_status[1..vp_count as usize]
356                .iter()
357                .any(|status| status.load(Relaxed) == CpuStatus::RUN.0)
358            {
359                panic!(
360                    "sidecar node {node_index} (base_vp={base_vp}, vp_count={vp_count}) has no sidecar-started APs"
361                );
362            }
363        }
364
365        node_init.push(NodeInit {
366            node: NodeDefinition {
367                base_vp,
368                vp_count,
369                control_page_pa: control_page_range.start(),
370                shmem_pages,
371                memory,
372            },
373            next_vp: AtomicU32::new(1), // skip the base VP in each node
374        });
375    }
376
377    // Downgrade the node init array to immutable, then start booting the APs.
378    // Each AP that boots will then start helping boot additional APs.
379    //
380    // SAFETY: no concurrent mutators.
381    let node_init = unsafe { &*addr_of!(NODE_INIT) };
382    start_aps(node_init, mapper);
383
384    // Wait for all the APs to finish starting.
385    {
386        for (node, output) in nodes.iter().zip(&output.nodes) {
387            // SAFETY: The page is not being concurrently accessed, and it has
388            // no invariant requirements.
389            let control = unsafe { mapper.map::<ControlPage>(output.control_page) };
390            for status in &control.cpu_status[0..node.vp_count as usize] {
391                while status.load(Acquire) == CpuStatus::RUN.0 {
392                    spin_loop();
393                }
394            }
395        }
396    }
397
398    Ok(())
399}
400
401struct NodeInit {
402    node: NodeDefinition,
403    next_vp: AtomicU32,
404}
405
406static mut NODE_INIT: ArrayVec<NodeInit, { sidecar_defs::MAX_NODES }> = ArrayVec::new_const();
407
408fn start_aps(node_init: &[NodeInit], mapper: &mut temporary_map::Mapper) {
409    for node in node_init {
410        loop {
411            let node_cpu_index = node.next_vp.fetch_add(1, Relaxed);
412            assert!(node_cpu_index != u32::MAX);
413            if node_cpu_index >= node.node.vp_count {
414                break;
415            }
416
417            // Read this VP's status from the node's control page.
418            // The mapping is scoped so the mapper is free for start().
419            let is_removed = {
420                // SAFETY: control page was initialized; no concurrent mutation yet.
421                let control = unsafe { mapper.map::<ControlPage>(node.node.control_page_pa) };
422                control.cpu_status[node_cpu_index as usize].load(Relaxed) == CpuStatus::REMOVED.0
423            };
424
425            let vp = node.node.base_vp + node_cpu_index;
426            if is_removed {
427                log!("start_aps: skipping VP {vp} (idx {node_cpu_index}): REMOVED");
428                continue;
429            }
430
431            match node.node.start(mapper, node_cpu_index) {
432                Ok(()) => {}
433                Err(err) => panic!("failed to start VP {vp}: {err}"),
434            }
435        }
436    }
437}
438
439/// # Safety
440/// Must be called as an AP entry point.
441unsafe fn ap_init() -> ! {
442    // Start any other pending APs.
443    {
444        // SAFETY: `NODE_INIT` is set before this routine is called.
445        let node_init = unsafe { &*addr_of!(NODE_INIT) };
446        // SAFETY: nothing else on this CPU is using the temporary map.
447        let mut mapper = unsafe { temporary_map::Mapper::new(0) };
448        start_aps(node_init, &mut mapper)
449    }
450    // SAFETY: this is an entry point.
451    unsafe { super::vp::ap_entry() }
452}
453
454struct NodeDefinition {
455    base_vp: u32,
456    vp_count: u32,
457    control_page_pa: u64,
458    shmem_pages: MemoryRange,
459    memory: MemoryRange,
460}
461
462impl NodeDefinition {
463    fn start(
464        &self,
465        mapper: &mut temporary_map::Mapper,
466        node_cpu_index: u32,
467    ) -> Result<(), InitVpError> {
468        let hv_vp_index = self.base_vp + node_cpu_index;
469
470        let shmem_pages = self.shmem_pages.start()
471            + node_cpu_index as u64 * PER_VP_SHMEM_PAGES as u64 * PAGE_SIZE as u64;
472        let command_page_pa = shmem_pages;
473        let reg_page_pa = shmem_pages + PAGE_SIZE as u64;
474        let memory_start =
475            self.memory.start() + node_cpu_index as u64 * PER_VP_PAGES as u64 * PAGE_SIZE as u64;
476        let memory =
477            MemoryRange::new(memory_start..memory_start + PER_VP_PAGES as u64 * PAGE_SIZE as u64);
478
479        let mut memory = AlignedSubranges::new(memory)
480            .with_max_range_len(PAGE_SIZE as u64)
481            .map(|r| r.start());
482        let pml4_pa = memory.next().unwrap();
483        let pdpt_pa = memory.next().unwrap();
484        let pd_pa = memory.next().unwrap();
485        let pt_pa = memory.next().unwrap();
486
487        let pte_table = |addr| {
488            Pte::new()
489                .with_address(addr)
490                .with_read_write(true)
491                .with_present(true)
492        };
493
494        {
495            // SAFETY: The page is not being concurrently accessed, and it has no
496            // invariant requirements.
497            let mut pml4 = unsafe { mapper.map::<[Pte; 512]>(pml4_pa) };
498            pml4[511] = pte_table(pdpt_pa);
499        }
500        {
501            // SAFETY: The page is not being concurrently accessed, and it has no
502            // invariant requirements.
503            let mut pdpt = unsafe { mapper.map::<Pte>(pdpt_pa) };
504            *pdpt = pte_table(pd_pa);
505        }
506        {
507            // SAFETY: The page is not being concurrently accessed, and it has no
508            // invariant requirements.
509            let mut pd = unsafe { mapper.map::<[Pte; 512]>(pd_pa) };
510            // SAFETY: the PTE is not being concurrently modified.
511            pd[0] = unsafe { IMAGE_PDE };
512            pd[1] = pte_table(pt_pa);
513        }
514        let globals_pa = {
515            // SAFETY: The page is not being concurrently accessed, and it has no
516            // invariant requirements.
517            let mut pt = unsafe { mapper.map::<[Pte; 512]>(pt_pa) };
518            addr_space::init_ap(
519                &mut pt,
520                pt_pa,
521                self.control_page_pa,
522                command_page_pa,
523                reg_page_pa,
524                &mut memory,
525            )
526        };
527        {
528            // SAFETY: The page is not being concurrently accessed, and it has no
529            // invariant requirements.
530            let mut globals = unsafe { mapper.map::<MaybeUninit<VpGlobals>>(globals_pa) };
531            globals.write(VpGlobals {
532                hv_vp_index,
533                node_cpu_index,
534                overlays_mapped: false,
535                register_page_mapped: false,
536            });
537        }
538
539        let cs = HvX64SegmentRegister {
540            base: 0,
541            limit: !0,
542            selector: 2 * 8,
543            attributes: x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.into(),
544        };
545        let ds = HvX64SegmentRegister {
546            base: 0,
547            limit: !0,
548            selector: 3 * 8,
549            attributes: x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.into(),
550        };
551        let gdtr = hvdef::HvX64TableRegister {
552            base: addr_of!(GDT) as u64,
553            limit: size_of_val(&GDT) as u16 - 1,
554            pad: [0; 3],
555        };
556        let idtr = hvdef::HvX64TableRegister {
557            base: addr_of!(IDT) as u64,
558            // SAFETY: just getting the size
559            limit: size_of_val(unsafe { &*addr_of!(IDT) }) as u16 - 1,
560            pad: [0; 3],
561        };
562        let context = hvdef::hypercall::InitialVpContextX64 {
563            rip: ap_init as *const () as u64,
564            rsp: addr_space::stack().end() - 8, // start unaligned to match calling convention
565            rflags: x86defs::RFlags::at_reset().into(),
566            cs,
567            ds,
568            es: ds,
569            fs: ds,
570            gs: ds,
571            ss: ds,
572            tr: HvX64SegmentRegister {
573                base: 0,
574                limit: 0xffff,
575                selector: 0,
576                attributes: x86defs::X64_BUSY_TSS_SEGMENT_ATTRIBUTES.into(),
577            },
578            ldtr: FromZeros::new_zeroed(),
579            idtr,
580            gdtr,
581            efer: x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
582            cr0: x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE,
583            cr3: pml4_pa,
584            cr4: x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE | x86defs::X64_CR4_FXSR,
585            msr_cr_pat: x86defs::X86X_MSR_DEFAULT_PAT,
586        };
587
588        {
589            // SAFETY: no concurrent accessors.
590            let input_page = unsafe { &mut *addr_space::hypercall_input().cast() };
591            let EnableVpVtlX64 {
592                partition_id,
593                vp_index,
594                target_vtl,
595                reserved,
596                vp_vtl_context,
597            } = input_page;
598
599            *partition_id = hvdef::HV_PARTITION_ID_SELF;
600            *vp_index = hv_vp_index;
601            *target_vtl = hvdef::Vtl::Vtl2.into();
602            *vp_vtl_context = context;
603            *reserved = [0; 3];
604        }
605        match hypercall(HypercallCode::HvCallEnableVpVtl, 0) {
606            Ok(()) | Err(HvError::VtlAlreadyEnabled) => {}
607            Err(err) => return Err(InitVpError::EnableVtl2(err)),
608        }
609
610        {
611            // SAFETY: no concurrent accessors.
612            let input_page = unsafe { &mut *addr_space::hypercall_input().cast() };
613            let StartVirtualProcessorX64 {
614                partition_id,
615                vp_index,
616                target_vtl,
617                rsvd0,
618                rsvd1,
619                vp_context,
620            } = input_page;
621
622            *partition_id = hvdef::HV_PARTITION_ID_SELF;
623            *vp_index = hv_vp_index;
624            *target_vtl = hvdef::Vtl::Vtl2.into();
625            *rsvd0 = 0;
626            *rsvd1 = 0;
627            *vp_context = context;
628        }
629        hypercall(HypercallCode::HvCallStartVirtualProcessor, 0).map_err(InitVpError::StartVp)?;
630
631        Ok(())
632    }
633}