Skip to content

Commit f0faa7f

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. In order to make saving/restore of ITS state work properly, we need to change the order in which we restore redistributor register GICR_CTLR. We need to make sure that this register is restored last. Otherwise, restoring GICR_PROPBASER doesn't have any effect and ITS depends on it in order to save/restore ITS tables to/from guest memory. Signed-off-by: Babis Chalios <[email protected]>
1 parent 506b361 commit f0faa7f

File tree

11 files changed

+298
-35
lines changed

11 files changed

+298
-35
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use crate::vstate::memory::{Address, GuestMemory, GuestMemoryMmap};
2828
const GIC_PHANDLE: u32 = 1;
2929
// This is a value for uniquely identifying the FDT node containing the clock definition.
3030
const CLOCK_PHANDLE: u32 = 2;
31+
// This is a value for uniquely identifying the FDT node declaring the MSI controller.
32+
const MSI_PHANDLE: u32 = 3;
3133
// You may be wondering why this big value?
3234
// This phandle is used to uniquely identify the FDT nodes containing cache information. Each cpu
3335
// can have a variable number of caches, some of these caches may be shared with other cpus.
@@ -302,6 +304,16 @@ fn create_gic_node(fdt: &mut FdtWriter, gic_device: &GICDevice) -> Result<(), Fd
302304
];
303305

304306
fdt.property_array_u32("interrupts", &gic_intr)?;
307+
308+
if let Some(msi_properties) = gic_device.msi_properties() {
309+
let msic_node = fdt.begin_node("msic")?;
310+
fdt.property_string("compatible", "arm,gic-v3-its")?;
311+
fdt.property_null("msi-controller")?;
312+
fdt.property_u32("phandle", MSI_PHANDLE)?;
313+
fdt.property_array_u64("reg", msi_properties)?;
314+
fdt.end_node(msic_node)?;
315+
}
316+
305317
fdt.end_node(interrupt)?;
306318

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

476503
fdt.property_string("compatible", "pci-host-ecam-generic")?;
@@ -491,6 +518,9 @@ fn create_pci_nodes(fdt: &mut FdtWriter, pci_devices: &PciDevices) -> Result<(),
491518
fdt.property_null("interrupt-map")?;
492519
fdt.property_null("interrupt-map-mask")?;
493520
fdt.property_null("dma-coherent")?;
521+
fdt.property_array_u32("msi-map", &msi_map)?;
522+
fdt.property_u32("msi-parent", MSI_PHANDLE)?;
523+
494524
Ok(fdt.end_node(pci_node)?)
495525
}
496526

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: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
mod regs;
4+
pub mod regs;
55

66
use kvm_ioctls::{DeviceFd, VmFd};
77

@@ -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

@@ -184,14 +234,14 @@ impl GICv3 {
184234
/// RDIST pending tables into guest RAM.
185235
///
186236
/// The tables get flushed to guest RAM whenever the VM gets stopped.
187-
fn save_pending_tables(fd: &DeviceFd) -> Result<(), GicError> {
237+
fn save_pending_tables(gic_device: &DeviceFd) -> Result<(), GicError> {
188238
let init_gic_attr = kvm_bindings::kvm_device_attr {
189239
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
190240
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
191241
addr: 0,
192242
flags: 0,
193243
};
194-
fd.set_device_attr(&init_gic_attr).map_err(|err| {
244+
gic_device.set_device_attr(&init_gic_attr).map_err(|err| {
195245
GicError::DeviceAttribute(err, true, kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL)
196246
})
197247
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use kvm_bindings::{
5+
KVM_DEV_ARM_ITS_RESTORE_TABLES, KVM_DEV_ARM_ITS_SAVE_TABLES, KVM_DEV_ARM_VGIC_GRP_CTRL,
6+
KVM_DEV_ARM_VGIC_GRP_ITS_REGS,
7+
};
8+
use kvm_ioctls::DeviceFd;
9+
use serde::{Deserialize, Serialize};
10+
11+
use crate::arch::aarch64::gic::GicError;
12+
13+
// ITS registers that we want to preserve across snapshots
14+
const GITS_CTLR: u32 = 0x0000;
15+
const GITS_IIDR: u32 = 0x0004;
16+
const GITS_CBASER: u32 = 0x0080;
17+
const GITS_CWRITER: u32 = 0x0088;
18+
const GITS_CREADR: u32 = 0x0090;
19+
const GITS_BASER: u32 = 0x0100;
20+
21+
fn set_device_attribute(
22+
its_device: &DeviceFd,
23+
group: u32,
24+
attr: u32,
25+
val: u64,
26+
) -> Result<(), GicError> {
27+
let gicv3_its_attr = kvm_bindings::kvm_device_attr {
28+
group,
29+
attr: attr as u64,
30+
addr: &val as *const u64 as u64,
31+
flags: 0,
32+
};
33+
34+
its_device
35+
.set_device_attr(&gicv3_its_attr)
36+
.map_err(|err| GicError::DeviceAttribute(err, true, group))
37+
}
38+
39+
fn get_device_attribute(its_device: &DeviceFd, group: u32, attr: u32) -> Result<u64, GicError> {
40+
let mut val = 0;
41+
42+
let mut gicv3_its_attr = kvm_bindings::kvm_device_attr {
43+
group,
44+
attr: attr as u64,
45+
addr: &mut val as *mut u64 as u64,
46+
flags: 0,
47+
};
48+
49+
// SAFETY: gicv3_its_attr.addr is safe to write to.
50+
unsafe { its_device.get_device_attr(&mut gicv3_its_attr) }
51+
.map_err(|err| GicError::DeviceAttribute(err, false, group))?;
52+
53+
Ok(val)
54+
}
55+
56+
fn its_read_register(its_fd: &DeviceFd, attr: u32) -> Result<u64, GicError> {
57+
get_device_attribute(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, attr)
58+
}
59+
60+
fn its_set_register(its_fd: &DeviceFd, attr: u32, val: u64) -> Result<(), GicError> {
61+
set_device_attribute(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, attr, val)
62+
}
63+
64+
pub fn its_save_tables(its_fd: &DeviceFd) -> Result<(), GicError> {
65+
set_device_attribute(
66+
its_fd,
67+
KVM_DEV_ARM_VGIC_GRP_CTRL,
68+
KVM_DEV_ARM_ITS_SAVE_TABLES,
69+
0,
70+
)
71+
}
72+
73+
pub fn its_restore_tables(its_fd: &DeviceFd) -> Result<(), GicError> {
74+
set_device_attribute(
75+
its_fd,
76+
KVM_DEV_ARM_VGIC_GRP_CTRL,
77+
KVM_DEV_ARM_ITS_RESTORE_TABLES,
78+
0,
79+
)
80+
}
81+
82+
/// ITS registers that we save/restore during snapshot
83+
#[derive(Debug, Default, Serialize, Deserialize)]
84+
pub struct ItsRegisterState {
85+
iidr: u64,
86+
cbaser: u64,
87+
creadr: u64,
88+
cwriter: u64,
89+
baser: [u64; 8],
90+
ctlr: u64,
91+
}
92+
93+
impl ItsRegisterState {
94+
/// Save ITS state
95+
pub fn save(its_fd: &DeviceFd) -> Result<Self, GicError> {
96+
let mut state = ItsRegisterState::default();
97+
98+
for i in 0..8 {
99+
state.baser[i as usize] = its_read_register(its_fd, GITS_BASER + i * 8)?;
100+
}
101+
state.ctlr = its_read_register(its_fd, GITS_CTLR)?;
102+
state.cbaser = its_read_register(its_fd, GITS_CBASER)?;
103+
state.creadr = its_read_register(its_fd, GITS_CREADR)?;
104+
state.cwriter = its_read_register(its_fd, GITS_CWRITER)?;
105+
state.iidr = its_read_register(its_fd, GITS_IIDR)?;
106+
107+
Ok(state)
108+
}
109+
110+
/// Restore ITS state
111+
///
112+
/// We need to restore ITS registers in a very specific order for things to work. Take a look
113+
/// at:
114+
/// https://elixir.bootlin.com/linux/v6.1.141/source/Documentation/virt/kvm/devices/arm-vgic-its.rst#L60
115+
/// and
116+
/// https://elixir.bootlin.com/linux/v6.1.141/source/Documentation/virt/kvm/devices/arm-vgic-its.rst#L123
117+
///
118+
/// for more details, but TL;DR is:
119+
///
120+
/// We need to restore GITS_CBASER, GITS_CREADER, GITS_CWRITER, GITS_BASER and GITS_IIDR
121+
/// registers before restoring ITS tables from guest memory. We also need to set GITS_CTLR
122+
/// last.
123+
pub fn restore(&self, its_fd: &DeviceFd) -> Result<(), GicError> {
124+
its_set_register(its_fd, GITS_IIDR, self.iidr)?;
125+
its_set_register(its_fd, GITS_CBASER, self.cbaser)?;
126+
its_set_register(its_fd, GITS_CREADR, self.creadr)?;
127+
its_set_register(its_fd, GITS_CWRITER, self.cwriter)?;
128+
for i in 0..8 {
129+
its_set_register(its_fd, GITS_BASER + i * 8, self.baser[i as usize])?;
130+
}
131+
// We need to restore saved ITS tables before restoring GITS_CTLR
132+
its_restore_tables(its_fd)?;
133+
its_set_register(its_fd, GITS_CTLR, self.ctlr)
134+
}
135+
}

0 commit comments

Comments
 (0)