Skip to content

Commit 6fb7faa

Browse files
committed
arm: support MSI-X on ARM
Add support for ITS device which provides support for MSI interrupts on ARM architecture. This is currently supported only on systems with GICv3 interrupt controller Signed-off-by: Babis Chalios <[email protected]>
1 parent 709dec1 commit 6fb7faa

File tree

9 files changed

+328
-35
lines changed

9 files changed

+328
-35
lines changed

src/vmm/src/arch/aarch64/fdt.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ use crate::device_manager::mmio::MMIODeviceInfo;
2222
use crate::device_manager::pci_mngr::PciDevices;
2323
use crate::devices::acpi::vmgenid::{VMGENID_MEM_SIZE, VmGenId};
2424
use crate::initrd::InitrdConfig;
25+
use crate::logger::info;
2526
use crate::vstate::memory::{Address, GuestMemory, GuestMemoryMmap};
2627

2728
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
2829
const GIC_PHANDLE: u32 = 1;
2930
// This is a value for uniquely identifying the FDT node containing the clock definition.
3031
const CLOCK_PHANDLE: u32 = 2;
32+
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
33+
const MSI_PHANDLE: u32 = 3;
3134
// You may be wondering why this big value?
3235
// This phandle is used to uniquely identify the FDT nodes containing cache information. Each cpu
3336
// can have a variable number of caches, some of these caches may be shared with other cpus.
@@ -302,6 +305,17 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &GICDevice) -> Result<(), Fd
302305
];
303306

304307
fdt.property_array_u32("interrupts", &gic_intr)?;
308+
309+
if let Some(msi_properties) = gic_device.msi_properties() {
310+
info!("msi_properties: {msi_properties:#?}");
311+
let msic_node = fdt.begin_node("msic")?;
312+
fdt.property_string("compatible", "arm,gic-v3-its")?;
313+
fdt.property_null("msi-controller")?;
314+
fdt.property_u32("phandle", MSI_PHANDLE)?;
315+
fdt.property_array_u64("reg", msi_properties)?;
316+
fdt.end_node(msic_node)?;
317+
}
318+
305319
fdt.end_node(interrupt)?;
306320

307321
Ok(())
@@ -471,6 +485,21 @@ fn create_pci_nodes(fdt: &mut FdtWriter, pci_devices: &PciDevices) -> Result<(),
471485
(MEM_64BIT_DEVICES_SIZE >> 32) as u32, // Range size
472486
((MEM_64BIT_DEVICES_SIZE & 0xffff_ffff) >> 32) as u32,
473487
];
488+
489+
// See kernel document Documentation/devicetree/bindings/pci/pci-msi.txt
490+
let msi_map = [
491+
// rid-base: A single cell describing the first RID matched by the entry.
492+
0x0,
493+
// msi-controller: A single phandle to an MSI controller.
494+
MSI_PHANDLE,
495+
// msi-base: An msi-specifier describing the msi-specifier produced for the
496+
// first RID matched by the entry.
497+
segment.id as u32,
498+
// length: A single cell describing how many consecutive RIDs are matched
499+
// following the rid-base.
500+
0x100,
501+
];
502+
474503
let pci_node = fdt.begin_node(&pci_node_name)?;
475504

476505
fdt.property_string("compatible", "pci-host-ecam-generic")?;
@@ -491,6 +520,9 @@ fn create_pci_nodes(fdt: &mut FdtWriter, pci_devices: &PciDevices) -> Result<(),
491520
fdt.property_null("interrupt-map")?;
492521
fdt.property_null("interrupt-map-mask")?;
493522
fdt.property_null("dma-coherent")?;
523+
fdt.property_array_u32("msi-map", &msi_map)?;
524+
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
525+
494526
Ok(fdt.end_node(pci_node)?)
495527
}
496528

src/vmm/src/arch/aarch64/gic/gicv2/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ impl GICv2 {
6868
GICv2::get_cpu_addr(),
6969
GICv2::get_cpu_size(),
7070
],
71+
msi_properties: None,
7172
vcpu_count,
73+
its_device: None,
7274
})
7375
}
7476

@@ -82,7 +84,7 @@ impl GICv2 {
8284

8385
pub fn init_device_attributes(gic_device: &Self) -> Result<(), GicError> {
8486
// Setting up the distributor attribute.
85-
// We are placing the GIC below 1GB so we need to substract the size of the distributor.
87+
// We are placing the GIC below 1GB so we need to subtract the size of the distributor.
8688
Self::set_device_attribute(
8789
gic_device.device_fd(),
8890
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,

src/vmm/src/arch/aarch64/gic/gicv2/regs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub fn save_state(fd: &DeviceFd, mpidrs: &[u64]) -> Result<GicState, GicError> {
2222
Ok(GicState {
2323
dist: dist_regs::get_dist_regs(fd)?,
2424
gic_vcpu_states: vcpu_states,
25+
..Default::default()
2526
})
2627
}
2728

src/vmm/src/arch/aarch64/gic/gicv3/mod.rs

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ impl std::ops::Deref for GICv3 {
1818
}
1919
}
2020

21+
impl std::ops::DerefMut for GICv3 {
22+
fn deref_mut(&mut self) -> &mut Self::Target {
23+
&mut self.0
24+
}
25+
}
26+
2127
impl GICv3 {
2228
// Unfortunately bindgen omits defines that are based on other defines.
2329
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
2430
const SZ_64K: u64 = 0x0001_0000;
2531
const KVM_VGIC_V3_DIST_SIZE: u64 = GICv3::SZ_64K;
2632
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * GICv3::SZ_64K);
33+
const GIC_V3_ITS_SIZE: u64 = 0x2_0000;
2734

2835
// Device trees specific constants
2936
const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
@@ -48,6 +55,16 @@ impl GICv3 {
4855
vcpu_count * GICv3::KVM_VGIC_V3_REDIST_SIZE
4956
}
5057

58+
/// Get the MSI address
59+
fn get_msi_address(vcpu_count: u64) -> u64 {
60+
Self::get_redists_addr(vcpu_count) - GICv3::GIC_V3_ITS_SIZE
61+
}
62+
63+
/// Get the MSI size
64+
const fn get_msi_size() -> u64 {
65+
GICv3::GIC_V3_ITS_SIZE
66+
}
67+
5168
pub const VERSION: u32 = kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3;
5269

5370
pub fn fdt_compatibility(&self) -> &str {
@@ -59,30 +76,43 @@ impl GICv3 {
5976
}
6077

6178
/// Create the GIC device object
62-
pub fn create_device(fd: DeviceFd, vcpu_count: u64) -> Self {
63-
GICv3(super::GIC {
64-
fd,
79+
pub fn create_device(vm: &VmFd, vcpu_count: u64) -> Result<Self, GicError> {
80+
// Create the GIC device
81+
let mut gic_device = kvm_bindings::kvm_create_device {
82+
type_: Self::VERSION,
83+
fd: 0,
84+
flags: 0,
85+
};
86+
87+
let gic_fd = vm
88+
.create_device(&mut gic_device)
89+
.map_err(GicError::CreateGIC)?;
90+
91+
Ok(GICv3(super::GIC {
92+
fd: gic_fd,
6593
properties: [
6694
GICv3::get_dist_addr(),
6795
GICv3::get_dist_size(),
6896
GICv3::get_redists_addr(vcpu_count),
6997
GICv3::get_redists_size(vcpu_count),
7098
],
99+
msi_properties: Some([GICv3::get_msi_address(vcpu_count), GICv3::get_msi_size()]),
71100
vcpu_count,
72-
})
101+
its_device: None,
102+
}))
73103
}
74104

75105
pub fn save_device(&self, mpidrs: &[u64]) -> Result<GicState, GicError> {
76-
regs::save_state(&self.fd, mpidrs)
106+
regs::save_state(&self.fd, self.its_device.as_ref().unwrap(), mpidrs)
77107
}
78108

79109
pub fn restore_device(&self, mpidrs: &[u64], state: &GicState) -> Result<(), GicError> {
80-
regs::restore_state(&self.fd, mpidrs, state)
110+
regs::restore_state(&self.fd, self.its_device.as_ref().unwrap(), mpidrs, state)
81111
}
82112

83113
pub fn init_device_attributes(gic_device: &Self) -> Result<(), GicError> {
84114
// Setting up the distributor attribute.
85-
// We are placing the GIC below 1GB so we need to substract the size of the distributor.
115+
// We are placing the GIC below 1GB so we need to subtract the size of the distributor.
86116
Self::set_device_attribute(
87117
gic_device.device_fd(),
88118
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -104,25 +134,45 @@ impl GICv3 {
104134
Ok(())
105135
}
106136

107-
/// Initialize a GIC device
108-
pub fn init_device(vm: &VmFd) -> Result<DeviceFd, GicError> {
109-
let mut gic_device = kvm_bindings::kvm_create_device {
110-
type_: Self::VERSION,
137+
fn init_its(vm: &VmFd, gic_device: &mut Self) -> Result<(), GicError> {
138+
// ITS part attributes
139+
let mut its_device = kvm_bindings::kvm_create_device {
140+
type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS,
111141
fd: 0,
112142
flags: 0,
113143
};
114144

115-
vm.create_device(&mut gic_device)
116-
.map_err(GicError::CreateGIC)
145+
let its_fd = vm
146+
.create_device(&mut its_device)
147+
.map_err(GicError::CreateGIC)?;
148+
149+
// Setting up the ITS attributes
150+
Self::set_device_attribute(
151+
&its_fd,
152+
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
153+
u64::from(kvm_bindings::KVM_VGIC_ITS_ADDR_TYPE),
154+
&Self::get_msi_address(gic_device.vcpu_count()) as *const u64 as u64,
155+
0,
156+
)?;
157+
158+
Self::set_device_attribute(
159+
&its_fd,
160+
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
161+
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
162+
0,
163+
0,
164+
)?;
165+
166+
gic_device.its_device = Some(its_fd);
167+
Ok(())
117168
}
118169

119170
/// Method to initialize the GIC device
120171
pub fn create(vm: &VmFd, vcpu_count: u64) -> Result<Self, GicError> {
121-
let vgic_fd = Self::init_device(vm)?;
122-
123-
let device = Self::create_device(vgic_fd, vcpu_count);
172+
let mut device = Self::create_device(vm, vcpu_count)?;
124173

125174
Self::init_device_attributes(&device)?;
175+
Self::init_its(vm, &mut device)?;
126176

127177
Self::finalize_device(&device)?;
128178

@@ -180,20 +230,41 @@ impl GICv3 {
180230
}
181231
}
182232

233+
/// Function that saves/restores ITS tables into guest RAM.
234+
pub fn gicv3_its_tables_access(its_device: &DeviceFd, save: bool) -> Result<(), GicError> {
235+
let attr = if save {
236+
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_SAVE_TABLES)
237+
} else {
238+
u64::from(kvm_bindings::KVM_DEV_ARM_ITS_RESTORE_TABLES)
239+
};
240+
241+
let init_gic_attr = kvm_bindings::kvm_device_attr {
242+
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
243+
attr,
244+
addr: 0,
245+
flags: 0,
246+
};
247+
248+
its_device.set_device_attr(&init_gic_attr).map_err(|err| {
249+
GicError::DeviceAttribute(err, true, kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL)
250+
})
251+
}
252+
183253
/// Function that flushes
184254
/// RDIST pending tables into guest RAM.
185255
///
186256
/// The tables get flushed to guest RAM whenever the VM gets stopped.
187-
fn save_pending_tables(fd: &DeviceFd) -> Result<(), GicError> {
257+
fn save_pending_tables(gic_device: &DeviceFd, its_device: &DeviceFd) -> Result<(), GicError> {
188258
let init_gic_attr = kvm_bindings::kvm_device_attr {
189259
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
190260
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
191261
addr: 0,
192262
flags: 0,
193263
};
194-
fd.set_device_attr(&init_gic_attr).map_err(|err| {
264+
gic_device.set_device_attr(&init_gic_attr).map_err(|err| {
195265
GicError::DeviceAttribute(err, true, kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL)
196-
})
266+
})?;
267+
gicv3_its_tables_access(its_device, true)
197268
}
198269

199270
#[cfg(test)]
@@ -211,11 +282,11 @@ mod tests {
211282
let kvm = Kvm::new().unwrap();
212283
let vm = kvm.create_vm().unwrap();
213284
let gic = create_gic(&vm, 1, Some(GICVersion::GICV3)).expect("Cannot create gic");
214-
save_pending_tables(gic.device_fd()).unwrap();
285+
save_pending_tables(gic.device_fd(), gic.its_fd().unwrap()).unwrap();
215286

216287
unsafe { libc::close(gic.device_fd().as_raw_fd()) };
217288

218-
let res = save_pending_tables(gic.device_fd());
289+
let res = save_pending_tables(gic.device_fd(), gic.its_fd().unwrap());
219290
assert_eq!(
220291
format!("{:?}", res.unwrap_err()),
221292
"DeviceAttribute(Error(9), true, 4)"

0 commit comments

Comments
 (0)