1mod 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
37pub const MP_FLOATING_POINTER_ADDR: usize = 0;
39pub const MP_CONFIG_TABLE_ADDR: usize = 0x400;
41
42const OEM_ID: [u8; 8] = *b"OPENVMM ";
44const PRODUCT_ID: [u8; 12] = *b"MICROVM ";
46const LOCAL_APIC_VERSION: u8 = 0x14;
49const IO_APIC_VERSION: u8 = 0x11;
52const IO_APIC_ADDRESS: u32 = 0xfec0_0000;
55const MAX_IO_APIC_ID: u8 = 0xf;
57const ISA_BUS_ID: u8 = 0;
59const ISA_IRQ_COUNT: u8 = 16;
61const ISA_CASCADE_IRQ: u8 = 2;
63const PIT_IRQ: u8 = 0;
65const PIT_IO_APIC_INTIN: u8 = 2;
67const ISA_ROUTED_IRQ_COUNT: usize = ISA_IRQ_COUNT as usize - 1;
69
70#[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
80const ISA_INTERRUPT_ROUTING_ENTRY_COUNT: usize = 2 + ISA_ROUTED_IRQ_COUNT;
82
83#[derive(Debug, Clone, Copy)]
85pub struct MpTableConfig<'a> {
86 pub apic_ids: &'a [u32],
90 pub level_triggered_irqs: &'a [u32],
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct MpTables {
97 pub floating_pointer: [u8; 16],
99 pub configuration_table: Vec<u8>,
101}
102
103#[derive(Debug, Error, PartialEq, Eq)]
105pub enum Error {
106 #[error("MP processor topology must contain at least one processor")]
108 NoProcessors,
109 #[error("MP processor {index} has APIC ID {apic_id}; expected {expected}")]
111 InvalidApicId {
112 index: usize,
114 apic_id: u32,
116 expected: u32,
118 },
119 #[error("MP processor {index} APIC ID {apic_id} does not fit in an MP 1.4 entry")]
121 ApicIdTooLarge {
122 index: usize,
124 apic_id: u32,
126 },
127 #[error("MP table has too many processor or interrupt entries")]
129 TooManyEntries,
130 #[error("MP topology has {processor_count} processors, leaving no representable I/O APIC ID")]
132 NoIoApicId {
133 processor_count: usize,
135 },
136 #[error("MP table IRQ {0} is outside the ISA range")]
138 InvalidIrq(u32),
139}
140
141pub 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 ..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
160pub 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
269fn 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 #[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 "50434d50dc00047b4f50454e564d4d204d4943524f564d2020202020",
354 "00000000000013000000e0fe00000000",
357 "0000140300000000000000000000000000000000",
359 "0001140100000000000000000000000000000000",
360 "0100495341202020",
362 "020211010000c0fe",
363 "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}