• 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::convert::TryFrom;
6 use std::fmt::{self, Display};
7 use std::mem;
8 use std::result;
9 use std::slice;
10 
11 use libc::c_char;
12 
13 use devices::{PciAddress, PciInterruptPin};
14 use vm_memory::{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 // source: linux/arch/x86/include/asm/apicdef.h
81 pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000;
82 // source: linux/arch/x86/include/asm/apicdef.h
83 pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000;
84 const APIC_VERSION: u8 = 0x14;
85 const CPU_STEPPING: u32 = 0x600;
86 const CPU_FEATURE_APIC: u32 = 0x200;
87 const CPU_FEATURE_FPU: u32 = 0x001;
88 const MPTABLE_START: u64 = 0x400 * 639; // Last 1k of Linux's 640k base RAM.
89 
compute_checksum<T: Copy>(v: &T) -> u890 fn compute_checksum<T: Copy>(v: &T) -> u8 {
91     // Safe because we are only reading the bytes within the size of the `T` reference `v`.
92     let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
93     let mut checksum: u8 = 0;
94     for i in v_slice {
95         checksum = checksum.wrapping_add(*i);
96     }
97     checksum
98 }
99 
mpf_intel_compute_checksum(v: &mpf_intel) -> u8100 fn mpf_intel_compute_checksum(v: &mpf_intel) -> u8 {
101     let checksum = compute_checksum(v).wrapping_sub(v.checksum);
102     (!checksum).wrapping_add(1)
103 }
104 
compute_mp_size(num_cpus: u8) -> usize105 fn compute_mp_size(num_cpus: u8) -> usize {
106     mem::size_of::<mpf_intel>()
107         + mem::size_of::<mpc_table>()
108         + mem::size_of::<mpc_cpu>() * (num_cpus as usize)
109         + mem::size_of::<mpc_ioapic>()
110         + mem::size_of::<mpc_bus>() * 2
111         + mem::size_of::<mpc_intsrc>()
112         + mem::size_of::<mpc_intsrc>() * 16
113         + mem::size_of::<mpc_lintsrc>() * 2
114 }
115 
116 /// Performs setup of the MP table for the given `num_cpus`.
setup_mptable( mem: &GuestMemory, num_cpus: u8, pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>, ) -> Result<()>117 pub fn setup_mptable(
118     mem: &GuestMemory,
119     num_cpus: u8,
120     pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
121 ) -> Result<()> {
122     // Used to keep track of the next base pointer into the MP table.
123     let mut base_mp = GuestAddress(MPTABLE_START);
124 
125     // Calculate ISA bus number in the system, report at least one PCI bus.
126     let isa_bus_id = match pci_irqs.iter().max_by_key(|v| v.0.bus) {
127         Some(pci_irq) => pci_irq.0.bus + 1,
128         _ => 1,
129     };
130     let mp_size = compute_mp_size(num_cpus);
131 
132     // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
133     // overflow.
134     if let Some(end_mp) = base_mp.checked_add(mp_size as u64 - 1) {
135         if !mem.address_in_range(end_mp) {
136             return Err(Error::NotEnoughMemory);
137         }
138     } else {
139         return Err(Error::AddressOverflow);
140     }
141 
142     mem.get_slice_at_addr(base_mp, mp_size)
143         .map_err(|_| Error::Clear)?
144         .write_bytes(0);
145 
146     {
147         let size = mem::size_of::<mpf_intel>();
148         let mut mpf_intel = mpf_intel::default();
149         mpf_intel.signature = SMP_MAGIC_IDENT;
150         mpf_intel.length = 1;
151         mpf_intel.specification = 4;
152         mpf_intel.physptr = (base_mp.offset() + mem::size_of::<mpf_intel>() as u64) as u32;
153         mpf_intel.checksum = mpf_intel_compute_checksum(&mpf_intel);
154         mem.write_obj_at_addr(mpf_intel, base_mp)
155             .map_err(|_| Error::WriteMpfIntel)?;
156         base_mp = base_mp.unchecked_add(size as u64);
157     }
158 
159     // We set the location of the mpc_table here but we can't fill it out until we have the length
160     // of the entire table later.
161     let table_base = base_mp;
162     base_mp = base_mp.unchecked_add(mem::size_of::<mpc_table>() as u64);
163 
164     let mut checksum: u8 = 0;
165     let ioapicid: u8 = num_cpus + 1;
166 
167     for cpu_id in 0..num_cpus {
168         let size = mem::size_of::<mpc_cpu>();
169         let mpc_cpu = mpc_cpu {
170             type_: MP_PROCESSOR as u8,
171             apicid: cpu_id,
172             apicver: APIC_VERSION,
173             cpuflag: CPU_ENABLED as u8
174                 | if cpu_id == 0 {
175                     CPU_BOOTPROCESSOR as u8
176                 } else {
177                     0
178                 },
179             cpufeature: CPU_STEPPING,
180             featureflag: CPU_FEATURE_APIC | CPU_FEATURE_FPU,
181             ..Default::default()
182         };
183         mem.write_obj_at_addr(mpc_cpu, base_mp)
184             .map_err(|_| Error::WriteMpcCpu)?;
185         base_mp = base_mp.unchecked_add(size as u64);
186         checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu));
187     }
188     {
189         let size = mem::size_of::<mpc_ioapic>();
190         let mpc_ioapic = mpc_ioapic {
191             type_: MP_IOAPIC as u8,
192             apicid: ioapicid,
193             apicver: APIC_VERSION,
194             flags: MPC_APIC_USABLE as u8,
195             apicaddr: IO_APIC_DEFAULT_PHYS_BASE,
196         };
197         mem.write_obj_at_addr(mpc_ioapic, base_mp)
198             .map_err(|_| Error::WriteMpcIoapic)?;
199         base_mp = base_mp.unchecked_add(size as u64);
200         checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic));
201     }
202     for pci_bus_id in 0..isa_bus_id {
203         let size = mem::size_of::<mpc_bus>();
204         let mpc_bus = mpc_bus {
205             type_: MP_BUS as u8,
206             busid: pci_bus_id,
207             bustype: BUS_TYPE_PCI,
208         };
209         mem.write_obj_at_addr(mpc_bus, base_mp)
210             .map_err(|_| Error::WriteMpcBus)?;
211         base_mp = base_mp.unchecked_add(size as u64);
212         checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
213     }
214     {
215         let size = mem::size_of::<mpc_bus>();
216         let mpc_bus = mpc_bus {
217             type_: MP_BUS as u8,
218             busid: isa_bus_id,
219             bustype: BUS_TYPE_ISA,
220         };
221         mem.write_obj_at_addr(mpc_bus, base_mp)
222             .map_err(|_| Error::WriteMpcBus)?;
223         base_mp = base_mp.unchecked_add(size as u64);
224         checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
225     }
226     {
227         let size = mem::size_of::<mpc_intsrc>();
228         let mpc_intsrc = mpc_intsrc {
229             type_: MP_INTSRC as u8,
230             irqtype: mp_irq_source_types_mp_INT as u8,
231             irqflag: MP_IRQDIR_DEFAULT as u16,
232             srcbus: isa_bus_id,
233             srcbusirq: 0,
234             dstapic: 0,
235             dstirq: 0,
236         };
237         mem.write_obj_at_addr(mpc_intsrc, base_mp)
238             .map_err(|_| Error::WriteMpcIntsrc)?;
239         base_mp = base_mp.unchecked_add(size as u64);
240         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
241     }
242     let sci_irq = super::X86_64_SCI_IRQ as u8;
243     // Per kvm_setup_default_irq_routing() in kernel
244     for i in 0..sci_irq {
245         let size = mem::size_of::<mpc_intsrc>();
246         let mpc_intsrc = mpc_intsrc {
247             type_: MP_INTSRC as u8,
248             irqtype: mp_irq_source_types_mp_INT as u8,
249             irqflag: MP_IRQDIR_DEFAULT as u16,
250             srcbus: isa_bus_id,
251             srcbusirq: i,
252             dstapic: ioapicid,
253             dstirq: i,
254         };
255         mem.write_obj_at_addr(mpc_intsrc, base_mp)
256             .map_err(|_| Error::WriteMpcIntsrc)?;
257         base_mp = base_mp.unchecked_add(size as u64);
258         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
259     }
260     // Insert SCI interrupt before PCI interrupts. Set the SCI interrupt
261     // to be the default trigger/polarity of PCI bus, which is level/low.
262     // This setting can be changed in future if necessary.
263     {
264         let size = mem::size_of::<mpc_intsrc>();
265         let mpc_intsrc = mpc_intsrc {
266             type_: MP_INTSRC as u8,
267             irqtype: mp_irq_source_types_mp_INT as u8,
268             irqflag: (MP_IRQDIR_HIGH | MP_LEVEL_TRIGGER) as u16,
269             srcbus: isa_bus_id,
270             srcbusirq: sci_irq,
271             dstapic: ioapicid,
272             dstirq: sci_irq,
273         };
274         mem.write_obj_at_addr(mpc_intsrc, base_mp)
275             .map_err(|_| Error::WriteMpcIntsrc)?;
276         base_mp = base_mp.unchecked_add(size as u64);
277         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
278     }
279 
280     // Insert PCI interrupts after platform IRQs.
281     for (address, irq_num, irq_pin) in pci_irqs.iter() {
282         let size = mem::size_of::<mpc_intsrc>();
283         let mpc_intsrc = mpc_intsrc {
284             type_: MP_INTSRC as u8,
285             irqtype: mp_irq_source_types_mp_INT as u8,
286             irqflag: MP_IRQDIR_DEFAULT as u16,
287             srcbus: address.bus,
288             srcbusirq: address.dev << 2 | irq_pin.to_mask() as u8,
289             dstapic: ioapicid,
290             dstirq: u8::try_from(*irq_num).map_err(|_| Error::WriteMpcIntsrc)?,
291         };
292         mem.write_obj_at_addr(mpc_intsrc, base_mp)
293             .map_err(|_| Error::WriteMpcIntsrc)?;
294         base_mp = base_mp.unchecked_add(size as u64);
295         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
296     }
297 
298     let starting_isa_irq_num = pci_irqs
299         .into_iter()
300         .map(|(_, irq_num, _)| irq_num + 1)
301         .fold(super::X86_64_IRQ_BASE, u32::max) as u8;
302 
303     // Finally insert ISA interrupts.
304     for i in starting_isa_irq_num..16 {
305         let size = mem::size_of::<mpc_intsrc>();
306         let mpc_intsrc = mpc_intsrc {
307             type_: MP_INTSRC as u8,
308             irqtype: mp_irq_source_types_mp_INT as u8,
309             irqflag: MP_IRQDIR_DEFAULT as u16,
310             srcbus: isa_bus_id,
311             srcbusirq: i as u8,
312             dstapic: ioapicid,
313             dstirq: i as u8,
314         };
315         mem.write_obj_at_addr(mpc_intsrc, base_mp)
316             .map_err(|_| Error::WriteMpcIntsrc)?;
317         base_mp = base_mp.unchecked_add(size as u64);
318         checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
319     }
320     {
321         let size = mem::size_of::<mpc_lintsrc>();
322         let mpc_lintsrc = mpc_lintsrc {
323             type_: MP_LINTSRC as u8,
324             irqtype: mp_irq_source_types_mp_ExtINT as u8,
325             irqflag: MP_IRQDIR_DEFAULT as u16,
326             srcbusid: isa_bus_id,
327             srcbusirq: 0,
328             destapic: 0,
329             destapiclint: 0,
330         };
331         mem.write_obj_at_addr(mpc_lintsrc, base_mp)
332             .map_err(|_| Error::WriteMpcLintsrc)?;
333         base_mp = base_mp.unchecked_add(size as u64);
334         checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
335     }
336     {
337         let size = mem::size_of::<mpc_lintsrc>();
338         let mpc_lintsrc = mpc_lintsrc {
339             type_: MP_LINTSRC as u8,
340             irqtype: mp_irq_source_types_mp_NMI as u8,
341             irqflag: MP_IRQDIR_DEFAULT as u16,
342             srcbusid: isa_bus_id,
343             srcbusirq: 0,
344             destapic: 0xFF, // Per SeaBIOS
345             destapiclint: 1,
346         };
347         mem.write_obj_at_addr(mpc_lintsrc, base_mp)
348             .map_err(|_| Error::WriteMpcLintsrc)?;
349         base_mp = base_mp.unchecked_add(size as u64);
350         checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
351     }
352 
353     // At this point we know the size of the mp_table.
354     let table_end = base_mp;
355 
356     {
357         let mut mpc_table = mpc_table {
358             signature: MPC_SIGNATURE,
359             length: table_end.offset_from(table_base) as u16,
360             spec: MPC_SPEC,
361             oem: MPC_OEM,
362             productid: MPC_PRODUCT_ID,
363             lapic: APIC_DEFAULT_PHYS_BASE,
364             ..Default::default()
365         };
366         checksum = checksum.wrapping_add(compute_checksum(&mpc_table));
367         mpc_table.checksum = (!checksum).wrapping_add(1) as i8;
368         mem.write_obj_at_addr(mpc_table, table_base)
369             .map_err(|_| Error::WriteMpcTable)?;
370     }
371 
372     Ok(())
373 }
374 
375 #[cfg(test)]
376 mod tests {
377     use super::*;
378     use base::pagesize;
379 
compute_page_aligned_mp_size(num_cpus: u8) -> u64380     fn compute_page_aligned_mp_size(num_cpus: u8) -> u64 {
381         let mp_size = compute_mp_size(num_cpus);
382         let pg_size = pagesize();
383         (mp_size + pg_size - (mp_size % pg_size)) as u64
384     }
385 
table_entry_size(type_: u8) -> usize386     fn table_entry_size(type_: u8) -> usize {
387         match type_ as u32 {
388             MP_PROCESSOR => mem::size_of::<mpc_cpu>(),
389             MP_BUS => mem::size_of::<mpc_bus>(),
390             MP_IOAPIC => mem::size_of::<mpc_ioapic>(),
391             MP_INTSRC => mem::size_of::<mpc_intsrc>(),
392             MP_LINTSRC => mem::size_of::<mpc_lintsrc>(),
393             _ => panic!("unrecognized mpc table entry type: {}", type_),
394         }
395     }
396 
397     #[test]
bounds_check()398     fn bounds_check() {
399         let num_cpus = 4;
400         let mem = GuestMemory::new(&[(
401             GuestAddress(MPTABLE_START),
402             compute_page_aligned_mp_size(num_cpus),
403         )])
404         .unwrap();
405 
406         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
407     }
408 
409     #[test]
bounds_check_fails()410     fn bounds_check_fails() {
411         let num_cpus = 255;
412         let mem = GuestMemory::new(&[(GuestAddress(MPTABLE_START), 0x1000)]).unwrap();
413 
414         assert!(setup_mptable(&mem, num_cpus, Vec::new()).is_err());
415     }
416 
417     #[test]
mpf_intel_checksum()418     fn mpf_intel_checksum() {
419         let num_cpus = 1;
420         let mem = GuestMemory::new(&[(
421             GuestAddress(MPTABLE_START),
422             compute_page_aligned_mp_size(num_cpus),
423         )])
424         .unwrap();
425 
426         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
427 
428         let mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
429 
430         assert_eq!(mpf_intel_compute_checksum(&mpf_intel), mpf_intel.checksum);
431     }
432 
433     #[test]
mpc_table_checksum()434     fn mpc_table_checksum() {
435         let num_cpus = 4;
436         let mem = GuestMemory::new(&[(
437             GuestAddress(MPTABLE_START),
438             compute_page_aligned_mp_size(num_cpus),
439         )])
440         .unwrap();
441 
442         setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
443 
444         let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
445         let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
446         let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
447 
448         let mut buf = vec![0; mpc_table.length as usize];
449         mem.read_at_addr(&mut buf[..], mpc_offset).unwrap();
450         let mut sum: u8 = 0;
451         for &v in &buf {
452             sum = sum.wrapping_add(v);
453         }
454 
455         assert_eq!(sum, 0);
456     }
457 
458     #[test]
cpu_entry_count()459     fn cpu_entry_count() {
460         const MAX_CPUS: u8 = 0xff;
461         let mem = GuestMemory::new(&[(
462             GuestAddress(MPTABLE_START),
463             compute_page_aligned_mp_size(MAX_CPUS),
464         )])
465         .unwrap();
466 
467         for i in 0..MAX_CPUS {
468             setup_mptable(&mem, i, Vec::new()).unwrap();
469 
470             let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
471             let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
472             let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
473             let mpc_end = mpc_offset.checked_add(mpc_table.length as u64).unwrap();
474 
475             let mut entry_offset = mpc_offset
476                 .checked_add(mem::size_of::<mpc_table>() as u64)
477                 .unwrap();
478             let mut cpu_count = 0;
479             while entry_offset < mpc_end {
480                 let entry_type: u8 = mem.read_obj_from_addr(entry_offset).unwrap();
481                 entry_offset = entry_offset
482                     .checked_add(table_entry_size(entry_type) as u64)
483                     .unwrap();
484                 assert!(entry_offset <= mpc_end);
485                 if entry_type as u32 == MP_PROCESSOR {
486                     cpu_count += 1;
487                 }
488             }
489             assert_eq!(cpu_count, i);
490         }
491     }
492 }
493