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