xref: /cloud-hypervisor/arch/src/x86_64/mptable.rs (revision eeae63b4595fbf0cc69f62b6e9d9a79c543c4ac7)
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: {0}")]
63     Clear(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: {0}")]
69     WriteMpfIntel(GuestMemoryError),
70     /// Failure to write MP CPU entry.
71     #[error("Failure to write MP CPU entry: {0}")]
72     WriteMpcCpu(GuestMemoryError),
73     /// Failure to write MP ioapic entry.
74     #[error("Failure to write MP ioapic entry: {0}")]
75     WriteMpcIoapic(GuestMemoryError),
76     /// Failure to write MP bus entry.
77     #[error("Failure to write MP bus entry: {0}")]
78     WriteMpcBus(GuestMemoryError),
79     /// Failure to write MP interrupt source entry.
80     #[error("Failure to write MP interrupt source entry: {0}")]
81     WriteMpcIntsrc(GuestMemoryError),
82     /// Failure to write MP local interrupt source entry.
83     #[error("Failure to write MP local interrupt source entry: {0}")]
84     WriteMpcLintsrc(GuestMemoryError),
85     /// Failure to write MP table header.
86     #[error("Failure to write MP table header: {0}")]
87     WriteMpcTable(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 
109 fn compute_checksum<T: Copy>(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 
119 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 
124 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`.
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 
313     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]
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]
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]
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]
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]
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]
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