• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::fmt::{self, Display};
6 use std::mem;
7 use std::result;
8 use std::slice;
9 
10 use libc::c_char;
11 
12 use data_model::VolatileMemory;
13 use devices::PciInterruptPin;
14 use sys_util::{GuestAddress, GuestMemory};
15 
16 use crate::mpspec::*;
17 
18 #[derive(Debug)]
19 pub enum Error {
20     /// There was too little guest memory to store the entire MP table.
21     NotEnoughMemory,
22     /// The MP table has too little address space to be stored.
23     AddressOverflow,
24     /// Failure while zeroing out the memory for the MP table.
25     Clear,
26     /// Failure to write the MP floating pointer.
27     WriteMpfIntel,
28     /// Failure to write MP CPU entry.
29     WriteMpcCpu,
30     /// Failure to write MP ioapic entry.
31     WriteMpcIoapic,
32     /// Failure to write MP bus entry.
33     WriteMpcBus,
34     /// Failure to write MP interrupt source entry.
35     WriteMpcIntsrc,
36     /// Failure to write MP local interrupt source entry.
37     WriteMpcLintsrc,
38     /// Failure to write MP table header.
39     WriteMpcTable,
40 }
41 
42 impl std::error::Error for Error {}
43 
44 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result45     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46         use self::Error::*;
47 
48         let description = match self {
49             NotEnoughMemory => "There was too little guest memory to store the MP table",
50             AddressOverflow => "The MP table has too little address space to be stored",
51             Clear => "Failure while zeroing out the memory for the MP table",
52             WriteMpfIntel => "Failure to write the MP floating pointer",
53             WriteMpcCpu => "Failure to write MP CPU entry",
54             WriteMpcIoapic => "Failure to write MP ioapic entry",
55             WriteMpcBus => "Failure to write MP bus entry",
56             WriteMpcIntsrc => "Failure to write MP interrupt source entry",
57             WriteMpcLintsrc => "Failure to write MP local interrupt source entry",
58             WriteMpcTable => "Failure to write MP table header",
59         };
60 
61         write!(f, "MPTable error: {}", description)
62     }
63 }
64 
65 pub type Result<T> = result::Result<T, Error>;
66 
67 // Convenience macro for making arrays of diverse character types.
68 macro_rules! char_array {
69     ($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
70 }
71 
72 // Most of these variables are sourced from the Intel MP Spec 1.4.
73 const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
74 const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
75 const MPC_SPEC: i8 = 4;
76 const MPC_OEM: [c_char; 8] = char_array!(c_char; 'C', 'R', 'O', 'S', 'V', 'M', ' ', ' ');
77 const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
78 const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
79 const BUS_TYPE_PCI: [u8; 6] = char_array!(u8; 'P', 'C', 'I', ' ', ' ', ' ');
80 const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
81 const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
82 const APIC_VERSION: u8 = 0x14;
83 const CPU_STEPPING: u32 = 0x600;
84 const CPU_FEATURE_APIC: u32 = 0x200;
85 const CPU_FEATURE_FPU: u32 = 0x001;
86 const MPTABLE_START: u64 = 0x400 * 639; // Last 1k of Linux's 640k base RAM.
87 
compute_checksum<T: Copy>(v: &T) -> u888 fn compute_checksum<T: Copy>(v: &T) -> u8 {
89     // Safe because we are only reading the bytes within the size of the `T` reference `v`.
90     let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
91     let mut checksum: u8 = 0;
92     for i in v_slice {
93         checksum = checksum.wrapping_add(*i);
94     }
95     checksum
96 }
97 
mpf_intel_compute_checksum(v: &mpf_intel) -> u898 fn mpf_intel_compute_checksum(v: &mpf_intel) -> u8 {
99     let checksum = compute_checksum(v).wrapping_sub(v.checksum);
100     (!checksum).wrapping_add(1)
101 }
102 
compute_mp_size(num_cpus: u8) -> usize103 fn compute_mp_size(num_cpus: u8) -> usize {
104     mem::size_of::<mpf_intel>()
105         + mem::size_of::<mpc_table>()
106         + mem::size_of::<mpc_cpu>() * (num_cpus as usize)
107         + mem::size_of::<mpc_ioapic>()
108         + mem::size_of::<mpc_bus>() * 2
109         + mem::size_of::<mpc_intsrc>()
110         + mem::size_of::<mpc_intsrc>() * 16
111         + mem::size_of::<mpc_lintsrc>() * 2
112 }
113 
114 /// Performs setup of the MP table for the given `num_cpus`.
setup_mptable( mem: &GuestMemory, num_cpus: u8, pci_irqs: Vec<(u32, PciInterruptPin)>, ) -> Result<()>115 pub fn setup_mptable(
116     mem: &GuestMemory,
117     num_cpus: u8,
118     pci_irqs: Vec<(u32, PciInterruptPin)>,
119 ) -> Result<()> {
120     const PCI_BUS_ID: u8 = 0;
121     const ISA_BUS_ID: u8 = 1;
122 
123     // Used to keep track of the next base pointer into the MP table.
124     let mut base_mp = GuestAddress(MPTABLE_START);
125 
126     let mp_size = compute_mp_size(num_cpus);
127 
128     // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
129     // overflow.
130     if let Some(end_mp) = base_mp.checked_add(mp_size as u64 - 1) {
131         if !mem.address_in_range(end_mp) {
132             return Err(Error::NotEnoughMemory);
133         }
134     } else {
135         return Err(Error::AddressOverflow);
136     }
137 
138     mem.get_slice(base_mp.0, mp_size as u64)
139         .map_err(|_| Error::Clear)?
140         .write_bytes(0);
141 
142     {
143         let size = mem::size_of::<mpf_intel>();
144         let mut mpf_intel = mpf_intel::default();
145         mpf_intel.signature = SMP_MAGIC_IDENT;
146         mpf_intel.length = 1;
147         mpf_intel.specification = 4;
148         mpf_intel.physptr = (base_mp.offset() + mem::size_of::<mpf_intel>() as u64) as u32;
149         mpf_intel.checksum = mpf_intel_compute_checksum(&mpf_intel);
150         mem.write_obj_at_addr(mpf_intel, base_mp)
151             .map_err(|_| Error::WriteMpfIntel)?;
152         base_mp = base_mp.unchecked_add(size as u64);
153     }
154 
155     // We set the location of the mpc_table here but we can't fill it out until we have the length
156     // of the entire table later.
157     let table_base = base_mp;
158     base_mp = base_mp.unchecked_add(mem::size_of::<mpc_table>() as u64);
159 
160     let mut checksum: u8 = 0;
161     let ioapicid: u8 = num_cpus + 1;
162 
163     for cpu_id in 0..num_cpus {
164         let size = mem::size_of::<mpc_cpu>();
165         let mut mpc_cpu = mpc_cpu::default();
166         mpc_cpu.type_ = MP_PROCESSOR as u8;
167         mpc_cpu.apicid = cpu_id;
168         mpc_cpu.apicver = APIC_VERSION;
169         mpc_cpu.cpuflag = CPU_ENABLED as u8
170             | if cpu_id == 0 {
171                 CPU_BOOTPROCESSOR as u8
172             } else {
173                 0
174             };
175         mpc_cpu.cpufeature = CPU_STEPPING;
176         mpc_cpu.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
177         mem.write_obj_at_addr(mpc_cpu, base_mp)
178             .map_err(|_| Error::WriteMpcCpu)?;
179         base_mp = base_mp.unchecked_add(size as u64);
180         checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu));
181     }
182     {
183         let size = mem::size_of::<mpc_ioapic>();
184         let mut mpc_ioapic = mpc_ioapic::default();
185         mpc_ioapic.type_ = MP_IOAPIC as u8;
186         mpc_ioapic.apicid = ioapicid;
187         mpc_ioapic.apicver = APIC_VERSION;
188         mpc_ioapic.flags = MPC_APIC_USABLE as u8;
189         mpc_ioapic.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
190         mem.write_obj_at_addr(mpc_ioapic, base_mp)
191             .map_err(|_| Error::WriteMpcIoapic)?;
192         base_mp = base_mp.unchecked_add(size as u64);
193         checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic));
194     }
195     {
196         let size = mem::size_of::<mpc_bus>();
197         let mut mpc_bus = mpc_bus::default();
198         mpc_bus.type_ = MP_BUS as u8;
199         mpc_bus.busid = PCI_BUS_ID;
200         mpc_bus.bustype = BUS_TYPE_PCI;
201         mem.write_obj_at_addr(mpc_bus, base_mp)
202             .map_err(|_| Error::WriteMpcBus)?;
203         base_mp = base_mp.unchecked_add(size as u64);
204         checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
205     }
206     {
207         let size = mem::size_of::<mpc_bus>();
208         let mut mpc_bus = mpc_bus::default();
209         mpc_bus.type_ = MP_BUS as u8;
210         mpc_bus.busid = ISA_BUS_ID;
211         mpc_bus.bustype = BUS_TYPE_ISA;
212         mem.write_obj_at_addr(mpc_bus, base_mp)
213             .map_err(|_| Error::WriteMpcBus)?;
214         base_mp = base_mp.unchecked_add(size as u64);
215         checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
216     }
217     {
218         let size = mem::size_of::<mpc_intsrc>();
219         let mut mpc_intsrc = mpc_intsrc::default();
220         mpc_intsrc.type_ = MP_INTSRC as u8;
221         mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8;
222         mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
223         mpc_intsrc.srcbus = ISA_BUS_ID;
224         mpc_intsrc.srcbusirq = 0;
225         mpc_intsrc.dstapic = 0;
226         mpc_intsrc.dstirq = 0;
227         mem.write_obj_at_addr(mpc_intsrc, base_mp)
228             .map_err(|_| Error::WriteMpcIntsrc)?;
229         base_mp = base_mp.unchecked_add(size as u64);
230         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
231     }
232     // Per kvm_setup_default_irq_routing() in kernel
233     for i in 0..5 {
234         let size = mem::size_of::<mpc_intsrc>();
235         let mut mpc_intsrc = mpc_intsrc::default();
236         mpc_intsrc.type_ = MP_INTSRC as u8;
237         mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8;
238         mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
239         mpc_intsrc.srcbus = ISA_BUS_ID;
240         mpc_intsrc.srcbusirq = i;
241         mpc_intsrc.dstapic = ioapicid;
242         mpc_intsrc.dstirq = i;
243         mem.write_obj_at_addr(mpc_intsrc, base_mp)
244             .map_err(|_| Error::WriteMpcIntsrc)?;
245         base_mp = base_mp.unchecked_add(size as u64);
246         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
247     }
248     // Insert PCI interrupts after platform IRQs.
249     for (i, pci_irq) in pci_irqs.iter().enumerate() {
250         let size = mem::size_of::<mpc_intsrc>();
251         let mut mpc_intsrc = mpc_intsrc::default();
252         mpc_intsrc.type_ = MP_INTSRC as u8;
253         mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8;
254         mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
255         mpc_intsrc.srcbus = PCI_BUS_ID;
256         mpc_intsrc.srcbusirq = (pci_irq.0 as u8 + 1) << 2 | pci_irq.1.to_mask() as u8;
257         mpc_intsrc.dstapic = ioapicid;
258         mpc_intsrc.dstirq = 5 + i as u8;
259         mem.write_obj_at_addr(mpc_intsrc, base_mp)
260             .map_err(|_| Error::WriteMpcIntsrc)?;
261         base_mp = base_mp.unchecked_add(size as u64);
262         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
263     }
264     // Finally insert ISA interrupts.
265     for i in 5 + pci_irqs.len()..16 {
266         let size = mem::size_of::<mpc_intsrc>();
267         let mut mpc_intsrc = mpc_intsrc::default();
268         mpc_intsrc.type_ = MP_INTSRC as u8;
269         mpc_intsrc.irqtype = mp_irq_source_types_mp_INT as u8;
270         mpc_intsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
271         mpc_intsrc.srcbus = ISA_BUS_ID;
272         mpc_intsrc.srcbusirq = i as u8;
273         mpc_intsrc.dstapic = ioapicid;
274         mpc_intsrc.dstirq = i as u8;
275         mem.write_obj_at_addr(mpc_intsrc, base_mp)
276             .map_err(|_| Error::WriteMpcIntsrc)?;
277         base_mp = base_mp.unchecked_add(size as u64);
278         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
279     }
280     {
281         let size = mem::size_of::<mpc_lintsrc>();
282         let mut mpc_lintsrc = mpc_lintsrc::default();
283         mpc_lintsrc.type_ = MP_LINTSRC as u8;
284         mpc_lintsrc.irqtype = mp_irq_source_types_mp_ExtINT as u8;
285         mpc_lintsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
286         mpc_lintsrc.srcbusid = ISA_BUS_ID;
287         mpc_lintsrc.srcbusirq = 0;
288         mpc_lintsrc.destapic = 0;
289         mpc_lintsrc.destapiclint = 0;
290         mem.write_obj_at_addr(mpc_lintsrc, base_mp)
291             .map_err(|_| Error::WriteMpcLintsrc)?;
292         base_mp = base_mp.unchecked_add(size as u64);
293         checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
294     }
295     {
296         let size = mem::size_of::<mpc_lintsrc>();
297         let mut mpc_lintsrc = mpc_lintsrc::default();
298         mpc_lintsrc.type_ = MP_LINTSRC as u8;
299         mpc_lintsrc.irqtype = mp_irq_source_types_mp_NMI as u8;
300         mpc_lintsrc.irqflag = MP_IRQDIR_DEFAULT as u16;
301         mpc_lintsrc.srcbusid = ISA_BUS_ID;
302         mpc_lintsrc.srcbusirq = 0;
303         mpc_lintsrc.destapic = 0xFF; // Per SeaBIOS
304         mpc_lintsrc.destapiclint = 1;
305         mem.write_obj_at_addr(mpc_lintsrc, base_mp)
306             .map_err(|_| Error::WriteMpcLintsrc)?;
307         base_mp = base_mp.unchecked_add(size as u64);
308         checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
309     }
310 
311     // At this point we know the size of the mp_table.
312     let table_end = base_mp;
313 
314     {
315         let mut mpc_table = mpc_table::default();
316         mpc_table.signature = MPC_SIGNATURE;
317         mpc_table.length = table_end.offset_from(table_base) as u16;
318         mpc_table.spec = MPC_SPEC;
319         mpc_table.oem = MPC_OEM;
320         mpc_table.productid = MPC_PRODUCT_ID;
321         mpc_table.lapic = APIC_DEFAULT_PHYS_BASE;
322         checksum = checksum.wrapping_add(compute_checksum(&mpc_table));
323         mpc_table.checksum = (!checksum).wrapping_add(1) as i8;
324         mem.write_obj_at_addr(mpc_table, table_base)
325             .map_err(|_| Error::WriteMpcTable)?;
326     }
327 
328     Ok(())
329 }
330 
331 #[cfg(test)]
332 mod tests {
333     use super::*;
334     use sys_util::pagesize;
335 
compute_page_aligned_mp_size(num_cpus: u8) -> u64336     fn compute_page_aligned_mp_size(num_cpus: u8) -> u64 {
337         let mp_size = compute_mp_size(num_cpus);
338         let pg_size = pagesize();
339         (mp_size + pg_size - (mp_size % pg_size)) as u64
340     }
341 
table_entry_size(type_: u8) -> usize342     fn table_entry_size(type_: u8) -> usize {
343         match type_ as u32 {
344             MP_PROCESSOR => mem::size_of::<mpc_cpu>(),
345             MP_BUS => mem::size_of::<mpc_bus>(),
346             MP_IOAPIC => mem::size_of::<mpc_ioapic>(),
347             MP_INTSRC => mem::size_of::<mpc_intsrc>(),
348             MP_LINTSRC => mem::size_of::<mpc_lintsrc>(),
349             _ => panic!("unrecognized mpc table entry type: {}", type_),
350         }
351     }
352 
353     #[test]
bounds_check()354     fn bounds_check() {
355         let num_cpus = 4;
356         let mem = GuestMemory::new(&[(
357             GuestAddress(MPTABLE_START),
358             compute_page_aligned_mp_size(num_cpus),
359         )])
360         .unwrap();
361 
362         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
363     }
364 
365     #[test]
bounds_check_fails()366     fn bounds_check_fails() {
367         let num_cpus = 255;
368         let mem = GuestMemory::new(&[(GuestAddress(MPTABLE_START), 0x1000)]).unwrap();
369 
370         assert!(setup_mptable(&mem, num_cpus, Vec::new()).is_err());
371     }
372 
373     #[test]
mpf_intel_checksum()374     fn mpf_intel_checksum() {
375         let num_cpus = 1;
376         let mem = GuestMemory::new(&[(
377             GuestAddress(MPTABLE_START),
378             compute_page_aligned_mp_size(num_cpus),
379         )])
380         .unwrap();
381 
382         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
383 
384         let mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
385 
386         assert_eq!(mpf_intel_compute_checksum(&mpf_intel), mpf_intel.checksum);
387     }
388 
389     #[test]
mpc_table_checksum()390     fn mpc_table_checksum() {
391         let num_cpus = 4;
392         let mem = GuestMemory::new(&[(
393             GuestAddress(MPTABLE_START),
394             compute_page_aligned_mp_size(num_cpus),
395         )])
396         .unwrap();
397 
398         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
399 
400         let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
401         let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
402         let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
403 
404         let mut buf = vec![0; mpc_table.length as usize];
405         mem.read_at_addr(&mut buf[..], mpc_offset).unwrap();
406         let mut sum: u8 = 0;
407         for &v in &buf {
408             sum = sum.wrapping_add(v);
409         }
410 
411         assert_eq!(sum, 0);
412     }
413 
414     #[test]
cpu_entry_count()415     fn cpu_entry_count() {
416         const MAX_CPUS: u8 = 0xff;
417         let mem = GuestMemory::new(&[(
418             GuestAddress(MPTABLE_START),
419             compute_page_aligned_mp_size(MAX_CPUS),
420         )])
421         .unwrap();
422 
423         for i in 0..MAX_CPUS {
424             setup_mptable(&mem, i, Vec::new()).unwrap();
425 
426             let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
427             let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
428             let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
429             let mpc_end = mpc_offset.checked_add(mpc_table.length as u64).unwrap();
430 
431             let mut entry_offset = mpc_offset
432                 .checked_add(mem::size_of::<mpc_table>() as u64)
433                 .unwrap();
434             let mut cpu_count = 0;
435             while entry_offset < mpc_end {
436                 let entry_type: u8 = mem.read_obj_from_addr(entry_offset).unwrap();
437                 entry_offset = entry_offset
438                     .checked_add(table_entry_size(entry_type) as u64)
439                     .unwrap();
440                 assert!(entry_offset <= mpc_end);
441                 if entry_type as u32 == MP_PROCESSOR {
442                     cpu_count += 1;
443                 }
444             }
445             assert_eq!(cpu_count, i);
446         }
447     }
448 }
449