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