x86_64/
mptable.rs

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
5use 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    /// 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    /// More than the maximum number of supported CPUs.
33    #[error("{0} is more than the maximum number of supported CPUs")]
34    TooManyCpus(u8),
35    /// Failure to write MP bus entry.
36    #[error("Failure to write MP bus entry")]
37    WriteMpcBus,
38    /// Failure to write MP CPU entry.
39    #[error("Failure to write MP CPU entry")]
40    WriteMpcCpu,
41    /// Failure to write MP interrupt source entry.
42    #[error("Failure to write MP interrupt source entry")]
43    WriteMpcIntsrc,
44    /// Failure to write MP ioapic entry.
45    #[error("Failure to write MP ioapic entry")]
46    WriteMpcIoapic,
47    /// Failure to write MP local interrupt source entry.
48    #[error("Failure to write MP local interrupt source entry")]
49    WriteMpcLintsrc,
50    /// Failure to write MP table header.
51    #[error("Failure to write MP table header")]
52    WriteMpcTable,
53    /// Failure to write the MP floating pointer.
54    #[error("Failure to write the MP floating pointer")]
55    WriteMpfIntel,
56}
57
58pub type Result<T> = result::Result<T, Error>;
59
60// Most of these variables are sourced from the Intel MP Spec 1.4.
61const 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   ";
68// source: linux/arch/x86/include/asm/apicdef.h
69pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000;
70// source: linux/arch/x86/include/asm/apicdef.h
71pub 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;
76/// Place the MP Floating Pointer Structure in the last kilobyte of base memory (639K-640K).
77const MP_FLOATING_POINTER_ADDR: u64 = 0x400 * 640 - size_of::<mpf_intel>() as u64;
78/// Reserve the last 6K of low memory (below 640K) just before the MP Floating Pointer Structure for
79/// the rest of the MP Table.
80pub 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
106/// Performs setup of the MP table for the given `num_cpus`.
107pub fn setup_mptable(
108    mem: &GuestMemory,
109    num_cpus: u8,
110    pci_irqs: &[(PciAddress, u32, PciInterruptPin)],
111) -> Result<()> {
112    // Write the MP Floating Pointer structure pointing at `MPTABLE_RANGE`. This structure must be
113    // in one of a few pre-defined memory areas so the OS can find it; we choose to place it in the
114    // last kilobyte of low system memory (639K-640K).
115    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    // Used to keep track of the next base pointer into the MP table.
128    let mut base_mp = GuestAddress(MPTABLE_RANGE.start);
129
130    // Calculate ISA bus number in the system, report at least one PCI bus.
131    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    // The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
138    // overflow.
139    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    // We set the location of the mpc_table here but we can't fill it out until we have the length
152    // of the entire table later.
153    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    // Per kvm_setup_default_irq_routing() in kernel
238    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    // Insert SCI interrupt before PCI interrupts. Set the SCI interrupt
255    // to be the default trigger/polarity of PCI bus, which is level/low.
256    // This setting can be changed in future if necessary.
257    {
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    // Insert PCI interrupts after platform IRQs.
275    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    // Finally insert ISA interrupts.
298    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, // Per SeaBIOS
339            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    // At this point we know the size of the mp_table.
348    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}