Skip to content

Commit a6c6a1f

Browse files
dianpopaSamuel Ortiz
authored and
Samuel Ortiz
committed
arch64: add necessary kvm ioctls
Tested that cargo test is functional on aarch64 too. Signed-off-by: Diana Popa <[email protected]>
1 parent 47a2525 commit a6c6a1f

File tree

8 files changed

+250
-12
lines changed

8 files changed

+250
-12
lines changed

src/ioctls/device.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::fs::File;
5+
use std::io;
6+
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
7+
8+
use kvm_bindings::kvm_device_attr;
9+
10+
use ioctls::Result;
11+
use kvm_ioctls::KVM_SET_DEVICE_ATTR;
12+
use sys_ioctl::ioctl_with_ref;
13+
14+
/// Wrapper over the file descriptor obtained when creating an emulated device in the kernel.
15+
pub struct DeviceFd {
16+
fd: File,
17+
}
18+
19+
impl DeviceFd {
20+
/// Sets a specified piece of device configuration and/or state.
21+
///
22+
/// See the documentation for `KVM_SET_DEVICE_ATTR`.
23+
/// # Arguments
24+
///
25+
/// * `device_attr` - The device attribute to be set.
26+
///
27+
pub fn set_device_attr(&self, device_attr: &kvm_device_attr) -> Result<()> {
28+
let ret = unsafe { ioctl_with_ref(self, KVM_SET_DEVICE_ATTR(), device_attr) };
29+
if ret != 0 {
30+
return Err(io::Error::last_os_error());
31+
}
32+
Ok(())
33+
}
34+
}
35+
36+
/// Helper function for creating a new device.
37+
pub fn new_device(dev_fd: File) -> DeviceFd {
38+
DeviceFd { fd: dev_fd }
39+
}
40+
41+
impl AsRawFd for DeviceFd {
42+
fn as_raw_fd(&self) -> RawFd {
43+
self.fd.as_raw_fd()
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
use ioctls::system::Kvm;
51+
use kvm_bindings::{
52+
kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3, kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_20,
53+
kvm_device_type_KVM_DEV_TYPE_VFIO, KVM_CREATE_DEVICE_TEST, KVM_DEV_VFIO_GROUP,
54+
KVM_DEV_VFIO_GROUP_ADD,
55+
};
56+
use std::io::{Error, ErrorKind};
57+
58+
#[test]
59+
fn test_create_device() {
60+
let kvm = Kvm::new().unwrap();
61+
let vm = kvm.create_vm().unwrap();
62+
63+
let mut gic_device = kvm_bindings::kvm_create_device {
64+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
65+
type_: kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3,
66+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
67+
type_: kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_20,
68+
fd: 0,
69+
flags: KVM_CREATE_DEVICE_TEST,
70+
};
71+
// This fails on x86_64 because there is no VGIC there.
72+
// This fails on aarch64 as it does not use MPIC (MultiProcessor Interrupt Controller), it uses
73+
// the VGIC.
74+
assert!(vm.create_device(&mut gic_device).is_err());
75+
76+
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
77+
gic_device.type_ = kvm_device_type_KVM_DEV_TYPE_VFIO;
78+
} else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) {
79+
gic_device.type_ = kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3;
80+
}
81+
let device_fd = vm
82+
.create_device(&mut gic_device)
83+
.expect("Cannot create KVM device");
84+
85+
let dist_attr = kvm_bindings::kvm_device_attr {
86+
group: KVM_DEV_VFIO_GROUP,
87+
attr: KVM_DEV_VFIO_GROUP_ADD as u64,
88+
addr: 0x0,
89+
flags: 0,
90+
};
91+
device_fd.set_device_attr(&dist_attr);
92+
// We are just creating a test device. Creating a real device would make the CI dependent
93+
// on host configuration (like having /dev/vfio). We expect this to fail.
94+
assert!(device_fd.set_device_attr(&dist_attr).is_err());
95+
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), 25);
96+
}
97+
}

src/ioctls/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
use kvm_bindings::kvm_run;
2-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
3-
use kvm_bindings::{kvm_cpuid2, kvm_cpuid_entry2};
41
use std::io;
52
use std::mem::size_of;
63
use std::os::unix::io::AsRawFd;
74
use std::ptr::null_mut;
85
use std::result;
96

7+
use kvm_bindings::kvm_run;
8+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
9+
use kvm_bindings::{kvm_cpuid2, kvm_cpuid_entry2};
10+
11+
/// Wrappers over KVM device ioctls.
12+
pub mod device;
1013
/// Wrappers over KVM system ioctls.
1114
pub mod system;
1215
/// Wrappers over KVM VCPU ioctls.

src/ioctls/system.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,13 @@ mod tests {
350350
assert_eq!(get_raw_errno(faulty_kvm.get_vcpu_mmap_size()), badf_errno);
351351
assert_eq!(faulty_kvm.get_nr_vcpus(), 4);
352352
assert_eq!(faulty_kvm.get_nr_memslots(), 32);
353-
assert_eq!(get_raw_errno(faulty_kvm.get_emulated_cpuid(4)), badf_errno);
354-
assert_eq!(get_raw_errno(faulty_kvm.get_supported_cpuid(4)), badf_errno);
355-
assert_eq!(get_raw_errno(faulty_kvm.get_msr_index_list()), badf_errno);
353+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
354+
{
355+
assert_eq!(get_raw_errno(faulty_kvm.get_emulated_cpuid(4)), badf_errno);
356+
assert_eq!(get_raw_errno(faulty_kvm.get_supported_cpuid(4)), badf_errno);
357+
358+
assert_eq!(get_raw_errno(faulty_kvm.get_msr_index_list()), badf_errno);
359+
}
356360
assert_eq!(get_raw_errno(faulty_kvm.create_vm()), badf_errno);
357361
}
358362
}

src/ioctls/vcpu.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,52 @@ impl VcpuFd {
293293
Ok(())
294294
}
295295

296+
/// Sets the type of CPU to be exposed to the guest and optional features.
297+
///
298+
/// This initializes an ARM vCPU to the specified type with the specified features
299+
/// and resets the values of all of its registers to defaults.
300+
///
301+
/// # Arguments
302+
///
303+
/// * `kvi` - information about preferred CPU target type and recommended features for it.
304+
///
305+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
306+
pub fn vcpu_init(&self, kvi: &kvm_vcpu_init) -> Result<()> {
307+
// This is safe because we allocated the struct and we know the kernel will read
308+
// exactly the size of the struct.
309+
let ret = unsafe { ioctl_with_ref(self, KVM_ARM_VCPU_INIT(), kvi) };
310+
if ret < 0 {
311+
return Err(io::Error::last_os_error());
312+
}
313+
Ok(())
314+
}
315+
316+
/// Sets the value of one register for this vCPU.
317+
///
318+
/// The id of the register is encoded as specified in the kernel documentation
319+
/// for `KVM_SET_ONE_REG`.
320+
///
321+
/// # Arguments
322+
///
323+
/// * `reg_id` - ID of the register for which we are setting the value.
324+
/// * `data` - value for the specified register.
325+
///
326+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
327+
pub fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()> {
328+
let data_ref = &data as *const u64;
329+
let onereg = kvm_one_reg {
330+
id: reg_id,
331+
addr: data_ref as u64,
332+
};
333+
// This is safe because we allocated the struct and we know the kernel will read
334+
// exactly the size of the struct.
335+
let ret = unsafe { ioctl_with_ref(self, KVM_SET_ONE_REG(), &onereg) };
336+
if ret < 0 {
337+
return Err(io::Error::last_os_error());
338+
}
339+
Ok(())
340+
}
341+
296342
/// Triggers the running of the current virtual CPU returning an exit reason.
297343
///
298344
pub fn run(&self) -> Result<VcpuExit> {
@@ -718,4 +764,41 @@ mod tests {
718764
);
719765
assert_eq!(get_raw_errno(faulty_vcpu_fd.run()), badf_errno);
720766
}
767+
768+
#[test]
769+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
770+
fn test_get_preferred_target() {
771+
let kvm = Kvm::new().unwrap();
772+
let vm = kvm.create_vm().unwrap();
773+
let vcpu = vm.create_vcpu(0).unwrap();
774+
775+
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
776+
assert!(vcpu.vcpu_init(&kvi).is_err());
777+
778+
vm.get_preferred_target(&mut kvi)
779+
.expect("Cannot get preferred target");
780+
assert!(vcpu.vcpu_init(&kvi).is_ok());
781+
}
782+
783+
#[test]
784+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
785+
fn test_set_one_reg() {
786+
let kvm = Kvm::new().unwrap();
787+
let vm = kvm.create_vm().unwrap();
788+
let vcpu = vm.create_vcpu(0).unwrap();
789+
790+
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
791+
vm.get_preferred_target(&mut kvi)
792+
.expect("Cannot get preferred target");
793+
vcpu.vcpu_init(&kvi).expect("Cannot initialize vcpu");
794+
let mut data: u64 = 0;
795+
let mut reg_id: u64 = 0;
796+
797+
assert!(vcpu.set_one_reg(reg_id, data).is_err());
798+
// Exercising KVM_SET_ONE_REG by trying to alter the data inside the PSTATE register (which is a
799+
// specific aarch64 register).
800+
const PSTATE_REG_ID: u64 = 0x6030_0000_0010_0042;
801+
vcpu.set_one_reg(PSTATE_REG_ID, data)
802+
.expect("Failed to set pstate register");
803+
}
721804
}

src/ioctls/vm.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::io;
55
use std::os::raw::{c_ulong, c_void};
66
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
77

8+
use ioctls::device::new_device;
9+
use ioctls::device::DeviceFd;
810
use ioctls::vcpu::new_vcpu;
911
use ioctls::vcpu::VcpuFd;
1012
use ioctls::KvmRunWrapper;
@@ -246,6 +248,28 @@ impl VmFd {
246248
Ok(new_vcpu(vcpu, kvm_run_ptr))
247249
}
248250

251+
/// Creates an emulated device in the kernel.
252+
pub fn create_device(&self, device: &mut kvm_create_device) -> Result<DeviceFd> {
253+
let ret = unsafe { ioctl_with_ref(self, KVM_CREATE_DEVICE(), device) };
254+
if ret == 0 {
255+
Ok((new_device(unsafe { File::from_raw_fd(device.fd as i32) })))
256+
} else {
257+
return Err(io::Error::last_os_error());
258+
}
259+
}
260+
261+
/// This queries the kernel for the preferred target CPU type.
262+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
263+
pub fn get_preferred_target(&self, kvi: &mut kvm_vcpu_init) -> Result<()> {
264+
// The ioctl is safe because we allocated the struct and we know the
265+
// kernel will write exactly the size of the struct.
266+
let ret = unsafe { ioctl_with_mut_ref(self, KVM_ARM_PREFERRED_TARGET(), kvi) };
267+
if ret != 0 {
268+
return Err(io::Error::last_os_error());
269+
}
270+
Ok(())
271+
}
272+
249273
/// Get the `kvm_run` size.
250274
pub fn get_run_size(&self) -> usize {
251275
self.run_size
@@ -307,7 +331,12 @@ mod tests {
307331
let kvm = Kvm::new().unwrap();
308332
assert!(kvm.check_extension(Cap::Irqchip));
309333
let vm = kvm.create_vm().unwrap();
310-
assert!(vm.create_irq_chip().is_ok());
334+
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
335+
assert!(vm.create_irq_chip().is_ok());
336+
} else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) {
337+
// On arm, we expect this to fail as the irq chip needs to be created after the vcpus.
338+
assert!(vm.create_irq_chip().is_err());
339+
}
311340
}
312341

313342
#[test]
@@ -358,11 +387,15 @@ mod tests {
358387
let evtfd1 = unsafe { eventfd(0, EFD_NONBLOCK) };
359388
let evtfd2 = unsafe { eventfd(0, EFD_NONBLOCK) };
360389
let evtfd3 = unsafe { eventfd(0, EFD_NONBLOCK) };
390+
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
391+
assert!(vm_fd.register_irqfd(evtfd1, 4).is_ok());
392+
assert!(vm_fd.register_irqfd(evtfd2, 8).is_ok());
393+
assert!(vm_fd.register_irqfd(evtfd3, 4).is_ok());
394+
}
361395

362-
assert!(vm_fd.register_irqfd(evtfd1, 4).is_ok());
363-
assert!(vm_fd.register_irqfd(evtfd2, 8).is_ok());
364-
assert!(vm_fd.register_irqfd(evtfd3, 4).is_ok());
365-
396+
// On aarch64, this fails because setting up the interrupt controller is mandatory before
397+
// registering any IRQ.
398+
// On x86_64 this fails as the event fd was already matched with a GSI.
366399
assert!(vm_fd.register_irqfd(evtfd3, 4).is_err());
367400
assert!(vm_fd.register_irqfd(evtfd3, 5).is_err());
368401
}
@@ -413,4 +446,13 @@ mod tests {
413446

414447
assert_eq!(get_raw_errno(faulty_vm_fd.get_dirty_log(0, 0)), badf_errno);
415448
}
449+
450+
#[test]
451+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
452+
fn test_get_preferred_target() {
453+
let kvm = Kvm::new().unwrap();
454+
let vm = kvm.create_vm().unwrap();
455+
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
456+
assert!(vm.get_preferred_target(&mut kvi).is_ok());
457+
}
416458
}

src/kvm_ioctls.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ ioctl_iow_nr!(KVM_SET_FPU, KVMIO, 0x8d, kvm_fpu);
8282
ioctl_ior_nr!(KVM_GET_LAPIC, KVMIO, 0x8e, kvm_lapic_state);
8383
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
8484
ioctl_iow_nr!(KVM_SET_LAPIC, KVMIO, 0x8f, kvm_lapic_state);
85+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
86+
ioctl_iow_nr!(KVM_SET_ONE_REG, KVMIO, 0xac, kvm_one_reg);
87+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
88+
ioctl_iow_nr!(KVM_ARM_VCPU_INIT, KVMIO, 0xae, kvm_vcpu_init);
89+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
90+
ioctl_ior_nr!(KVM_ARM_PREFERRED_TARGET, KVMIO, 0xaf, kvm_vcpu_init);
91+
ioctl_iowr_nr!(KVM_CREATE_DEVICE, KVMIO, 0xe0, kvm_create_device);
92+
ioctl_iow_nr!(KVM_SET_DEVICE_ATTR, KVMIO, 0xe1, kvm_device_attr);
8593

8694
#[cfg(test)]
8795
mod tests {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod cap;
2020
mod ioctls;
2121

2222
pub use cap::Cap;
23+
pub use ioctls::device::DeviceFd;
2324
pub use ioctls::system::Kvm;
2425
pub use ioctls::vcpu::{VcpuExit, VcpuFd};
2526
pub use ioctls::vm::VmFd;

tests/coverage

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
91.1
1+
91.3

0 commit comments

Comments
 (0)