1 // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // SPDX-License-Identifier: Apache-2.0
3 //
4 // Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5 // Use of this source code is governed by a BSD-style license that can be
6 // found in the LICENSE-BSD-3-Clause file.
7
8 use std::{mem, result, slice};
9
10 use libc::c_uchar;
11 use thiserror::Error;
12 use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
13
14 use crate::layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
15 use crate::x86_64::{get_x2apic_id, mpspec};
16 use crate::GuestMemoryMmap;
17
18 // This is a workaround to the Rust enforcement specifying that any implementation of a foreign
19 // trait (in this case `ByteValued`) where:
20 // * the type that is implementing the trait is foreign or
21 // * all of the parameters being passed to the trait (if there are any) are also foreign
22 // is prohibited.
23 #[derive(Copy, Clone, Default)]
24 struct MpcBusWrapper(mpspec::mpc_bus);
25 #[derive(Copy, Clone, Default)]
26 struct MpcCpuWrapper(mpspec::mpc_cpu);
27 #[derive(Copy, Clone, Default)]
28 struct MpcIntsrcWrapper(mpspec::mpc_intsrc);
29 #[derive(Copy, Clone, Default)]
30 struct MpcIoapicWrapper(mpspec::mpc_ioapic);
31 #[derive(Copy, Clone, Default)]
32 struct MpcTableWrapper(mpspec::mpc_table);
33 #[derive(Copy, Clone, Default)]
34 struct MpcLintsrcWrapper(mpspec::mpc_lintsrc);
35 #[derive(Copy, Clone, Default)]
36 struct MpfIntelWrapper(mpspec::mpf_intel);
37
38 // SAFETY: These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
39 unsafe impl ByteValued for MpcBusWrapper {}
40 // SAFETY: see above
41 unsafe impl ByteValued for MpcCpuWrapper {}
42 // SAFETY: see above
43 unsafe impl ByteValued for MpcIntsrcWrapper {}
44 // SAFETY: see above
45 unsafe impl ByteValued for MpcIoapicWrapper {}
46 // SAFETY: see above
47 unsafe impl ByteValued for MpcTableWrapper {}
48 // SAFETY: see above
49 unsafe impl ByteValued for MpcLintsrcWrapper {}
50 // SAFETY: see above
51 unsafe impl ByteValued for MpfIntelWrapper {}
52
53 #[derive(Debug, Error)]
54 pub enum Error {
55 /// There was too little guest memory to store the entire MP table.
56 #[error("There was too little guest memory to store the entire MP table")]
57 NotEnoughMemory,
58 /// The MP table has too little address space to be stored.
59 #[error("The MP table has too little address space to be stored")]
60 AddressOverflow,
61 /// Failure while zeroing out the memory for the MP table.
62 #[error("Failure while zeroing out the memory for the MP table")]
63 Clear(#[source] GuestMemoryError),
64 /// Number of CPUs exceeds the maximum supported CPUs
65 #[error("Number of CPUs exceeds the maximum supported CPUs")]
66 TooManyCpus,
67 /// Failure to write the MP floating pointer.
68 #[error("Failure to write the MP floating pointer")]
69 WriteMpfIntel(#[source] GuestMemoryError),
70 /// Failure to write MP CPU entry.
71 #[error("Failure to write MP CPU entry")]
72 WriteMpcCpu(#[source] GuestMemoryError),
73 /// Failure to write MP ioapic entry.
74 #[error("Failure to write MP ioapic entry")]
75 WriteMpcIoapic(#[source] GuestMemoryError),
76 /// Failure to write MP bus entry.
77 #[error("Failure to write MP bus entry")]
78 WriteMpcBus(#[source] GuestMemoryError),
79 /// Failure to write MP interrupt source entry.
80 #[error("Failure to write MP interrupt source entry")]
81 WriteMpcIntsrc(#[source] GuestMemoryError),
82 /// Failure to write MP local interrupt source entry.
83 #[error("Failure to write MP local interrupt source entry")]
84 WriteMpcLintsrc(#[source] GuestMemoryError),
85 /// Failure to write MP table header.
86 #[error("Failure to write MP table header")]
87 WriteMpcTable(#[source] GuestMemoryError),
88 }
89
90 pub type Result<T> = result::Result<T, Error>;
91
92 // With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
93 // one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
94 // a large number for FC usecases.
95 pub const MAX_SUPPORTED_CPUS: u32 = 254;
96
97 // Most of these variables are sourced from the Intel MP Spec 1.4.
98 const SMP_MAGIC_IDENT: &[c_uchar; 4] = b"_MP_";
99 const MPC_SIGNATURE: &[c_uchar; 4] = b"PCMP";
100 const MPC_SPEC: u8 = 4;
101 const MPC_OEM: &[c_uchar; 8] = b"FC ";
102 const MPC_PRODUCT_ID: &[c_uchar; 12] = &[b'0'; 12];
103 const BUS_TYPE_ISA: &[c_uchar; 6] = b"ISA ";
104 const APIC_VERSION: u8 = 0x14;
105 const CPU_STEPPING: u32 = 0x600;
106 const CPU_FEATURE_APIC: u32 = 0x200;
107 const CPU_FEATURE_FPU: u32 = 0x001;
108
compute_checksum<T: Copy + ByteValued>(v: &T) -> u8109 fn compute_checksum<T: Copy + ByteValued>(v: &T) -> u8 {
110 // SAFETY: we are only reading the bytes within the size of the `T` reference `v`.
111 let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
112 let mut checksum: u8 = 0;
113 for i in v_slice.iter() {
114 checksum = checksum.wrapping_add(*i);
115 }
116 checksum
117 }
118
mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8119 fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
120 let checksum = compute_checksum(v).wrapping_sub(v.checksum);
121 (!checksum).wrapping_add(1)
122 }
123
compute_mp_size(num_cpus: u8) -> usize124 fn compute_mp_size(num_cpus: u8) -> usize {
125 mem::size_of::<MpfIntelWrapper>()
126 + mem::size_of::<MpcTableWrapper>()
127 + mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
128 + mem::size_of::<MpcIoapicWrapper>()
129 + mem::size_of::<MpcBusWrapper>()
130 + mem::size_of::<MpcIntsrcWrapper>() * 16
131 + mem::size_of::<MpcLintsrcWrapper>() * 2
132 }
133
134 /// Performs setup of the MP table for the given `num_cpus`.
setup_mptable( offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8, topology: Option<(u8, u8, u8)>, ) -> Result<()>135 pub fn setup_mptable(
136 offset: GuestAddress,
137 mem: &GuestMemoryMmap,
138 num_cpus: u8,
139 topology: Option<(u8, u8, u8)>,
140 ) -> Result<()> {
141 if num_cpus > 0 {
142 let cpu_id_max = num_cpus - 1;
143 let x2apic_id_max = get_x2apic_id(cpu_id_max.into(), topology);
144 if x2apic_id_max >= MAX_SUPPORTED_CPUS {
145 return Err(Error::TooManyCpus);
146 }
147 }
148
149 // Used to keep track of the next base pointer into the MP table.
150 let mut base_mp = offset;
151
152 let mp_size = compute_mp_size(num_cpus);
153
154 if offset.unchecked_add(mp_size as u64) >= HIGH_RAM_START {
155 warn!("Skipping mptable creation due to insufficient space");
156 return Ok(());
157 }
158
159 let mut checksum: u8 = 0;
160 let ioapicid: u8 = MAX_SUPPORTED_CPUS as u8 + 1;
161
162 // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
163 // overflow.
164 if let Some(end_mp) = base_mp.checked_add((mp_size - 1) as u64) {
165 if !mem.address_in_range(end_mp) {
166 return Err(Error::NotEnoughMemory);
167 }
168 } else {
169 return Err(Error::AddressOverflow);
170 }
171
172 mem.read_exact_volatile_from(base_mp, &mut vec![0; mp_size].as_slice(), mp_size)
173 .map_err(Error::Clear)?;
174
175 {
176 let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
177 let size = mem::size_of::<MpfIntelWrapper>() as u64;
178 mpf_intel.0.signature = *SMP_MAGIC_IDENT;
179 mpf_intel.0.length = 1;
180 mpf_intel.0.specification = 4;
181 mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
182 mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
183 mem.write_obj(mpf_intel, base_mp)
184 .map_err(Error::WriteMpfIntel)?;
185 base_mp = base_mp.unchecked_add(size);
186 }
187
188 // We set the location of the mpc_table here but we can't fill it out until we have the length
189 // of the entire table later.
190 let table_base = base_mp;
191 base_mp = base_mp.unchecked_add(mem::size_of::<MpcTableWrapper>() as u64);
192
193 {
194 let size = mem::size_of::<MpcCpuWrapper>();
195 for cpu_id in 0..num_cpus {
196 let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
197 mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
198 mpc_cpu.0.apicid = get_x2apic_id(cpu_id as u32, topology) as u8;
199 mpc_cpu.0.apicver = APIC_VERSION;
200 mpc_cpu.0.cpuflag = mpspec::CPU_ENABLED as u8
201 | if cpu_id == 0 {
202 mpspec::CPU_BOOTPROCESSOR as u8
203 } else {
204 0
205 };
206 mpc_cpu.0.cpufeature = CPU_STEPPING;
207 mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
208 mem.write_obj(mpc_cpu, base_mp)
209 .map_err(Error::WriteMpcCpu)?;
210 base_mp = base_mp.unchecked_add(size as u64);
211 checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
212 }
213 }
214 {
215 let size = mem::size_of::<MpcBusWrapper>();
216 let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
217 mpc_bus.0.type_ = mpspec::MP_BUS as u8;
218 mpc_bus.0.busid = 0;
219 mpc_bus.0.bustype = *BUS_TYPE_ISA;
220 mem.write_obj(mpc_bus, base_mp)
221 .map_err(Error::WriteMpcBus)?;
222 base_mp = base_mp.unchecked_add(size as u64);
223 checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
224 }
225 {
226 let size = mem::size_of::<MpcIoapicWrapper>();
227 let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default());
228 mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8;
229 mpc_ioapic.0.apicid = ioapicid;
230 mpc_ioapic.0.apicver = APIC_VERSION;
231 mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
232 mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
233 mem.write_obj(mpc_ioapic, base_mp)
234 .map_err(Error::WriteMpcIoapic)?;
235 base_mp = base_mp.unchecked_add(size as u64);
236 checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
237 }
238 // Per kvm_setup_default_irq_routing() in kernel
239 for i in 0..16 {
240 let size = mem::size_of::<MpcIntsrcWrapper>();
241 let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
242 mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
243 mpc_intsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_INT as u8;
244 mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
245 mpc_intsrc.0.srcbus = 0;
246 mpc_intsrc.0.srcbusirq = i;
247 mpc_intsrc.0.dstapic = ioapicid;
248 mpc_intsrc.0.dstirq = i;
249 mem.write_obj(mpc_intsrc, base_mp)
250 .map_err(Error::WriteMpcIntsrc)?;
251 base_mp = base_mp.unchecked_add(size as u64);
252 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
253 }
254 {
255 let size = mem::size_of::<MpcLintsrcWrapper>();
256 let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
257 mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
258 mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_EXT_INT as u8;
259 mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
260 mpc_lintsrc.0.srcbusid = 0;
261 mpc_lintsrc.0.srcbusirq = 0;
262 mpc_lintsrc.0.destapic = 0;
263 mpc_lintsrc.0.destapiclint = 0;
264 mem.write_obj(mpc_lintsrc, base_mp)
265 .map_err(Error::WriteMpcLintsrc)?;
266 base_mp = base_mp.unchecked_add(size as u64);
267 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
268 }
269 {
270 let size = mem::size_of::<MpcLintsrcWrapper>();
271 let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
272 mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
273 mpc_lintsrc.0.irqtype = mpspec::MP_IRQ_SOURCE_TYPES_MP_NMI as u8;
274 mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
275 mpc_lintsrc.0.srcbusid = 0;
276 mpc_lintsrc.0.srcbusirq = 0;
277 mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */
278 mpc_lintsrc.0.destapiclint = 1;
279 mem.write_obj(mpc_lintsrc, base_mp)
280 .map_err(Error::WriteMpcLintsrc)?;
281 base_mp = base_mp.unchecked_add(size as u64);
282 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
283 }
284
285 // At this point we know the size of the mp_table.
286 let table_end = base_mp;
287
288 {
289 let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
290 mpc_table.0.signature = *MPC_SIGNATURE;
291 mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
292 mpc_table.0.spec = MPC_SPEC;
293 mpc_table.0.oem = *MPC_OEM;
294 mpc_table.0.productid = *MPC_PRODUCT_ID;
295 mpc_table.0.lapic = APIC_START.0 as u32;
296 checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
297 mpc_table.0.checksum = (!checksum).wrapping_add(1);
298 mem.write_obj(mpc_table, table_base)
299 .map_err(Error::WriteMpcTable)?;
300 }
301
302 Ok(())
303 }
304
305 #[cfg(test)]
306 mod tests {
307 use vm_memory::bitmap::BitmapSlice;
308 use vm_memory::{GuestUsize, VolatileMemoryError, VolatileSlice, WriteVolatile};
309
310 use super::*;
311 use crate::layout::MPTABLE_START;
312
table_entry_size(type_: u8) -> usize313 fn table_entry_size(type_: u8) -> usize {
314 match type_ as u32 {
315 mpspec::MP_PROCESSOR => mem::size_of::<MpcCpuWrapper>(),
316 mpspec::MP_BUS => mem::size_of::<MpcBusWrapper>(),
317 mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
318 mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
319 mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
320 _ => panic!("unrecognized mpc table entry type: {type_}"),
321 }
322 }
323
324 #[test]
bounds_check()325 fn bounds_check() {
326 let num_cpus = 4;
327 let mem =
328 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
329
330 setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
331 }
332
333 #[test]
bounds_check_fails()334 fn bounds_check_fails() {
335 let num_cpus = 4;
336 let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
337 .unwrap();
338
339 setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap_err();
340 }
341
342 #[test]
mpf_intel_checksum()343 fn mpf_intel_checksum() {
344 let num_cpus = 1;
345 let mem =
346 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
347
348 setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
349
350 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
351
352 assert_eq!(
353 mpf_intel_compute_checksum(&mpf_intel.0),
354 mpf_intel.0.checksum
355 );
356 }
357
358 #[test]
mpc_table_checksum()359 fn mpc_table_checksum() {
360 let num_cpus = 4;
361 let mem =
362 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
363
364 setup_mptable(MPTABLE_START, &mem, num_cpus, None).unwrap();
365
366 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
367 let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
368 let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
369
370 struct Sum(u8);
371 impl WriteVolatile for Sum {
372 fn write_volatile<B: BitmapSlice>(
373 &mut self,
374 buf: &VolatileSlice<B>,
375 ) -> result::Result<usize, VolatileMemoryError> {
376 let mut tmp = vec![0u8; buf.len()];
377 tmp.write_all_volatile(buf)?;
378
379 for v in tmp.iter() {
380 self.0 = self.0.wrapping_add(*v);
381 }
382
383 Ok(buf.len())
384 }
385 }
386
387 let mut sum = Sum(0);
388 mem.write_volatile_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
389 .unwrap();
390 assert_eq!(sum.0, 0);
391 }
392
393 #[test]
cpu_entry_count()394 fn cpu_entry_count() {
395 let mem = GuestMemoryMmap::from_ranges(&[(
396 MPTABLE_START,
397 compute_mp_size(MAX_SUPPORTED_CPUS as u8),
398 )])
399 .unwrap();
400
401 for i in 0..MAX_SUPPORTED_CPUS as u8 {
402 setup_mptable(MPTABLE_START, &mem, i, None).unwrap();
403
404 let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
405 let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
406 let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
407 let mpc_end = mpc_offset
408 .checked_add(mpc_table.0.length as GuestUsize)
409 .unwrap();
410
411 let mut entry_offset = mpc_offset
412 .checked_add(mem::size_of::<MpcTableWrapper>() as GuestUsize)
413 .unwrap();
414 let mut cpu_count = 0;
415 while entry_offset < mpc_end {
416 let entry_type: u8 = mem.read_obj(entry_offset).unwrap();
417 entry_offset = entry_offset
418 .checked_add(table_entry_size(entry_type) as GuestUsize)
419 .unwrap();
420 assert!(entry_offset <= mpc_end);
421 if entry_type as u32 == mpspec::MP_PROCESSOR {
422 cpu_count += 1;
423 }
424 }
425 assert_eq!(cpu_count, i);
426 }
427 }
428
429 #[test]
cpu_entry_count_max()430 fn cpu_entry_count_max() {
431 let cpus = MAX_SUPPORTED_CPUS + 1;
432 let mem =
433 GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
434
435 let result = setup_mptable(MPTABLE_START, &mem, cpus as u8, None);
436 result.unwrap_err();
437 }
438 }
439