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