1use crate::cmdline::SidecarOptions;
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 num_cpus: usize,
30 pub per_cpu_state: &'a sidecar_defs::PerCpuState,
31 pub node_params: &'a [SidecarNodeParams],
32 pub nodes: &'a [SidecarNodeOutput],
33 pub start_reftime: u64,
34 pub end_reftime: u64,
35}
36
37impl SidecarConfig<'_> {
38 pub fn kernel_command_line(&self) -> SidecarKernelCommandLine<'_> {
41 SidecarKernelCommandLine(self)
42 }
43}
44
45pub struct SidecarKernelCommandLine<'a>(&'a SidecarConfig<'a>);
46
47impl core::fmt::Display for SidecarKernelCommandLine<'_> {
48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49 f.write_str("boot_cpus=")?;
55 let mut comma = "";
56 if self.0.per_cpu_state.per_cpu_state_specified {
57 for (i, &starts) in self.0.per_cpu_state.sidecar_starts_cpu[..self.0.num_cpus]
58 .iter()
59 .enumerate()
60 {
61 if !starts {
62 write!(f, "{comma}{i}")?;
63 comma = ",";
64 }
65 }
66 } else {
67 for node in self.0.node_params {
68 write!(f, "{comma}{}", node.base_vp)?;
69 comma = ",";
70 }
71 }
72 Ok(())
73 }
74}
75
76fn sidecar_node_is_empty(
83 overrides: &sidecar_defs::PerCpuState,
84 base_vp: usize,
85 size: usize,
86) -> bool {
87 if !overrides.per_cpu_state_specified {
89 return size == 1;
90 }
91 !(1..size).any(|i| overrides.sidecar_starts_cpu[base_vp + i])
93}
94
95pub fn start_sidecar<'a>(
96 p: &ShimParams,
97 partition_info: &PartitionInfo,
98 address_space: &mut AddressSpaceManager,
99 sidecar_params: &'a mut SidecarParams,
100 sidecar_output: &'a mut SidecarOutput,
101) -> Option<SidecarConfig<'a>> {
102 if !cfg!(target_arch = "x86_64") || p.isolation_type != IsolationType::None {
103 return None;
104 }
105
106 if p.sidecar_size == 0 {
107 log::info!("sidecar: not present in image");
108 return None;
109 }
110
111 match partition_info.boot_options.sidecar {
112 SidecarOptions::DisabledCommandLine => {
113 log::info!("sidecar: disabled via command line");
114 return None;
115 }
116 SidecarOptions::DisabledPerCpuStateCapacity => {
117 log::info!(
118 "sidecar: disabled because servicing per-CPU overrides exceed state capacity"
119 );
120 return None;
121 }
122 SidecarOptions::Enabled { enable_logging, .. } => {
123 sidecar_params.enable_logging = enable_logging;
124 }
125 }
126
127 let max_vnode = partition_info
129 .cpus
130 .iter()
131 .map(|cpu| cpu.vnode)
132 .chain(partition_info.vtl2_ram.iter().map(|e| e.vnode))
133 .max()
134 .unwrap();
135
136 if max_vnode >= MAX_NUMA_NODES as u32 {
137 log::warn!("sidecar: NUMA node {max_vnode} too large");
138 return None;
139 }
140
141 #[cfg(target_arch = "x86_64")]
142 if !x86defs::cpuid::VersionAndFeaturesEcx::from(
143 safe_intrinsics::cpuid(x86defs::cpuid::CpuidFunction::VersionAndFeatures.0, 0).ecx,
144 )
145 .x2_apic()
146 {
147 log::warn!("sidecar: x2apic not available; not using sidecar");
149 return None;
150 }
151
152 let cpus_by_node = || {
155 partition_info
156 .cpus
157 .chunk_by(|a, b| a.vnode == b.vnode)
158 .flat_map(|cpus| {
159 let chunks = cpus.len().div_ceil(MAX_SIDECAR_NODE_SIZE);
160 cpus.chunks(cpus.len().div_ceil(chunks))
161 })
162 };
163 if cpus_by_node().all(|cpus_by_node| cpus_by_node.len() == 1) {
164 log::info!("sidecar: all NUMA nodes have one CPU");
165 return None;
166 }
167 let mut total_ram;
168 {
169 let SidecarParams {
170 hypercall_page,
171 enable_logging: _,
172 node_count,
173 nodes,
174 initial_state,
175 } = sidecar_params;
176
177 *hypercall_page = 0;
178 #[cfg(target_arch = "x86_64")]
179 {
180 *hypercall_page = crate::hypercall::hvcall().hypercall_page();
181 }
182
183 let mut base_vp = 0;
184 total_ram = 0;
185 *node_count = 0;
186 *initial_state = partition_info.sidecar_cpu_overrides.clone();
187 for cpus in cpus_by_node() {
188 if sidecar_node_is_empty(
191 &partition_info.sidecar_cpu_overrides,
192 base_vp as usize,
193 cpus.len(),
194 ) {
195 if initial_state.per_cpu_state_specified {
196 initial_state.sidecar_starts_cpu[base_vp as usize] = false;
199 }
200 log::info!(
201 "sidecar: node at base VP {base_vp} ({} VPs) has no sidecar-started APs; kernel-starting all of them",
202 cpus.len(),
203 );
204 base_vp += cpus.len() as u32;
205 continue;
206 }
207
208 let required_ram = sidecar_defs::required_memory(cpus.len() as u32) as u64;
209 let local_vnode = cpus[0].vnode as usize;
212
213 let mem = match address_space.allocate(
214 Some(local_vnode as u32),
215 required_ram,
216 AllocationType::SidecarNode,
217 AllocationPolicy::LowMemory,
218 ) {
219 Some(mem) => mem,
220 None => {
221 match address_space.allocate(
223 None,
224 required_ram,
225 AllocationType::SidecarNode,
226 AllocationPolicy::LowMemory,
227 ) {
228 Some(mem) => {
229 log::warn!(
230 "sidecar: unable to allocate memory for sidecar node on node {local_vnode}, falling back to node {}",
231 mem.vnode
232 );
233 mem
234 }
235 None => {
236 log::warn!("sidecar: not enough memory for sidecar");
237 return None;
238 }
239 }
240 }
241 };
242
243 nodes[*node_count as usize] = SidecarNodeParams {
244 memory_base: mem.range.start(),
245 memory_size: mem.range.len(),
246 base_vp,
247 vp_count: cpus.len() as u32,
248 };
249 log::info!(
250 "sidecar: created node index={} base_vp={base_vp} vp_count={} vnode={local_vnode}",
251 *node_count,
252 cpus.len(),
253 );
254 if initial_state.per_cpu_state_specified {
255 initial_state.sidecar_starts_cpu[base_vp as usize] = false;
260 log::info!(
261 "sidecar: per_cpu_state_specified=true, marking base_vp={} as kernel-started",
262 base_vp
263 );
264 }
265 base_vp += cpus.len() as u32;
266 *node_count += 1;
267 total_ram += required_ram;
268 }
269 }
270
271 let node_count = sidecar_params.node_count as usize;
274 if node_count == 0 {
275 log::info!("sidecar: no nodes have sidecar-started APs; disabling sidecar");
276 return None;
277 }
278
279 let sidecar_entry: extern "C" fn(&SidecarParams, &mut SidecarOutput) -> bool =
281 unsafe { core::mem::transmute(p.sidecar_entry_address) };
282
283 let boot_start_reftime = minimal_rt::reftime::reference_time();
284 log::info!(
285 "sidecar starting, {} nodes, {} cpus, {:#x} total bytes",
286 node_count,
287 partition_info.cpus.len(),
288 total_ram
289 );
290 if !sidecar_entry(sidecar_params, sidecar_output) {
291 panic!(
292 "failed to start sidecar: {}",
293 core::str::from_utf8(&sidecar_output.error.buf[..sidecar_output.error.len as usize])
294 .unwrap()
295 );
296 }
297 let boot_end_reftime = minimal_rt::reftime::reference_time();
298
299 let SidecarOutput { nodes, error: _ } = sidecar_output;
300 let config = SidecarConfig {
301 num_cpus: partition_info.cpus.len(),
302 start_reftime: boot_start_reftime,
303 end_reftime: boot_end_reftime,
304 node_params: &sidecar_params.nodes[..node_count],
305 nodes: &nodes[..node_count],
306 per_cpu_state: &sidecar_params.initial_state,
307 };
308 log::info!(
309 "sidecar: boot_cpus parameter: {}",
310 config.kernel_command_line()
311 );
312 Some(config)
313}