1use crate::boot_logger::log;
5use crate::host_params::MAX_CPU_COUNT;
6use crate::host_params::MAX_NUMA_NODES;
7use crate::host_params::PartitionInfo;
8use crate::host_params::shim_params::IsolationType;
9use crate::host_params::shim_params::ShimParams;
10use crate::memory::AddressSpaceManager;
11use crate::memory::AllocationPolicy;
12use crate::memory::AllocationType;
13use sidecar_defs::SidecarNodeOutput;
14use sidecar_defs::SidecarNodeParams;
15use sidecar_defs::SidecarOutput;
16use sidecar_defs::SidecarParams;
17
18const MAX_SIDECAR_NODE_SIZE: usize = 32;
21
22const _: () = assert!(
25 sidecar_defs::MAX_NODES >= (MAX_NUMA_NODES - 1) + MAX_CPU_COUNT.div_ceil(MAX_SIDECAR_NODE_SIZE)
26);
27
28pub struct SidecarConfig<'a> {
29 pub node_params: &'a [SidecarNodeParams],
30 pub nodes: &'a [SidecarNodeOutput],
31 pub start_reftime: u64,
32 pub end_reftime: u64,
33}
34
35impl SidecarConfig<'_> {
36 pub fn kernel_command_line(&self) -> SidecarKernelCommandLine<'_> {
39 SidecarKernelCommandLine(self)
40 }
41}
42
43pub struct SidecarKernelCommandLine<'a>(&'a SidecarConfig<'a>);
44
45impl core::fmt::Display for SidecarKernelCommandLine<'_> {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 f.write_str("boot_cpus=")?;
51 let mut comma = "";
52 for node in self.0.node_params {
53 write!(f, "{}{}", comma, node.base_vp)?;
54 comma = ",";
55 }
56 Ok(())
57 }
58}
59
60pub fn start_sidecar<'a>(
61 p: &ShimParams,
62 partition_info: &PartitionInfo,
63 address_space: &mut AddressSpaceManager,
64 sidecar_params: &'a mut SidecarParams,
65 sidecar_output: &'a mut SidecarOutput,
66) -> Option<SidecarConfig<'a>> {
67 if !cfg!(target_arch = "x86_64") || p.isolation_type != IsolationType::None {
68 return None;
69 }
70
71 if p.sidecar_size == 0 {
72 log!("sidecar: not present in image");
73 return None;
74 }
75
76 if !partition_info.boot_options.sidecar {
77 log!("sidecar: disabled via command line");
78 return None;
79 }
80
81 let max_vnode = partition_info
83 .cpus
84 .iter()
85 .map(|cpu| cpu.vnode)
86 .chain(partition_info.vtl2_ram.iter().map(|e| e.vnode))
87 .max()
88 .unwrap();
89
90 if max_vnode >= MAX_NUMA_NODES as u32 {
91 log!("sidecar: NUMA node {max_vnode} too large");
92 return None;
93 }
94
95 #[cfg(target_arch = "x86_64")]
96 if !x86defs::cpuid::VersionAndFeaturesEcx::from(
97 safe_intrinsics::cpuid(x86defs::cpuid::CpuidFunction::VersionAndFeatures.0, 0).ecx,
98 )
99 .x2_apic()
100 {
101 log!("sidecar: x2apic not available; not using sidecar");
103 return None;
104 }
105
106 let cpus_by_node = || {
109 partition_info
110 .cpus
111 .chunk_by(|a, b| a.vnode == b.vnode)
112 .flat_map(|cpus| {
113 let chunks = cpus.len().div_ceil(MAX_SIDECAR_NODE_SIZE);
114 cpus.chunks(cpus.len().div_ceil(chunks))
115 })
116 };
117 if cpus_by_node().all(|cpus_by_node| cpus_by_node.len() == 1) {
118 log!("sidecar: all NUMA nodes have one CPU");
119 return None;
120 }
121 let node_count = cpus_by_node().count();
122
123 let mut total_ram;
124 {
125 let SidecarParams {
126 hypercall_page,
127 enable_logging,
128 node_count,
129 nodes,
130 } = sidecar_params;
131
132 *hypercall_page = 0;
133 #[cfg(target_arch = "x86_64")]
134 {
135 *hypercall_page = crate::hypercall::hvcall().hypercall_page();
136 }
137 *enable_logging = partition_info.boot_options.sidecar_logging;
138
139 let mut base_vp = 0;
140 total_ram = 0;
141 for (cpus, node) in cpus_by_node().zip(nodes) {
142 let required_ram = sidecar_defs::required_memory(cpus.len() as u32) as u64;
143 let local_vnode = cpus[0].vnode as usize;
146
147 let mem = match address_space.allocate(
148 Some(local_vnode as u32),
149 required_ram,
150 AllocationType::SidecarNode,
151 AllocationPolicy::LowMemory,
152 ) {
153 Some(mem) => mem,
154 None => {
155 match address_space.allocate(
157 None,
158 required_ram,
159 AllocationType::SidecarNode,
160 AllocationPolicy::LowMemory,
161 ) {
162 Some(mem) => {
163 log!(
164 "sidecar: unable to allocate memory for sidecar node on node {local_vnode}, falling back to node {}",
165 mem.vnode
166 );
167 mem
168 }
169 None => {
170 log!("sidecar: not enough memory for sidecar");
171 return None;
172 }
173 }
174 }
175 };
176
177 *node = SidecarNodeParams {
178 memory_base: mem.range.start(),
179 memory_size: mem.range.len(),
180 base_vp,
181 vp_count: cpus.len() as u32,
182 };
183 base_vp += cpus.len() as u32;
184 *node_count += 1;
185 total_ram += required_ram;
186 }
187 }
188
189 let sidecar_entry: extern "C" fn(&SidecarParams, &mut SidecarOutput) -> bool =
191 unsafe { core::mem::transmute(p.sidecar_entry_address) };
192
193 let boot_start_reftime = minimal_rt::reftime::reference_time();
194 log!(
195 "sidecar starting, {} nodes, {} cpus, {:#x} total bytes",
196 node_count,
197 partition_info.cpus.len(),
198 total_ram
199 );
200 if !sidecar_entry(sidecar_params, sidecar_output) {
201 panic!(
202 "failed to start sidecar: {}",
203 core::str::from_utf8(&sidecar_output.error.buf[..sidecar_output.error.len as usize])
204 .unwrap()
205 );
206 }
207 let boot_end_reftime = minimal_rt::reftime::reference_time();
208
209 let SidecarOutput { nodes, error: _ } = sidecar_output;
210 Some(SidecarConfig {
211 start_reftime: boot_start_reftime,
212 end_reftime: boot_end_reftime,
213 node_params: &sidecar_params.nodes[..node_count],
214 nodes: &nodes[..node_count],
215 })
216}