Skip to main content

loader/
mptable.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Intel MP 1.4 table construction for x86 guests.
5
6mod spec;
7
8use spec::BUS_TYPE_ISA;
9use spec::BusEntry;
10use spec::CONFIG_TABLE_SIGNATURE;
11use spec::CPU_FLAG_BOOTSTRAP;
12use spec::CPU_FLAG_ENABLED;
13use spec::ConfigTableHeader;
14use spec::ENTRY_TYPE_BUS;
15use spec::ENTRY_TYPE_IO_APIC;
16use spec::ENTRY_TYPE_IO_INTERRUPT;
17use spec::ENTRY_TYPE_PROCESSOR;
18use spec::FLOATING_POINTER_SIGNATURE;
19use spec::FloatingPointer;
20use spec::INTERRUPT_FLAG_ACTIVE_HIGH;
21use spec::INTERRUPT_FLAG_LEVEL_TRIGGERED;
22use spec::INTERRUPT_TYPE_INT;
23use spec::IO_APIC_FLAG_ENABLED;
24use spec::IoApicEntry;
25use spec::IoInterruptEntry;
26use spec::PARAGRAPH_SIZE;
27use spec::ProcessorEntry;
28use spec::SPEC_REV_1_4;
29use thiserror::Error;
30use x86defs::apic::APIC_BASE_ADDRESS;
31use zerocopy::FromBytes;
32use zerocopy::FromZeros;
33use zerocopy::Immutable;
34use zerocopy::IntoBytes;
35use zerocopy::KnownLayout;
36
37/// Guest-physical address of the MP floating pointer.
38pub const MP_FLOATING_POINTER_ADDR: usize = 0;
39/// Guest-physical address of the MP configuration table.
40pub const MP_CONFIG_TABLE_ADDR: usize = 0x400;
41
42/// Configuration table OEM ID, space-padded.
43const OEM_ID: [u8; 8] = *b"OPENVMM ";
44/// Configuration table product ID, space-padded.
45const PRODUCT_ID: [u8; 12] = *b"MICROVM     ";
46/// Low byte of the local APIC version register: an integrated xAPIC
47/// (`0x1X`) of version `0x14`, as every backend reports.
48const LOCAL_APIC_VERSION: u8 = 0x14;
49/// Version of the emulated Intel 82093AA I/O APIC, matching
50/// `chipset::ioapic`.
51const IO_APIC_VERSION: u8 = 0x11;
52/// Base address of the I/O APIC, matching
53/// `chipset::ioapic::IOAPIC_DEVICE_MMIO_REGION_BASE_ADDRESS`.
54const IO_APIC_ADDRESS: u32 = 0xfec0_0000;
55/// The I/O APIC ID register holds four bits.
56const MAX_IO_APIC_ID: u8 = 0xf;
57/// Bus ID of the ISA bus, the only bus described.
58const ISA_BUS_ID: u8 = 0;
59/// Number of ISA IRQs.
60const ISA_IRQ_COUNT: u8 = 16;
61/// ISA IRQ 2 cascades the secondary PIC and has no I/O APIC route.
62const ISA_CASCADE_IRQ: u8 = 2;
63/// ISA IRQ of the PIT.
64const PIT_IRQ: u8 = 0;
65/// I/O APIC input that the PIT's ISA IRQ is wired to.
66const PIT_IO_APIC_INTIN: u8 = 2;
67/// Number of routed ISA IRQs: every ISA IRQ except the cascade.
68const ISA_ROUTED_IRQ_COUNT: usize = ISA_IRQ_COUNT as usize - 1;
69
70/// The fixed entries that follow the processor entries: the ISA bus, the
71/// I/O APIC, and the route of each ISA IRQ to the I/O APIC.
72#[repr(C)]
73#[derive(Debug, Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes)]
74struct IsaInterruptRouting {
75    bus: BusEntry,
76    io_apic: IoApicEntry,
77    interrupts: [IoInterruptEntry; ISA_ROUTED_IRQ_COUNT],
78}
79
80/// Number of entries in [`IsaInterruptRouting`].
81const ISA_INTERRUPT_ROUTING_ENTRY_COUNT: usize = 2 + ISA_ROUTED_IRQ_COUNT;
82
83/// Guest-visible processor and interrupt data represented by MP tables.
84#[derive(Debug, Clone, Copy)]
85pub struct MpTableConfig<'a> {
86    /// APIC IDs in virtual-processor order. The first entry is the BSP. The
87    /// IDs must be 0, 1, 2, and so on, for 1 to 15 processors, because the
88    /// I/O APIC takes the next ID, which must fit in four bits.
89    pub apic_ids: &'a [u32],
90    /// ISA IRQs, 0 through 15, described as active-high, level-triggered.
91    pub level_triggered_irqs: &'a [u32],
92}
93
94/// Fully constructed MP 1.4 data.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct MpTables {
97    /// The 16-byte MP floating pointer.
98    pub floating_pointer: [u8; 16],
99    /// The MP configuration table referenced by the floating pointer.
100    pub configuration_table: Vec<u8>,
101}
102
103/// MP table construction error.
104#[derive(Debug, Error, PartialEq, Eq)]
105pub enum Error {
106    /// At least one processor must be described.
107    #[error("MP processor topology must contain at least one processor")]
108    NoProcessors,
109    /// APIC IDs must be contiguous and start at zero.
110    #[error("MP processor {index} has APIC ID {apic_id}; expected {expected}")]
111    InvalidApicId {
112        /// Processor index.
113        index: usize,
114        /// Supplied APIC ID.
115        apic_id: u32,
116        /// Required APIC ID.
117        expected: u32,
118    },
119    /// MP 1.4 processor entries contain only an 8-bit APIC ID.
120    #[error("MP processor {index} APIC ID {apic_id} does not fit in an MP 1.4 entry")]
121    ApicIdTooLarge {
122        /// Processor index.
123        index: usize,
124        /// Supplied APIC ID.
125        apic_id: u32,
126    },
127    /// The entry count or table length cannot be represented.
128    #[error("MP table has too many processor or interrupt entries")]
129    TooManyEntries,
130    /// The I/O APIC ID register exposes only four ID bits.
131    #[error("MP topology has {processor_count} processors, leaving no representable I/O APIC ID")]
132    NoIoApicId {
133        /// Number of processor APIC IDs already in use.
134        processor_count: usize,
135    },
136    /// ISA IRQs are limited to 0 through 15.
137    #[error("MP table IRQ {0} is outside the ISA range")]
138    InvalidIrq(u32),
139}
140
141/// Builds the MP floating pointer and configuration table.
142pub fn build(config: &MpTableConfig<'_>) -> Result<MpTables, Error> {
143    let configuration_table = build_config_table(config)?;
144    let mut floating_pointer = FloatingPointer {
145        signature: FLOATING_POINTER_SIGNATURE,
146        config_table_address: (MP_CONFIG_TABLE_ADDR as u32).into(),
147        length: (size_of::<FloatingPointer>() / PARAGRAPH_SIZE) as u8,
148        spec_rev: SPEC_REV_1_4,
149        // A configuration table is present, and the IMCR is absent, which
150        // selects virtual wire mode.
151        ..FromZeros::new_zeroed()
152    };
153    floating_pointer.checksum = checksum(&[floating_pointer.as_bytes()]);
154    Ok(MpTables {
155        floating_pointer: zerocopy::transmute!(floating_pointer),
156        configuration_table,
157    })
158}
159
160/// Builds an Intel MP 1.4 configuration table.
161pub fn build_config_table(config: &MpTableConfig<'_>) -> Result<Vec<u8>, Error> {
162    if config.apic_ids.is_empty() {
163        return Err(Error::NoProcessors);
164    }
165    if let Some(&irq) = config
166        .level_triggered_irqs
167        .iter()
168        .find(|irq| **irq >= ISA_IRQ_COUNT.into())
169    {
170        return Err(Error::InvalidIrq(irq));
171    }
172    let entry_count = u16::try_from(
173        config
174            .apic_ids
175            .len()
176            .checked_add(ISA_INTERRUPT_ROUTING_ENTRY_COUNT)
177            .ok_or(Error::TooManyEntries)?,
178    )
179    .map_err(|_| Error::TooManyEntries)?;
180
181    let processors = config
182        .apic_ids
183        .iter()
184        .enumerate()
185        .map(|(index, &apic_id)| {
186            let expected = u32::try_from(index).map_err(|_| Error::TooManyEntries)?;
187            if apic_id != expected {
188                return Err(Error::InvalidApicId {
189                    index,
190                    apic_id,
191                    expected,
192                });
193            }
194            let local_apic_id =
195                u8::try_from(apic_id).map_err(|_| Error::ApicIdTooLarge { index, apic_id })?;
196            let bootstrap = if index == 0 { CPU_FLAG_BOOTSTRAP } else { 0 };
197            Ok(ProcessorEntry {
198                entry_type: ENTRY_TYPE_PROCESSOR,
199                local_apic_id,
200                local_apic_version: LOCAL_APIC_VERSION,
201                cpu_flags: CPU_FLAG_ENABLED | bootstrap,
202                ..FromZeros::new_zeroed()
203            })
204        })
205        .collect::<Result<Vec<_>, _>>()?;
206
207    let io_apic_id = u8::try_from(config.apic_ids.len())
208        .ok()
209        .filter(|id| *id <= MAX_IO_APIC_ID)
210        .ok_or(Error::NoIoApicId {
211            processor_count: config.apic_ids.len(),
212        })?;
213    let mut routing = IsaInterruptRouting {
214        bus: BusEntry {
215            entry_type: ENTRY_TYPE_BUS,
216            bus_id: ISA_BUS_ID,
217            bus_type: BUS_TYPE_ISA,
218        },
219        io_apic: IoApicEntry {
220            entry_type: ENTRY_TYPE_IO_APIC,
221            io_apic_id,
222            io_apic_version: IO_APIC_VERSION,
223            flags: IO_APIC_FLAG_ENABLED,
224            address: IO_APIC_ADDRESS.into(),
225        },
226        interrupts: FromZeros::new_zeroed(),
227    };
228    let routed_irqs = (0..ISA_IRQ_COUNT).filter(|irq| *irq != ISA_CASCADE_IRQ);
229    for (entry, irq) in routing.interrupts.iter_mut().zip(routed_irqs) {
230        let flags = if config.level_triggered_irqs.contains(&irq.into()) {
231            INTERRUPT_FLAG_ACTIVE_HIGH | INTERRUPT_FLAG_LEVEL_TRIGGERED
232        } else {
233            0
234        };
235        *entry = IoInterruptEntry {
236            entry_type: ENTRY_TYPE_IO_INTERRUPT,
237            interrupt_type: INTERRUPT_TYPE_INT,
238            flags: flags.into(),
239            source_bus_id: ISA_BUS_ID,
240            source_bus_irq: irq,
241            destination_io_apic_id: io_apic_id,
242            destination_io_apic_intin: if irq == PIT_IRQ {
243                PIT_IO_APIC_INTIN
244            } else {
245                irq
246            },
247        };
248    }
249
250    let table_len = size_of::<ConfigTableHeader>()
251        + processors.as_bytes().len()
252        + size_of::<IsaInterruptRouting>();
253    let mut header = ConfigTableHeader {
254        signature: CONFIG_TABLE_SIGNATURE,
255        base_table_length: u16::try_from(table_len)
256            .map_err(|_| Error::TooManyEntries)?
257            .into(),
258        spec_rev: SPEC_REV_1_4,
259        oem_id: OEM_ID,
260        product_id: PRODUCT_ID,
261        entry_count: entry_count.into(),
262        local_apic_address: APIC_BASE_ADDRESS.into(),
263        ..FromZeros::new_zeroed()
264    };
265    header.checksum = checksum(&[header.as_bytes(), processors.as_bytes(), routing.as_bytes()]);
266    Ok([header.as_bytes(), processors.as_bytes(), routing.as_bytes()].concat())
267}
268
269/// Returns the byte that makes the sum of `parts` and itself zero.
270fn checksum(parts: &[&[u8]]) -> u8 {
271    0u8.wrapping_sub(
272        parts
273            .iter()
274            .flat_map(|part| part.iter())
275            .fold(0u8, |sum, byte| sum.wrapping_add(*byte)),
276    )
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use test_with_tracing::test;
283
284    #[test]
285    fn builds_valid_checksums_and_processor_entries() {
286        for processor_count in [1usize, 2, 4, 8, 15] {
287            let apic_ids = (0..processor_count as u32).collect::<Vec<_>>();
288            let tables = build(&MpTableConfig {
289                apic_ids: &apic_ids,
290                level_triggered_irqs: &[4, 5, 6, 7],
291            })
292            .unwrap();
293
294            let floating_pointer =
295                FloatingPointer::read_from_bytes(&tables.floating_pointer).unwrap();
296            assert_eq!(floating_pointer.signature, FLOATING_POINTER_SIGNATURE);
297            assert_eq!(
298                floating_pointer.config_table_address.get(),
299                MP_CONFIG_TABLE_ADDR as u32
300            );
301            assert_eq!(checksum(&[&tables.floating_pointer]), 0);
302
303            let table = &tables.configuration_table;
304            assert_eq!(checksum(&[table]), 0);
305            let (header, rest) = ConfigTableHeader::ref_from_prefix(table).unwrap();
306            assert_eq!(usize::from(header.base_table_length.get()), table.len());
307            assert_eq!(
308                usize::from(header.entry_count.get()),
309                processor_count + ISA_INTERRUPT_ROUTING_ENTRY_COUNT
310            );
311            let (processors, rest) =
312                <[ProcessorEntry]>::ref_from_prefix_with_elems(rest, processor_count).unwrap();
313            for (index, processor) in processors.iter().enumerate() {
314                assert_eq!(processor.entry_type, ENTRY_TYPE_PROCESSOR);
315                assert_eq!(processor.local_apic_id, index as u8);
316                let bootstrap = if index == 0 { CPU_FLAG_BOOTSTRAP } else { 0 };
317                assert_eq!(processor.cpu_flags, CPU_FLAG_ENABLED | bootstrap);
318            }
319            let routing = IsaInterruptRouting::ref_from_bytes(rest).unwrap();
320            assert_eq!(routing.io_apic.entry_type, ENTRY_TYPE_IO_APIC);
321            assert_eq!(routing.io_apic.io_apic_id, processor_count as u8);
322            for entry in &routing.interrupts {
323                assert_eq!(entry.entry_type, ENTRY_TYPE_IO_INTERRUPT);
324                assert_eq!(entry.destination_io_apic_id, processor_count as u8);
325            }
326        }
327    }
328
329    /// Pins the guest-visible bytes of two processors with ISA IRQ 5
330    /// active-high and level-triggered.
331    #[test]
332    fn builds_expected_bytes() {
333        let tables = build(&MpTableConfig {
334            apic_ids: &[0, 1],
335            level_triggered_irqs: &[5],
336        })
337        .unwrap();
338        let hex = |bytes: &[u8]| {
339            bytes
340                .iter()
341                .map(|byte| format!("{byte:02x}"))
342                .collect::<String>()
343        };
344        assert_eq!(
345            hex(&tables.floating_pointer),
346            "5f4d505f0004000001049c0000000000"
347        );
348        assert_eq!(
349            hex(&tables.configuration_table),
350            concat!(
351                // Header: "PCMP", 220 bytes, revision 1.4, checksum, OEM ID,
352                // and product ID.
353                "50434d50dc00047b4f50454e564d4d204d4943524f564d2020202020",
354                // No OEM table, 19 entries, the local APIC at 0xfee00000,
355                // and no extended table.
356                "00000000000013000000e0fe00000000",
357                // Processors: the BSP (enabled, BP) and one AP (enabled).
358                "0000140300000000000000000000000000000000",
359                "0001140100000000000000000000000000000000",
360                // ISA bus 0 and the I/O APIC (ID 2, version 0x11, enabled).
361                "0100495341202020",
362                "020211010000c0fe",
363                // ISA IRQs 0-15 except 2; IRQ 0 goes to INTIN 2.
364                "0300000000000202",
365                "0300000000010201",
366                "0300000000030203",
367                "0300000000040204",
368                "03000d0000050205",
369                "0300000000060206",
370                "0300000000070207",
371                "0300000000080208",
372                "0300000000090209",
373                "03000000000a020a",
374                "03000000000b020b",
375                "03000000000c020c",
376                "03000000000d020d",
377                "03000000000e020e",
378                "03000000000f020f",
379            )
380        );
381    }
382
383    #[test]
384    fn rejects_invalid_inputs() {
385        assert_eq!(
386            build(&MpTableConfig {
387                apic_ids: &[],
388                level_triggered_irqs: &[],
389            }),
390            Err(Error::NoProcessors)
391        );
392        assert!(matches!(
393            build(&MpTableConfig {
394                apic_ids: &[0, 2],
395                level_triggered_irqs: &[],
396            }),
397            Err(Error::InvalidApicId { .. })
398        ));
399        assert_eq!(
400            build(&MpTableConfig {
401                apic_ids: &[0],
402                level_triggered_irqs: &[16],
403            }),
404            Err(Error::InvalidIrq(16))
405        );
406        let apic_ids = (0..16).collect::<Vec<_>>();
407        assert_eq!(
408            build(&MpTableConfig {
409                apic_ids: &apic_ids,
410                level_triggered_irqs: &[],
411            }),
412            Err(Error::NoIoApicId {
413                processor_count: 16
414            })
415        );
416        let apic_ids = (0..=256).collect::<Vec<_>>();
417        assert!(matches!(
418            build(&MpTableConfig {
419                apic_ids: &apic_ids,
420                level_triggered_irqs: &[],
421            }),
422            Err(Error::ApicIdTooLarge {
423                index: 256,
424                apic_id: 256
425            })
426        ));
427    }
428}