xref: /aosp_15_r20/external/crosvm/x86_64/src/mptable.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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