1use std::convert::TryFrom;
6use std::mem;
7use std::result;
8
9use devices::PciAddress;
10use devices::PciInterruptPin;
11use remain::sorted;
12use resources::AddressRange;
13use thiserror::Error;
14use vm_memory::GuestAddress;
15use vm_memory::GuestMemory;
16use zerocopy::IntoBytes;
17
18use crate::mpspec::*;
19
20#[sorted]
21#[derive(Error, Debug)]
22pub enum Error {
23 #[error("The MP table has too little address space to be stored")]
25 AddressOverflow,
26 #[error("Failure while zeroing out the memory for the MP table")]
28 Clear,
29 #[error("There was too little guest memory to store the MP table")]
31 NotEnoughMemory,
32 #[error("{0} is more than the maximum number of supported CPUs")]
34 TooManyCpus(u8),
35 #[error("Failure to write MP bus entry")]
37 WriteMpcBus,
38 #[error("Failure to write MP CPU entry")]
40 WriteMpcCpu,
41 #[error("Failure to write MP interrupt source entry")]
43 WriteMpcIntsrc,
44 #[error("Failure to write MP ioapic entry")]
46 WriteMpcIoapic,
47 #[error("Failure to write MP local interrupt source entry")]
49 WriteMpcLintsrc,
50 #[error("Failure to write MP table header")]
52 WriteMpcTable,
53 #[error("Failure to write the MP floating pointer")]
55 WriteMpfIntel,
56}
57
58pub type Result<T> = result::Result<T, Error>;
59
60const SMP_MAGIC_IDENT: [u8; 4] = *b"_MP_";
62const MPC_SIGNATURE: [u8; 4] = *b"PCMP";
63const MPC_SPEC: i8 = 4;
64const MPC_OEM: [u8; 8] = *b"CROSVM ";
65const MPC_PRODUCT_ID: [u8; 12] = *b"000000000000";
66const BUS_TYPE_ISA: [u8; 6] = *b"ISA ";
67const BUS_TYPE_PCI: [u8; 6] = *b"PCI ";
68pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000;
70pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000;
72const APIC_VERSION: u8 = 0x14;
73const CPU_STEPPING: u32 = 0x600;
74const CPU_FEATURE_APIC: u32 = 0x200;
75const CPU_FEATURE_FPU: u32 = 0x001;
76const MP_FLOATING_POINTER_ADDR: u64 = 0x400 * 640 - size_of::<mpf_intel>() as u64;
78pub const MPTABLE_RANGE: AddressRange =
81 AddressRange::from_start_and_end(0x400 * 634, MP_FLOATING_POINTER_ADDR - 1);
82
83fn compute_checksum(v: &[u8]) -> u8 {
84 let mut checksum: u8 = 0;
85 for i in v {
86 checksum = checksum.wrapping_add(*i);
87 }
88 checksum
89}
90
91fn mpf_intel_compute_checksum(v: &mpf_intel) -> u8 {
92 let checksum = compute_checksum(v.as_bytes()).wrapping_sub(v.checksum);
93 (!checksum).wrapping_add(1)
94}
95
96fn compute_mp_size(num_cpus: u8) -> usize {
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
106pub fn setup_mptable(
108 mem: &GuestMemory,
109 num_cpus: u8,
110 pci_irqs: &[(PciAddress, u32, PciInterruptPin)],
111) -> Result<()> {
112 let mut mpf_intel = mpf_intel::default();
116 mpf_intel.signature = SMP_MAGIC_IDENT;
117 mpf_intel.length = 1;
118 mpf_intel.specification = 4;
119 mpf_intel.physptr = MPTABLE_RANGE
120 .start
121 .try_into()
122 .map_err(|_| Error::AddressOverflow)?;
123 mpf_intel.checksum = mpf_intel_compute_checksum(&mpf_intel);
124 mem.write_obj_at_addr(mpf_intel, GuestAddress(MP_FLOATING_POINTER_ADDR))
125 .map_err(|_| Error::WriteMpfIntel)?;
126
127 let mut base_mp = GuestAddress(MPTABLE_RANGE.start);
129
130 let isa_bus_id = match pci_irqs.iter().max_by_key(|v| v.0.bus) {
132 Some(pci_irq) => pci_irq.0.bus + 1,
133 _ => 1,
134 };
135 let mp_size = compute_mp_size(num_cpus);
136
137 if let Some(end_mp) = base_mp.checked_add(mp_size as u64 - 1) {
140 if !mem.address_in_range(end_mp) || !MPTABLE_RANGE.contains(end_mp.0) {
141 return Err(Error::NotEnoughMemory);
142 }
143 } else {
144 return Err(Error::AddressOverflow);
145 }
146
147 mem.get_slice_at_addr(base_mp, mp_size)
148 .map_err(|_| Error::Clear)?
149 .write_bytes(0);
150
151 let table_base = base_mp;
154 base_mp = base_mp.unchecked_add(mem::size_of::<mpc_table>() as u64);
155
156 let mut checksum: u8 = 0;
157 let ioapicid: u8 = num_cpus
158 .checked_add(1)
159 .ok_or(Error::TooManyCpus(num_cpus))?;
160
161 for cpu_id in 0..num_cpus {
162 let size = mem::size_of::<mpc_cpu>();
163 let mpc_cpu = mpc_cpu {
164 type_: MP_PROCESSOR as u8,
165 apicid: cpu_id,
166 apicver: APIC_VERSION,
167 cpuflag: CPU_ENABLED as u8
168 | if cpu_id == 0 {
169 CPU_BOOTPROCESSOR as u8
170 } else {
171 0
172 },
173 cpufeature: CPU_STEPPING,
174 featureflag: CPU_FEATURE_APIC | CPU_FEATURE_FPU,
175 ..Default::default()
176 };
177 mem.write_obj_at_addr(mpc_cpu, base_mp)
178 .map_err(|_| Error::WriteMpcCpu)?;
179 base_mp = base_mp.unchecked_add(size as u64);
180 checksum = checksum.wrapping_add(compute_checksum(mpc_cpu.as_bytes()));
181 }
182 {
183 let size = mem::size_of::<mpc_ioapic>();
184 let mpc_ioapic = mpc_ioapic {
185 type_: MP_IOAPIC as u8,
186 apicid: ioapicid,
187 apicver: APIC_VERSION,
188 flags: MPC_APIC_USABLE as u8,
189 apicaddr: IO_APIC_DEFAULT_PHYS_BASE,
190 };
191 mem.write_obj_at_addr(mpc_ioapic, base_mp)
192 .map_err(|_| Error::WriteMpcIoapic)?;
193 base_mp = base_mp.unchecked_add(size as u64);
194 checksum = checksum.wrapping_add(compute_checksum(mpc_ioapic.as_bytes()));
195 }
196 for pci_bus_id in 0..isa_bus_id {
197 let size = mem::size_of::<mpc_bus>();
198 let mpc_bus = mpc_bus {
199 type_: MP_BUS as u8,
200 busid: pci_bus_id,
201 bustype: BUS_TYPE_PCI,
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.as_bytes()));
207 }
208 {
209 let size = mem::size_of::<mpc_bus>();
210 let mpc_bus = mpc_bus {
211 type_: MP_BUS as u8,
212 busid: isa_bus_id,
213 bustype: BUS_TYPE_ISA,
214 };
215 mem.write_obj_at_addr(mpc_bus, base_mp)
216 .map_err(|_| Error::WriteMpcBus)?;
217 base_mp = base_mp.unchecked_add(size as u64);
218 checksum = checksum.wrapping_add(compute_checksum(mpc_bus.as_bytes()));
219 }
220 {
221 let size = mem::size_of::<mpc_intsrc>();
222 let mpc_intsrc = mpc_intsrc {
223 type_: MP_LINTSRC as u8,
224 irqtype: mp_irq_source_types_mp_INT as u8,
225 irqflag: MP_IRQDIR_DEFAULT as u16,
226 srcbus: isa_bus_id,
227 srcbusirq: 0,
228 dstapic: 0,
229 dstirq: 0,
230 };
231 mem.write_obj_at_addr(mpc_intsrc, base_mp)
232 .map_err(|_| Error::WriteMpcIntsrc)?;
233 base_mp = base_mp.unchecked_add(size as u64);
234 checksum = checksum.wrapping_add(compute_checksum(mpc_intsrc.as_bytes()));
235 }
236 let sci_irq = super::X86_64_SCI_IRQ as u8;
237 for i in (0..sci_irq).chain(std::iter::once(devices::cmos::RTC_IRQ)) {
239 let size = mem::size_of::<mpc_intsrc>();
240 let mpc_intsrc = mpc_intsrc {
241 type_: MP_INTSRC as u8,
242 irqtype: mp_irq_source_types_mp_INT as u8,
243 irqflag: MP_IRQDIR_DEFAULT as u16,
244 srcbus: isa_bus_id,
245 srcbusirq: i,
246 dstapic: ioapicid,
247 dstirq: i,
248 };
249 mem.write_obj_at_addr(mpc_intsrc, base_mp)
250 .map_err(|_| Error::WriteMpcIntsrc)?;
251 base_mp = base_mp.unchecked_add(size as u64);
252 checksum = checksum.wrapping_add(compute_checksum(mpc_intsrc.as_bytes()));
253 }
254 {
258 let size = mem::size_of::<mpc_intsrc>();
259 let mpc_intsrc = mpc_intsrc {
260 type_: MP_INTSRC as u8,
261 irqtype: mp_irq_source_types_mp_INT as u8,
262 irqflag: (MP_IRQDIR_HIGH | MP_LEVEL_TRIGGER) as u16,
263 srcbus: isa_bus_id,
264 srcbusirq: sci_irq,
265 dstapic: ioapicid,
266 dstirq: sci_irq,
267 };
268 mem.write_obj_at_addr(mpc_intsrc, base_mp)
269 .map_err(|_| Error::WriteMpcIntsrc)?;
270 base_mp = base_mp.unchecked_add(size as u64);
271 checksum = checksum.wrapping_add(compute_checksum(mpc_intsrc.as_bytes()));
272 }
273
274 for (address, irq_num, irq_pin) in pci_irqs.iter() {
276 let size = mem::size_of::<mpc_intsrc>();
277 let mpc_intsrc = mpc_intsrc {
278 type_: MP_INTSRC as u8,
279 irqtype: mp_irq_source_types_mp_INT as u8,
280 irqflag: MP_IRQDIR_DEFAULT as u16,
281 srcbus: address.bus,
282 srcbusirq: address.dev << 2 | irq_pin.to_mask() as u8,
283 dstapic: ioapicid,
284 dstirq: u8::try_from(*irq_num).map_err(|_| Error::WriteMpcIntsrc)?,
285 };
286 mem.write_obj_at_addr(mpc_intsrc, base_mp)
287 .map_err(|_| Error::WriteMpcIntsrc)?;
288 base_mp = base_mp.unchecked_add(size as u64);
289 checksum = checksum.wrapping_add(compute_checksum(mpc_intsrc.as_bytes()));
290 }
291
292 let starting_isa_irq_num = pci_irqs
293 .iter()
294 .map(|(_, irq_num, _)| irq_num + 1)
295 .fold(super::X86_64_IRQ_BASE, u32::max) as u8;
296
297 for i in starting_isa_irq_num..16 {
299 let size = mem::size_of::<mpc_intsrc>();
300 let mpc_intsrc = mpc_intsrc {
301 type_: MP_INTSRC as u8,
302 irqtype: mp_irq_source_types_mp_INT as u8,
303 irqflag: MP_IRQDIR_DEFAULT as u16,
304 srcbus: isa_bus_id,
305 srcbusirq: i,
306 dstapic: ioapicid,
307 dstirq: i,
308 };
309 mem.write_obj_at_addr(mpc_intsrc, base_mp)
310 .map_err(|_| Error::WriteMpcIntsrc)?;
311 base_mp = base_mp.unchecked_add(size as u64);
312 checksum = checksum.wrapping_add(compute_checksum(mpc_intsrc.as_bytes()));
313 }
314 {
315 let size = mem::size_of::<mpc_lintsrc>();
316 let mpc_lintsrc = mpc_lintsrc {
317 type_: MP_LINTSRC as u8,
318 irqtype: mp_irq_source_types_mp_ExtINT as u8,
319 irqflag: MP_IRQDIR_DEFAULT as u16,
320 srcbusid: isa_bus_id,
321 srcbusirq: 0,
322 destapic: 0,
323 destapiclint: 0,
324 };
325 mem.write_obj_at_addr(mpc_lintsrc, base_mp)
326 .map_err(|_| Error::WriteMpcLintsrc)?;
327 base_mp = base_mp.unchecked_add(size as u64);
328 checksum = checksum.wrapping_add(compute_checksum(mpc_lintsrc.as_bytes()));
329 }
330 {
331 let size = mem::size_of::<mpc_lintsrc>();
332 let mpc_lintsrc = mpc_lintsrc {
333 type_: MP_LINTSRC as u8,
334 irqtype: mp_irq_source_types_mp_NMI as u8,
335 irqflag: MP_IRQDIR_DEFAULT as u16,
336 srcbusid: isa_bus_id,
337 srcbusirq: 0,
338 destapic: 0xFF, destapiclint: 1,
340 };
341 mem.write_obj_at_addr(mpc_lintsrc, base_mp)
342 .map_err(|_| Error::WriteMpcLintsrc)?;
343 base_mp = base_mp.unchecked_add(size as u64);
344 checksum = checksum.wrapping_add(compute_checksum(mpc_lintsrc.as_bytes()));
345 }
346
347 let table_end = base_mp;
349
350 {
351 let mut mpc_table = mpc_table {
352 signature: MPC_SIGNATURE,
353 length: table_end.offset_from(table_base) as u16,
354 spec: MPC_SPEC,
355 oem: MPC_OEM,
356 productid: MPC_PRODUCT_ID,
357 lapic: APIC_DEFAULT_PHYS_BASE,
358 ..Default::default()
359 };
360 checksum = checksum.wrapping_add(compute_checksum(mpc_table.as_bytes()));
361 mpc_table.checksum = (!checksum).wrapping_add(1) as i8;
362 mem.write_obj_at_addr(mpc_table, table_base)
363 .map_err(|_| Error::WriteMpcTable)?;
364 }
365
366 Ok(())
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 fn test_guest_mem() -> GuestMemory {
374 GuestMemory::new(&[(GuestAddress(0), 640 * 1024)]).unwrap()
375 }
376
377 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]
389 fn bounds_check() {
390 let num_cpus = 4;
391 let mem = test_guest_mem();
392
393 setup_mptable(&mem, num_cpus, &[]).unwrap();
394 }
395
396 #[test]
397 fn bounds_check_fails() {
398 let num_cpus = 255;
399 let mem = test_guest_mem();
400
401 assert!(setup_mptable(&mem, num_cpus, &[]).is_err());
402 }
403
404 #[test]
405 fn mpf_intel_checksum() {
406 let num_cpus = 1;
407 let mem = test_guest_mem();
408
409 setup_mptable(&mem, num_cpus, &[]).unwrap();
410
411 let mpf_intel = mem
412 .read_obj_from_addr(GuestAddress(MP_FLOATING_POINTER_ADDR))
413 .unwrap();
414
415 assert_eq!(mpf_intel_compute_checksum(&mpf_intel), mpf_intel.checksum);
416 }
417
418 #[test]
419 fn mpc_table_checksum() {
420 let num_cpus = 4;
421 let mem = test_guest_mem();
422
423 setup_mptable(&mem, num_cpus, &[]).unwrap();
424
425 let mpf_intel: mpf_intel = mem
426 .read_obj_from_addr(GuestAddress(MP_FLOATING_POINTER_ADDR))
427 .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]
442 fn cpu_entry_count() {
443 const MAX_CPUS: u8 = 0xff;
444 let mem = test_guest_mem();
445
446 for i in 0..MAX_CPUS {
447 setup_mptable(&mem, i, &[]).unwrap();
448
449 let mpf_intel: mpf_intel = mem
450 .read_obj_from_addr(GuestAddress(MP_FLOATING_POINTER_ADDR))
451 .unwrap();
452 let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
453 let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
454 let mpc_end = mpc_offset.checked_add(mpc_table.length as u64).unwrap();
455
456 let mut entry_offset = mpc_offset
457 .checked_add(mem::size_of::<mpc_table>() as u64)
458 .unwrap();
459 let mut cpu_count = 0;
460 while entry_offset < mpc_end {
461 let entry_type: u8 = mem.read_obj_from_addr(entry_offset).unwrap();
462 entry_offset = entry_offset
463 .checked_add(table_entry_size(entry_type) as u64)
464 .unwrap();
465 assert!(entry_offset <= mpc_end);
466 if entry_type as u32 == MP_PROCESSOR {
467 cpu_count += 1;
468 }
469 }
470 assert_eq!(cpu_count, i);
471 }
472 }
473}