Skip to content

Commit ccf0b9e

Browse files
committed
vmm: Refactor IORT table generation to use structured data types
The current implementation is based on IORT spec revisition E.b [1]. [1] https://developer.arm.com/documentation/den0049/eb/?lang=en Fixes: cloud-hypervisor#7587 Signed-off-by: Bo Chen <bchen@crusoe.ai>
1 parent cde7856 commit ccf0b9e

File tree

1 file changed

+181
-71
lines changed

1 file changed

+181
-71
lines changed

vmm/src/acpi.rs

Lines changed: 181 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -514,86 +514,196 @@ fn create_dbg2_table(base_address: u64) -> Sdt {
514514
dbg2
515515
}
516516

517+
#[cfg(target_arch = "aarch64")]
518+
#[allow(dead_code)]
519+
#[repr(C, packed)]
520+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
521+
struct IortBodyBase {
522+
pub num_nodes: u32,
523+
pub offset_first_node: u32,
524+
_reserved: u32,
525+
}
526+
527+
#[cfg(target_arch = "aarch64")]
528+
#[allow(dead_code)]
529+
#[repr(C, packed)]
530+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
531+
struct IortNodeCommon {
532+
pub type_: u8,
533+
pub length: u16,
534+
pub revision: u8,
535+
pub node_id: u32,
536+
pub num_id_mappings: u32,
537+
pub id_mappings_array_offset: u32,
538+
}
539+
540+
#[cfg(target_arch = "aarch64")]
541+
#[allow(dead_code)]
542+
#[repr(C, packed)]
543+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
544+
struct IortIdMapping {
545+
pub input_base: u32,
546+
pub num_ids: u32,
547+
pub output_base: u32,
548+
pub output_reference: u32,
549+
pub flags: u32,
550+
}
551+
552+
#[cfg(target_arch = "aarch64")]
553+
#[allow(dead_code)]
554+
#[repr(C, packed)]
555+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
556+
struct IortMemoryAccessProperties {
557+
pub cca: u32,
558+
pub ah: u8,
559+
_reserved: u16,
560+
pub maf: u8,
561+
}
562+
563+
#[cfg(target_arch = "aarch64")]
564+
#[allow(dead_code)]
565+
#[repr(C, packed)]
566+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
567+
struct IortItsGroupBase {
568+
pub common: IortNodeCommon,
569+
pub its_count: u32,
570+
// GIC ITS identifiers follow: array of `u32`
571+
}
572+
573+
#[cfg(target_arch = "aarch64")]
574+
#[allow(dead_code)]
575+
#[repr(C, packed)]
576+
#[derive(Default, IntoBytes, Immutable, FromBytes)]
577+
struct IortPciRootComplexBase {
578+
pub common: IortNodeCommon,
579+
pub mem_access_props: IortMemoryAccessProperties,
580+
pub ats_attribute: u32,
581+
pub pci_segment_number: u32,
582+
pub memory_address_size_limit: u8,
583+
_reserved: [u8; 3],
584+
// ID mappings follow: array of `struct IortIdMapping`
585+
}
586+
587+
#[cfg(target_arch = "aarch64")]
588+
#[inline]
589+
fn align_to_8_bytes(len: usize) -> usize {
590+
(8 - (len % 8)) % 8
591+
}
592+
517593
#[cfg(target_arch = "aarch64")]
518594
// Generate IORT table based on Spec Revision E.b:
519595
// https://developer.arm.com/documentation/den0049/eb/?lang=en
520596
fn create_iort_table(pci_segments: &[PciSegment]) -> Sdt {
597+
const ACPI_IORT_HEADER_SIZE: u32 = 36;
598+
const ACPI_IORT_REVISION: u8 = 3;
521599
const ACPI_IORT_NODE_ITS_GROUP: u8 = 0x00;
522600
const ACPI_IORT_NODE_PCI_ROOT_COMPLEX: u8 = 0x02;
523-
const ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET: usize = 72;
524-
const ACPI_IORT_NODE_ROOT_COMPLEX_SIZE: usize = 60;
601+
602+
// IORT header
603+
let mut iort = Sdt::new(
604+
*b"IORT",
605+
ACPI_IORT_HEADER_SIZE,
606+
ACPI_IORT_REVISION,
607+
*b"CLOUDH",
608+
*b"CHIORT ",
609+
1,
610+
);
611+
assert_eq!(iort.len(), ACPI_IORT_HEADER_SIZE as usize);
525612

526613
// The IORT table contains:
527-
// - Header (size = 40)
528-
// - 1 x ITS Group Node (size = 24)
529-
// - N x Root Complex Node (N = number of pci segments, size = 60 x N)
530-
let iort_table_size: u32 = (ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET
531-
+ ACPI_IORT_NODE_ROOT_COMPLEX_SIZE * pci_segments.len())
532-
as u32;
533-
let mut iort = Sdt::new(*b"IORT", iort_table_size, 3, *b"CLOUDH", *b"CHIORT ", 1);
534-
iort.write(36, ((1 + pci_segments.len()) as u32).to_le());
535-
iort.write(40, (48u32).to_le());
536-
537-
// ITS group node
538-
iort.write(48, ACPI_IORT_NODE_ITS_GROUP);
539-
// Length of the ITS group node in bytes
540-
iort.write(49, (24u16).to_le());
541-
// Revision
542-
iort.write(51, (1u8).to_le());
543-
// ITS counts
544-
iort.write(64, (1u32).to_le());
545-
// GIC ITS Identity Array
546-
iort.write(68, (0u32).to_le()); // Value must match what's defined in MADT
547-
548-
// Root Complex Nodes
549-
for (i, segment) in pci_segments.iter().enumerate() {
550-
let node_offset: usize =
551-
ACPI_IORT_NODE_ROOT_COMPLEX_OFFSET + i * ACPI_IORT_NODE_ROOT_COMPLEX_SIZE;
552-
iort.write(node_offset, ACPI_IORT_NODE_PCI_ROOT_COMPLEX);
553-
// Length of the root complex node in bytes
554-
iort.write(
555-
node_offset + 1,
556-
(ACPI_IORT_NODE_ROOT_COMPLEX_SIZE as u16).to_le(),
557-
);
558-
// Revision
559-
iort.write(node_offset + 3, (3u8).to_le());
560-
// Node ID
561-
iort.write(node_offset + 4, (segment.id as u32).to_le());
562-
// Mapping counts
563-
iort.write(node_offset + 8, (1u32).to_le());
564-
// Offset from the start of the RC node to the start of its Array of ID mappings
565-
iort.write(node_offset + 12, (36u32).to_le());
566-
// Fully coherent device
567-
iort.write(node_offset + 16, (1u32).to_le());
568-
// CCA = CPM = DCAS = 1
569-
iort.write(node_offset + 23, 3u8);
570-
// PCI segment number
571-
iort.write(node_offset + 28, (segment.id as u32).to_le());
572-
// Memory address size limit
573-
iort.write(node_offset + 32, (64u8).to_le());
574-
575-
// From offset 32 onward is the space for ID mappings Array.
576-
// Now we have only one mapping.
577-
let mapping_offset: usize = node_offset + 36;
578-
// The lowest value in the input range
579-
iort.write(mapping_offset, (0u32).to_le());
580-
// The number of IDs in the range minus one:
581-
// This should cover all the devices of a segment:
582-
// 1 (bus) x 32 (devices) x 8 (functions) = 256
583-
// Note: Currently only 1 bus is supported in a segment.
584-
iort.write(mapping_offset + 4, (255_u32).to_le());
585-
// Output base maps to ITS device IDs which must match the
586-
// device ID encoding used in KVM MSI routing setup, which
587-
// shares the same limitation - only 1 bus per segment and
588-
// up to 256 segments.
589-
// See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484
614+
// - IortBodyBase
615+
// - 1 x ITS Group Node
616+
// - N x PCI Root Complex Node (N = number of pci segments)
617+
let num_nodes = (1 + pci_segments.len()) as u32;
618+
// First node is the ITS Group Node located right after the IORT Body Base
619+
let offset_its_node = iort.len() + std::mem::size_of::<IortBodyBase>();
620+
assert!(align_to_8_bytes(offset_its_node) == 0); // Ensure the ITS node is 8-byte aligned
621+
iort.append(IortBodyBase {
622+
num_nodes,
623+
offset_first_node: offset_its_node as u32,
624+
_reserved: 0,
625+
});
626+
assert!(iort.len() == offset_its_node);
627+
628+
// ITS Group Node contains:
629+
// - IortItsGroupBase
630+
// - ITS Identifiers Array: Array of u32 ITS IDs
631+
// Currently contains a single ITS with ID 0, which matches the
632+
// `translation_id` field of the `GisIts`` structure in the MADT table.
633+
let its_id_array = [0u32; 1];
634+
let its_count = its_id_array.len();
635+
let its_group_node_size =
636+
std::mem::size_of::<IortItsGroupBase>() + its_count * std::mem::size_of::<u32>();
637+
let padding = align_to_8_bytes(iort.len() + its_group_node_size);
638+
iort.append(IortItsGroupBase {
639+
common: IortNodeCommon {
640+
type_: ACPI_IORT_NODE_ITS_GROUP,
641+
length: (its_group_node_size + padding) as u16,
642+
revision: 1,
643+
node_id: 0, // todo
644+
num_id_mappings: 0,
645+
id_mappings_array_offset: 0,
646+
},
647+
its_count: its_count as u32,
648+
});
649+
iort.append(its_id_array);
650+
iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes
651+
652+
// Create PCI Root Complex Node for each PCI segment
653+
for segment in pci_segments.iter() {
654+
assert!(align_to_8_bytes(iort.len()) == 0); // Ensure each node is 8-byte aligned
655+
656+
// Each PCI Root Complex Node contains:
657+
// - IortPciRootComplexBase
658+
// - ID mapping Array: Array of IortIdMapping
659+
// Currently contains a single mapping that maps all device IDs
660+
// in the segment to the ITS Group Node.
661+
let num_id_mappings = 1;
662+
let node_size = std::mem::size_of::<IortPciRootComplexBase>()
663+
+ num_id_mappings * std::mem::size_of::<IortIdMapping>();
664+
let padding = align_to_8_bytes(iort.len() + node_size);
665+
iort.append(IortPciRootComplexBase {
666+
common: IortNodeCommon {
667+
type_: ACPI_IORT_NODE_PCI_ROOT_COMPLEX,
668+
length: (node_size + padding) as u16,
669+
revision: 3,
670+
node_id: segment.id as u32, // todo to avoid conflict with ITS node IDs
671+
num_id_mappings: num_id_mappings as u32,
672+
// ID mapping array starts right after `IortPciRootComplexBase`
673+
id_mappings_array_offset: std::mem::size_of::<IortPciRootComplexBase>() as u32,
674+
},
675+
mem_access_props: IortMemoryAccessProperties {
676+
cca: 1, // Fully coherent device
677+
ah: 0,
678+
_reserved: 0,
679+
maf: 3, // CPM = DCAS = 1
680+
},
681+
ats_attribute: 0,
682+
pci_segment_number: segment.id as u32,
683+
memory_address_size_limit: 64u8,
684+
_reserved: [0; 3],
685+
});
686+
// ID Mapping for this Root Complex
687+
// Maps 256 device IDs (1 bus × 32 devices × 8 functions)
590688
assert!(segment.id < 256, "Up to 256 PCI segments are supported.");
591-
iort.write(mapping_offset + 8, ((256 * segment.id) as u32).to_le());
592-
// id_mapping_array_output_reference should be
593-
// the ITS group node (the first node) if no SMMU
594-
iort.write(mapping_offset + 12, (48u32).to_le());
595-
// Flags
596-
iort.write(mapping_offset + 16, (0u32).to_le());
689+
iort.append(IortIdMapping {
690+
input_base: 0,
691+
// The number of IDs in the range minus one:
692+
// This should cover all the devices of a segment:
693+
// 1 (bus) x 32 (devices) x 8 (functions) = 256
694+
// Note: Currently only 1 bus is supported in a segment.
695+
num_ids: 255,
696+
// Output base maps to ITS device IDs which must match the
697+
// device ID encoding used in KVM MSI routing setup, which
698+
// shares the same limitation - only 1 bus per segment and
699+
// up to 256 segments.
700+
// See: https://github.com/cloud-hypervisor/cloud-hypervisor/commit/c9374d87ac453d49185aa7b734df089444166484
701+
output_base: (256 * segment.id) as u32,
702+
// Output reference node is the ITS group node as there is no SMMU node
703+
output_reference: offset_its_node as u32,
704+
flags: 0,
705+
});
706+
iort.append_slice(&vec![0u8; padding]); // Add padding to align to 8 bytes
597707
}
598708

599709
iort.update_checksum();

0 commit comments

Comments
 (0)