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::DisabledServicing => {
117 log::info!("sidecar: disabled because this is a servicing restore");
118 return None;
119 }
120 SidecarOptions::Enabled { enable_logging, .. } => {
121 sidecar_params.enable_logging = enable_logging;
122 }
123 }
124
125 let max_vnode = partition_info
127 .cpus
128 .iter()
129 .map(|cpu| cpu.vnode)
130 .chain(partition_info.vtl2_ram.iter().map(|e| e.vnode))
131 .max()
132 .unwrap();
133
134 if max_vnode >= MAX_NUMA_NODES as u32 {
135 log::warn!("sidecar: NUMA node {max_vnode} too large");
136 return None;
137 }
138
139 #[cfg(target_arch = "x86_64")]
140 if !x86defs::cpuid::VersionAndFeaturesEcx::from(
141 safe_intrinsics::cpuid(x86defs::cpuid::CpuidFunction::VersionAndFeatures.0, 0).ecx,
142 )
143 .x2_apic()
144 {
145 log::warn!("sidecar: x2apic not available; not using sidecar");
147 return None;
148 }
149
150 let cpus_by_node = || {
153 partition_info
154 .cpus
155 .chunk_by(|a, b| a.vnode == b.vnode)
156 .flat_map(|cpus| {
157 let chunks = cpus.len().div_ceil(MAX_SIDECAR_NODE_SIZE);
158 cpus.chunks(cpus.len().div_ceil(chunks))
159 })
160 };
161 if cpus_by_node().all(|cpus_by_node| cpus_by_node.len() == 1) {
162 log::info!("sidecar: all NUMA nodes have one CPU");
163 return None;
164 }
165 let mut total_ram;
166 {
167 let SidecarParams {
168 hypercall_page,
169 enable_logging: _,
170 node_count,
171 nodes,
172 initial_state,
173 } = sidecar_params;
174
175 *hypercall_page = 0;
176 #[cfg(target_arch = "x86_64")]
177 {
178 *hypercall_page = crate::hypercall::hvcall().hypercall_page();
179 }
180
181 let mut base_vp = 0;
182 total_ram = 0;
183 *node_count = 0;
184 *initial_state = partition_info.sidecar_cpu_overrides.clone();
185 for cpus in cpus_by_node() {
186 if sidecar_node_is_empty(
189 &partition_info.sidecar_cpu_overrides,
190 base_vp as usize,
191 cpus.len(),
192 ) {
193 if initial_state.per_cpu_state_specified {
194 initial_state.sidecar_starts_cpu[base_vp as usize] = false;
197 }
198 log::info!(
199 "sidecar: node at base VP {base_vp} ({} VPs) has no sidecar-started APs; kernel-starting all of them",
200 cpus.len(),
201 );
202 base_vp += cpus.len() as u32;
203 continue;
204 }
205
206 let required_ram = sidecar_defs::required_memory(cpus.len() as u32) as u64;
207 let local_vnode = cpus[0].vnode as usize;
210
211 let mem = match address_space.allocate(
212 Some(local_vnode as u32),
213 required_ram,
214 AllocationType::SidecarNode,
215 AllocationPolicy::LowMemory,
216 ) {
217 Some(mem) => mem,
218 None => {
219 match address_space.allocate(
221 None,
222 required_ram,
223 AllocationType::SidecarNode,
224 AllocationPolicy::LowMemory,
225 ) {
226 Some(mem) => {
227 log::warn!(
228 "sidecar: unable to allocate memory for sidecar node on node {local_vnode}, falling back to node {}",
229 mem.vnode
230 );
231 mem
232 }
233 None => {
234 log::warn!("sidecar: not enough memory for sidecar");
235 return None;
236 }
237 }
238 }
239 };
240
241 nodes[*node_count as usize] = SidecarNodeParams {
242 memory_base: mem.range.start(),
243 memory_size: mem.range.len(),
244 base_vp,
245 vp_count: cpus.len() as u32,
246 };
247 if initial_state.per_cpu_state_specified {
248 initial_state.sidecar_starts_cpu[base_vp as usize] = false;
253 log::info!(
254 "sidecar: per_cpu_state_specified=true, marking base_vp={} as kernel-started",
255 base_vp
256 );
257 }
258 base_vp += cpus.len() as u32;
259 *node_count += 1;
260 total_ram += required_ram;
261 }
262 }
263
264 let node_count = sidecar_params.node_count as usize;
267 if node_count == 0 {
268 log::info!("sidecar: no nodes have sidecar-started APs; disabling sidecar");
269 return None;
270 }
271
272 let sidecar_entry: extern "C" fn(&SidecarParams, &mut SidecarOutput) -> bool =
274 unsafe { core::mem::transmute(p.sidecar_entry_address) };
275
276 let boot_start_reftime = minimal_rt::reftime::reference_time();
277 log::info!(
278 "sidecar starting, {} nodes, {} cpus, {:#x} total bytes",
279 node_count,
280 partition_info.cpus.len(),
281 total_ram
282 );
283 if !sidecar_entry(sidecar_params, sidecar_output) {
284 panic!(
285 "failed to start sidecar: {}",
286 core::str::from_utf8(&sidecar_output.error.buf[..sidecar_output.error.len as usize])
287 .unwrap()
288 );
289 }
290 let boot_end_reftime = minimal_rt::reftime::reference_time();
291
292 let SidecarOutput { nodes, error: _ } = sidecar_output;
293 Some(SidecarConfig {
294 num_cpus: partition_info.cpus.len(),
295 start_reftime: boot_start_reftime,
296 end_reftime: boot_end_reftime,
297 node_params: &sidecar_params.nodes[..node_count],
298 nodes: &nodes[..node_count],
299 per_cpu_state: &sidecar_params.initial_state,
300 })
301}