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