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