1 // Copyright 2017 The ChromiumOS Authors
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
9 use devices::PciAddress;
10 use devices::PciInterruptPin;
11 use libc::c_char;
12 use remain::sorted;
13 use thiserror::Error;
14 use vm_memory::GuestAddress;
15 use vm_memory::GuestMemory;
16 use zerocopy::AsBytes;
17
18 use crate::mpspec::*;
19
20 #[sorted]
21 #[derive(Error, Debug)]
22 pub enum Error {
23 /// The MP table has too little address space to be stored.
24 #[error("The MP table has too little address space to be stored")]
25 AddressOverflow,
26 /// Failure while zeroing out the memory for the MP table.
27 #[error("Failure while zeroing out the memory for the MP table")]
28 Clear,
29 /// There was too little guest memory to store the entire MP table.
30 #[error("There was too little guest memory to store the MP table")]
31 NotEnoughMemory,
32 /// Failure to write MP bus entry.
33 #[error("Failure to write MP bus entry")]
34 WriteMpcBus,
35 /// Failure to write MP CPU entry.
36 #[error("Failure to write MP CPU entry")]
37 WriteMpcCpu,
38 /// Failure to write MP interrupt source entry.
39 #[error("Failure to write MP interrupt source entry")]
40 WriteMpcIntsrc,
41 /// Failure to write MP ioapic entry.
42 #[error("Failure to write MP ioapic entry")]
43 WriteMpcIoapic,
44 /// Failure to write MP local interrupt source entry.
45 #[error("Failure to write MP local interrupt source entry")]
46 WriteMpcLintsrc,
47 /// Failure to write MP table header.
48 #[error("Failure to write MP table header")]
49 WriteMpcTable,
50 /// Failure to write the MP floating pointer.
51 #[error("Failure to write the MP floating pointer")]
52 WriteMpfIntel,
53 }
54
55 pub type Result<T> = result::Result<T, Error>;
56
57 // Convenience macro for making arrays of diverse character types.
58 macro_rules! char_array {
59 ($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
60 }
61
62 // Most of these variables are sourced from the Intel MP Spec 1.4.
63 const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
64 const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
65 const MPC_SPEC: i8 = 4;
66 const MPC_OEM: [c_char; 8] = char_array!(c_char; 'C', 'R', 'O', 'S', 'V', 'M', ' ', ' ');
67 const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
68 const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
69 const BUS_TYPE_PCI: [u8; 6] = char_array!(u8; 'P', 'C', 'I', ' ', ' ', ' ');
70 // source: linux/arch/x86/include/asm/apicdef.h
71 pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000;
72 // source: linux/arch/x86/include/asm/apicdef.h
73 pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000;
74 const APIC_VERSION: u8 = 0x14;
75 const CPU_STEPPING: u32 = 0x600;
76 const CPU_FEATURE_APIC: u32 = 0x200;
77 const CPU_FEATURE_FPU: u32 = 0x001;
78 const MPTABLE_START: u64 = 0x400 * 639; // Last 1k of Linux's 640k base RAM.
79
compute_checksum<T: AsBytes>(v: &T) -> u880 fn compute_checksum<T: AsBytes>(v: &T) -> u8 {
81 let mut checksum: u8 = 0;
82 for i in v.as_bytes() {
83 checksum = checksum.wrapping_add(*i);
84 }
85 checksum
86 }
87
mpf_intel_compute_checksum(v: &mpf_intel) -> u888 fn mpf_intel_compute_checksum(v: &mpf_intel) -> u8 {
89 let checksum = compute_checksum(v).wrapping_sub(v.checksum);
90 (!checksum).wrapping_add(1)
91 }
92
compute_mp_size(num_cpus: u8) -> usize93 fn compute_mp_size(num_cpus: u8) -> usize {
94 mem::size_of::<mpf_intel>()
95 + mem::size_of::<mpc_table>()
96 + mem::size_of::<mpc_cpu>() * (num_cpus as usize)
97 + mem::size_of::<mpc_ioapic>()
98 + mem::size_of::<mpc_bus>() * 2
99 + mem::size_of::<mpc_intsrc>()
100 + mem::size_of::<mpc_intsrc>() * 16
101 + mem::size_of::<mpc_lintsrc>() * 2
102 }
103
104 /// Performs setup of the MP table for the given `num_cpus`.
setup_mptable( mem: &GuestMemory, num_cpus: u8, pci_irqs: &[(PciAddress, u32, PciInterruptPin)], ) -> Result<()>105 pub fn setup_mptable(
106 mem: &GuestMemory,
107 num_cpus: u8,
108 pci_irqs: &[(PciAddress, u32, PciInterruptPin)],
109 ) -> Result<()> {
110 // Used to keep track of the next base pointer into the MP table.
111 let mut base_mp = GuestAddress(MPTABLE_START);
112
113 // Calculate ISA bus number in the system, report at least one PCI bus.
114 let isa_bus_id = match pci_irqs.iter().max_by_key(|v| v.0.bus) {
115 Some(pci_irq) => pci_irq.0.bus + 1,
116 _ => 1,
117 };
118 let mp_size = compute_mp_size(num_cpus);
119
120 // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
121 // overflow.
122 if let Some(end_mp) = base_mp.checked_add(mp_size as u64 - 1) {
123 if !mem.address_in_range(end_mp) {
124 return Err(Error::NotEnoughMemory);
125 }
126 } else {
127 return Err(Error::AddressOverflow);
128 }
129
130 mem.get_slice_at_addr(base_mp, mp_size)
131 .map_err(|_| Error::Clear)?
132 .write_bytes(0);
133
134 {
135 let size = mem::size_of::<mpf_intel>();
136 let mut mpf_intel = mpf_intel::default();
137 mpf_intel.signature = SMP_MAGIC_IDENT;
138 mpf_intel.length = 1;
139 mpf_intel.specification = 4;
140 mpf_intel.physptr = (base_mp.offset() + mem::size_of::<mpf_intel>() as u64) as u32;
141 mpf_intel.checksum = mpf_intel_compute_checksum(&mpf_intel);
142 mem.write_obj_at_addr(mpf_intel, base_mp)
143 .map_err(|_| Error::WriteMpfIntel)?;
144 base_mp = base_mp.unchecked_add(size as u64);
145 }
146
147 // We set the location of the mpc_table here but we can't fill it out until we have the length
148 // of the entire table later.
149 let table_base = base_mp;
150 base_mp = base_mp.unchecked_add(mem::size_of::<mpc_table>() as u64);
151
152 let mut checksum: u8 = 0;
153 let ioapicid: u8 = num_cpus + 1;
154
155 for cpu_id in 0..num_cpus {
156 let size = mem::size_of::<mpc_cpu>();
157 let mpc_cpu = mpc_cpu {
158 type_: MP_PROCESSOR as u8,
159 apicid: cpu_id,
160 apicver: APIC_VERSION,
161 cpuflag: CPU_ENABLED as u8
162 | if cpu_id == 0 {
163 CPU_BOOTPROCESSOR as u8
164 } else {
165 0
166 },
167 cpufeature: CPU_STEPPING,
168 featureflag: CPU_FEATURE_APIC | CPU_FEATURE_FPU,
169 ..Default::default()
170 };
171 mem.write_obj_at_addr(mpc_cpu, base_mp)
172 .map_err(|_| Error::WriteMpcCpu)?;
173 base_mp = base_mp.unchecked_add(size as u64);
174 checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu));
175 }
176 {
177 let size = mem::size_of::<mpc_ioapic>();
178 let mpc_ioapic = mpc_ioapic {
179 type_: MP_IOAPIC as u8,
180 apicid: ioapicid,
181 apicver: APIC_VERSION,
182 flags: MPC_APIC_USABLE as u8,
183 apicaddr: IO_APIC_DEFAULT_PHYS_BASE,
184 };
185 mem.write_obj_at_addr(mpc_ioapic, base_mp)
186 .map_err(|_| Error::WriteMpcIoapic)?;
187 base_mp = base_mp.unchecked_add(size as u64);
188 checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic));
189 }
190 for pci_bus_id in 0..isa_bus_id {
191 let size = mem::size_of::<mpc_bus>();
192 let mpc_bus = mpc_bus {
193 type_: MP_BUS as u8,
194 busid: pci_bus_id,
195 bustype: BUS_TYPE_PCI,
196 };
197 mem.write_obj_at_addr(mpc_bus, base_mp)
198 .map_err(|_| Error::WriteMpcBus)?;
199 base_mp = base_mp.unchecked_add(size as u64);
200 checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
201 }
202 {
203 let size = mem::size_of::<mpc_bus>();
204 let mpc_bus = mpc_bus {
205 type_: MP_BUS as u8,
206 busid: isa_bus_id,
207 bustype: BUS_TYPE_ISA,
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_intsrc>();
216 let mpc_intsrc = mpc_intsrc {
217 type_: MP_LINTSRC as u8,
218 irqtype: mp_irq_source_types_mp_INT as u8,
219 irqflag: MP_IRQDIR_DEFAULT as u16,
220 srcbus: isa_bus_id,
221 srcbusirq: 0,
222 dstapic: 0,
223 dstirq: 0,
224 };
225 mem.write_obj_at_addr(mpc_intsrc, base_mp)
226 .map_err(|_| Error::WriteMpcIntsrc)?;
227 base_mp = base_mp.unchecked_add(size as u64);
228 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
229 }
230 let sci_irq = super::X86_64_SCI_IRQ as u8;
231 // Per kvm_setup_default_irq_routing() in kernel
232 for i in (0..sci_irq).chain(std::iter::once(devices::cmos::RTC_IRQ)) {
233 let size = mem::size_of::<mpc_intsrc>();
234 let mpc_intsrc = mpc_intsrc {
235 type_: MP_INTSRC as u8,
236 irqtype: mp_irq_source_types_mp_INT as u8,
237 irqflag: MP_IRQDIR_DEFAULT as u16,
238 srcbus: isa_bus_id,
239 srcbusirq: i,
240 dstapic: ioapicid,
241 dstirq: i,
242 };
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 SCI interrupt before PCI interrupts. Set the SCI interrupt
249 // to be the default trigger/polarity of PCI bus, which is level/low.
250 // This setting can be changed in future if necessary.
251 {
252 let size = mem::size_of::<mpc_intsrc>();
253 let mpc_intsrc = mpc_intsrc {
254 type_: MP_INTSRC as u8,
255 irqtype: mp_irq_source_types_mp_INT as u8,
256 irqflag: (MP_IRQDIR_HIGH | MP_LEVEL_TRIGGER) as u16,
257 srcbus: isa_bus_id,
258 srcbusirq: sci_irq,
259 dstapic: ioapicid,
260 dstirq: sci_irq,
261 };
262 mem.write_obj_at_addr(mpc_intsrc, base_mp)
263 .map_err(|_| Error::WriteMpcIntsrc)?;
264 base_mp = base_mp.unchecked_add(size as u64);
265 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
266 }
267
268 // Insert PCI interrupts after platform IRQs.
269 for (address, irq_num, irq_pin) in pci_irqs.iter() {
270 let size = mem::size_of::<mpc_intsrc>();
271 let mpc_intsrc = mpc_intsrc {
272 type_: MP_INTSRC as u8,
273 irqtype: mp_irq_source_types_mp_INT as u8,
274 irqflag: MP_IRQDIR_DEFAULT as u16,
275 srcbus: address.bus,
276 srcbusirq: address.dev << 2 | irq_pin.to_mask() as u8,
277 dstapic: ioapicid,
278 dstirq: u8::try_from(*irq_num).map_err(|_| Error::WriteMpcIntsrc)?,
279 };
280 mem.write_obj_at_addr(mpc_intsrc, base_mp)
281 .map_err(|_| Error::WriteMpcIntsrc)?;
282 base_mp = base_mp.unchecked_add(size as u64);
283 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
284 }
285
286 let starting_isa_irq_num = pci_irqs
287 .iter()
288 .map(|(_, irq_num, _)| irq_num + 1)
289 .fold(super::X86_64_IRQ_BASE, u32::max) as u8;
290
291 // Finally insert ISA interrupts.
292 for i in starting_isa_irq_num..16 {
293 let size = mem::size_of::<mpc_intsrc>();
294 let mpc_intsrc = mpc_intsrc {
295 type_: MP_INTSRC as u8,
296 irqtype: mp_irq_source_types_mp_INT as u8,
297 irqflag: MP_IRQDIR_DEFAULT as u16,
298 srcbus: isa_bus_id,
299 srcbusirq: i,
300 dstapic: ioapicid,
301 dstirq: i,
302 };
303 mem.write_obj_at_addr(mpc_intsrc, base_mp)
304 .map_err(|_| Error::WriteMpcIntsrc)?;
305 base_mp = base_mp.unchecked_add(size as u64);
306 checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
307 }
308 {
309 let size = mem::size_of::<mpc_lintsrc>();
310 let mpc_lintsrc = mpc_lintsrc {
311 type_: MP_LINTSRC as u8,
312 irqtype: mp_irq_source_types_mp_ExtINT as u8,
313 irqflag: MP_IRQDIR_DEFAULT as u16,
314 srcbusid: isa_bus_id,
315 srcbusirq: 0,
316 destapic: 0,
317 destapiclint: 0,
318 };
319 mem.write_obj_at_addr(mpc_lintsrc, base_mp)
320 .map_err(|_| Error::WriteMpcLintsrc)?;
321 base_mp = base_mp.unchecked_add(size as u64);
322 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
323 }
324 {
325 let size = mem::size_of::<mpc_lintsrc>();
326 let mpc_lintsrc = mpc_lintsrc {
327 type_: MP_LINTSRC as u8,
328 irqtype: mp_irq_source_types_mp_NMI as u8,
329 irqflag: MP_IRQDIR_DEFAULT as u16,
330 srcbusid: isa_bus_id,
331 srcbusirq: 0,
332 destapic: 0xFF, // Per SeaBIOS
333 destapiclint: 1,
334 };
335 mem.write_obj_at_addr(mpc_lintsrc, base_mp)
336 .map_err(|_| Error::WriteMpcLintsrc)?;
337 base_mp = base_mp.unchecked_add(size as u64);
338 checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
339 }
340
341 // At this point we know the size of the mp_table.
342 let table_end = base_mp;
343
344 {
345 let mut mpc_table = mpc_table {
346 signature: MPC_SIGNATURE,
347 length: table_end.offset_from(table_base) as u16,
348 spec: MPC_SPEC,
349 oem: MPC_OEM,
350 productid: MPC_PRODUCT_ID,
351 lapic: APIC_DEFAULT_PHYS_BASE,
352 ..Default::default()
353 };
354 checksum = checksum.wrapping_add(compute_checksum(&mpc_table));
355 mpc_table.checksum = (!checksum).wrapping_add(1) as i8;
356 mem.write_obj_at_addr(mpc_table, table_base)
357 .map_err(|_| Error::WriteMpcTable)?;
358 }
359
360 Ok(())
361 }
362
363 #[cfg(test)]
364 mod tests {
365 use base::pagesize;
366
367 use super::*;
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