Skip to content

Commit 5ce8a7d

Browse files
author
Dimitris Charisis
committed
riscv64: add interrupt support for serial device
Implement interrupt support for the legacy serial console device on riscv64, following the same logic as the VirtIO device implementation. Replace the `EventFdTrigger` structure, which triggers IRQFD-related interrupts on x86_64 and aarch64, with a new `IrqLineTrigger` type. This type encapsulates the two required pieces of information to fire a `KVM_IRQ_LINE` interrupt: the raw file descriptor behind the `VmFd` type and the corresponding GSI number. This change is necessary because the `VmFd` structure does not implement `Clone`, preventing it from being carried into the `trigger()` function when injecting an interrupt from the serial device. Additionally, make the `allocate_mmio_resources()` method of `MMIODeviceManager` public. This is required to allocate an IRQ number *before* setting up the serial device, since `setup_serial_device()` builds the `IrqLineTrigger` and needs to know the IRQ number associated with the serial device at that point. Signed-off-by: Dimitris Charisis <dchar@cslab.ece.ntua.gr>
1 parent 41c7b54 commit 5ce8a7d

File tree

5 files changed

+399
-3
lines changed

5 files changed

+399
-3
lines changed

src/vmm/src/builder.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
use std::fmt::Debug;
77
use std::io;
8+
#[cfg(target_arch = "riscv64")]
9+
use std::os::fd::AsRawFd;
810
#[cfg(feature = "gdb")]
911
use std::sync::mpsc;
1012
use std::sync::{Arc, Mutex};
@@ -31,6 +33,8 @@ use crate::cpu_config::templates::{
3133
use crate::device_manager::acpi::ACPIDeviceManager;
3234
#[cfg(target_arch = "x86_64")]
3335
use crate::device_manager::legacy::PortIODeviceManager;
36+
#[cfg(target_arch = "riscv64")]
37+
use crate::device_manager::mmio::MMIODeviceInfo;
3438
use crate::device_manager::mmio::{MMIODeviceManager, MmioError};
3539
use crate::device_manager::persist::{
3640
ACPIDeviceManagerConstructorArgs, ACPIDeviceManagerRestoreError, MMIODevManagerConstructorArgs,
@@ -39,6 +43,8 @@ use crate::device_manager::resources::ResourceAllocator;
3943
use crate::devices::BusDevice;
4044
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
4145
use crate::devices::acpi::vmgenid::{VmGenId, VmGenIdError};
46+
#[cfg(target_arch = "riscv64")]
47+
use crate::devices::legacy::IrqLineTrigger;
4248
#[cfg(target_arch = "aarch64")]
4349
use crate::devices::legacy::RTCDevice;
4450
use crate::devices::legacy::serial::SerialOut;
@@ -306,6 +312,9 @@ pub fn build_microvm_for_boot(
306312
#[cfg(target_arch = "aarch64")]
307313
attach_legacy_devices_aarch64(event_manager, &mut vmm, &mut boot_cmdline)?;
308314

315+
#[cfg(target_arch = "riscv64")]
316+
attach_legacy_devices_riscv64(event_manager, &mut vmm, &mut boot_cmdline)?;
317+
309318
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
310319
attach_vmgenid_device(&mut vmm)?;
311320

@@ -566,6 +575,7 @@ pub fn build_microvm_from_snapshot(
566575
Ok(vmm)
567576
}
568577

578+
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
569579
/// Sets up the serial device.
570580
pub fn setup_serial_device(
571581
event_manager: &mut EventManager,
@@ -589,6 +599,35 @@ pub fn setup_serial_device(
589599
Ok(serial)
590600
}
591601

602+
#[cfg(target_arch = "riscv64")]
603+
/// Sets up the serial device.
604+
pub fn setup_serial_device(
605+
event_manager: &mut EventManager,
606+
vmfd: &kvm_ioctls::VmFd,
607+
input: std::io::Stdin,
608+
out: std::io::Stdout,
609+
device_info: &Option<MMIODeviceInfo>,
610+
) -> Result<Arc<Mutex<BusDevice>>, VmmError> {
611+
let interrupt_evt = IrqLineTrigger::new(
612+
vmfd.as_raw_fd(),
613+
device_info.as_ref().unwrap().irq.unwrap().get(),
614+
);
615+
let kick_stdin_read_evt =
616+
EventFdTrigger::new(EventFd::new(EFD_NONBLOCK).map_err(VmmError::EventFd)?);
617+
let serial = Arc::new(Mutex::new(BusDevice::Serial(SerialWrapper {
618+
serial: Serial::with_events(
619+
interrupt_evt,
620+
SerialEventsWrapper {
621+
buffer_ready_event_fd: Some(kick_stdin_read_evt),
622+
},
623+
SerialOut::Stdout(out),
624+
),
625+
input: Some(input),
626+
})));
627+
event_manager.add_subscriber(serial.clone());
628+
Ok(serial)
629+
}
630+
592631
/// 64 bytes due to alignment requirement in 3.1 of https://www.kernel.org/doc/html/v5.8/virt/kvm/devices/vcpu.html#attribute-kvm-arm-vcpu-pvtime-ipa
593632
#[cfg(target_arch = "aarch64")]
594633
const STEALTIME_STRUCT_MEM_SIZE: u64 = 64;
@@ -661,6 +700,47 @@ fn attach_legacy_devices_aarch64(
661700
.map_err(VmmError::RegisterMMIODevice)
662701
}
663702

703+
#[cfg(target_arch = "riscv64")]
704+
fn attach_legacy_devices_riscv64(
705+
event_manager: &mut EventManager,
706+
vmm: &mut Vmm,
707+
cmdline: &mut LoaderKernelCmdline,
708+
) -> Result<(), VmmError> {
709+
// Serial device setup.
710+
let cmdline_contains_console = cmdline
711+
.as_cstring()
712+
.map_err(|_| VmmError::Cmdline)?
713+
.into_string()
714+
.map_err(|_| VmmError::Cmdline)?
715+
.contains("console=");
716+
717+
if cmdline_contains_console {
718+
// Make stdout non-blocking.
719+
set_stdout_nonblocking();
720+
let device_info = vmm
721+
.mmio_device_manager
722+
.allocate_mmio_resources(&mut vmm.resource_allocator, 1)
723+
.map_err(|err| VmmError::DeviceManager(err))?;
724+
725+
let serial = setup_serial_device(
726+
event_manager,
727+
vmm.vm.fd(),
728+
std::io::stdin(),
729+
std::io::stdout(),
730+
&Some(device_info.clone()),
731+
)?;
732+
733+
vmm.mmio_device_manager
734+
.register_mmio_serial(&mut vmm.resource_allocator, serial, Some(device_info))
735+
.map_err(VmmError::RegisterMMIODevice)?;
736+
vmm.mmio_device_manager
737+
.add_mmio_serial_to_cmdline(cmdline)
738+
.map_err(VmmError::RegisterMMIODevice)?;
739+
}
740+
741+
Ok(())
742+
}
743+
664744
/// Attaches a VirtioDevice device to the device manager and event manager.
665745
fn attach_virtio_device<T: 'static + VirtioDevice + MutEventSubscriber + Debug>(
666746
event_manager: &mut EventManager,

src/vmm/src/device_manager/mmio.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl MMIODeviceManager {
147147
}
148148

149149
/// Allocates resources for a new device to be added.
150-
fn allocate_mmio_resources(
150+
pub fn allocate_mmio_resources(
151151
&mut self,
152152
resource_allocator: &mut ResourceAllocator,
153153
irq_count: u32,
@@ -312,7 +312,29 @@ impl MMIODeviceManager {
312312
self.register_mmio_device(identifier, device_info, serial)
313313
}
314314

315-
#[cfg(target_arch = "aarch64")]
315+
#[cfg(target_arch = "riscv64")]
316+
/// Register an early console at the specified MMIO configuration if given as parameter,
317+
/// otherwise allocate a new MMIO resources for it.
318+
pub fn register_mmio_serial(
319+
&mut self,
320+
resource_allocator: &mut ResourceAllocator,
321+
serial: Arc<Mutex<BusDevice>>,
322+
device_info_opt: Option<MMIODeviceInfo>,
323+
) -> Result<(), MmioError> {
324+
// Create a new MMIODeviceInfo object on boot path or unwrap the
325+
// existing object on restore path.
326+
let device_info = if let Some(device_info) = device_info_opt {
327+
device_info
328+
} else {
329+
self.allocate_mmio_resources(resource_allocator, 1)?
330+
};
331+
332+
let identifier = (DeviceType::Serial, DeviceType::Serial.to_string());
333+
// Register the newly created Serial object.
334+
self.register_mmio_device(identifier, device_info, serial)
335+
}
336+
337+
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
316338
/// Append the registered early console to the kernel cmdline.
317339
pub fn add_mmio_serial_to_cmdline(
318340
&self,

src/vmm/src/devices/legacy/mod.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,24 @@ pub mod serial;
1313

1414
use std::io;
1515
use std::ops::Deref;
16+
#[cfg(target_arch = "riscv64")]
17+
use std::os::fd::AsRawFd;
1618

1719
use serde::Serializer;
1820
use serde::ser::SerializeMap;
1921
use vm_superio::Trigger;
2022
use vmm_sys_util::eventfd::EventFd;
23+
#[cfg(target_arch = "riscv64")]
24+
use vmm_sys_util::{errno, ioctl::ioctl_with_ref, ioctl_ioc_nr, ioctl_iow_nr};
2125

2226
pub use self::i8042::{I8042Device, I8042Error as I8042DeviceError};
2327
#[cfg(target_arch = "aarch64")]
2428
pub use self::rtc_pl031::RTCDevice;
2529
pub use self::serial::{
2630
IER_RDA_BIT, IER_RDA_OFFSET, SerialDevice, SerialEventsWrapper, SerialWrapper,
2731
};
32+
#[cfg(target_arch = "riscv64")]
33+
use crate::logger::error;
2834

2935
/// Wrapper for implementing the trigger functionality for `EventFd`.
3036
///
@@ -64,6 +70,74 @@ impl EventFdTrigger {
6470
}
6571
}
6672

73+
// TODO: raw_vmfd and gsi are actually never None.
74+
#[cfg(target_arch = "riscv64")]
75+
#[derive(Debug)]
76+
pub struct IrqLineTrigger {
77+
raw_vmfd: Option<i32>,
78+
gsi: Option<u32>,
79+
}
80+
81+
#[cfg(target_arch = "riscv64")]
82+
impl IrqLineTrigger {
83+
pub fn new(raw_vmfd: i32, gsi: u32) -> Self {
84+
Self {
85+
raw_vmfd: Some(raw_vmfd),
86+
gsi: Some(gsi),
87+
}
88+
}
89+
90+
// This function is taken from kvm-ioctls because it requires VmFd, which we don't
91+
// have at this point. However, it only uses the raw file descriptor, which is just
92+
// an i32. So, we copy it here and use it directly with the raw fd.
93+
fn set_irq_line<F: AsRawFd>(fd: F, irq: u32, active: bool) -> Result<(), kvm_ioctls::Error> {
94+
let mut irq_level = kvm_bindings::kvm_irq_level::default();
95+
irq_level.__bindgen_anon_1.irq = irq;
96+
irq_level.level = u32::from(active);
97+
98+
// SAFETY: Safe because we know that our file is a VM fd, we know the kernel will only read
99+
// the correct amount of memory from our pointer, and we verify the return result.
100+
let ret = unsafe { ioctl_with_ref(&fd, IrqLineTrigger::KVM_IRQ_LINE(), &irq_level) };
101+
if ret == 0 {
102+
Ok(())
103+
} else {
104+
Err(errno::Error::last())
105+
}
106+
}
107+
108+
ioctl_iow_nr!(
109+
KVM_IRQ_LINE,
110+
kvm_bindings::KVMIO,
111+
0x61,
112+
kvm_bindings::kvm_irq_level
113+
);
114+
}
115+
116+
#[cfg(target_arch = "riscv64")]
117+
impl Trigger for IrqLineTrigger {
118+
type E = ::std::io::Error;
119+
120+
fn trigger(&self) -> ::std::io::Result<()> {
121+
// Safe to unwrap since `gsi` and `vmfd` have been set
122+
let gsi = self.gsi.unwrap();
123+
124+
IrqLineTrigger::set_irq_line(self.raw_vmfd.unwrap().as_raw_fd(), gsi, true).map_err(
125+
|err| {
126+
error!("set_irq_line() failed: {err:?}");
127+
std::io::Error::last_os_error()
128+
},
129+
)?;
130+
IrqLineTrigger::set_irq_line(self.raw_vmfd.unwrap().as_raw_fd(), gsi, false).map_err(
131+
|err| {
132+
error!("set_irq_line() failed: {err:?}");
133+
std::io::Error::last_os_error()
134+
},
135+
)?;
136+
137+
Ok(())
138+
}
139+
}
140+
67141
/// Called by METRICS.flush(), this function facilitates serialization of aggregated metrics.
68142
pub fn flush_metrics<S: Serializer>(serializer: S) -> Result<S::Ok, S::Error> {
69143
let mut seq = serializer.serialize_map(Some(1))?;

0 commit comments

Comments
 (0)