Skip to main content

arcbox_hypervisor/linux/vm/
virtio.rs

1use std::os::unix::io::RawFd;
2use std::sync::atomic::Ordering;
3
4use crate::{error::HypervisorError, types::VirtioDeviceType};
5
6use super::KvmVm;
7
8/// Base address for VirtIO MMIO devices (ARM64).
9/// This is placed at 160MB to avoid conflicts with RAM and other devices.
10pub(super) const VIRTIO_MMIO_BASE: u64 = 0x0a00_0000;
11
12/// Size of each VirtIO MMIO device region (512 bytes).
13pub(super) const VIRTIO_MMIO_SIZE: u64 = 0x200;
14
15/// Gap between VirtIO MMIO devices (for alignment).
16pub(super) const VIRTIO_MMIO_GAP: u64 = 0x200;
17
18/// VirtIO MMIO register offset for queue notify (used for IOEVENTFD).
19pub(super) const VIRTIO_MMIO_QUEUE_NOTIFY: u64 = 0x50;
20
21/// Base IRQ for VirtIO devices.
22/// On ARM64 GIC, SPI interrupts start at 32.
23/// On x86 IOAPIC, we use IRQs starting at 5 (avoiding legacy devices).
24#[cfg(target_arch = "aarch64")]
25const VIRTIO_IRQ_BASE: u32 = 32;
26
27#[cfg(target_arch = "x86_64")]
28const VIRTIO_IRQ_BASE: u32 = 5;
29
30/// Maximum number of VirtIO devices.
31const MAX_VIRTIO_DEVICES: usize = 32;
32
33/// Information about an attached VirtIO device.
34#[derive(Debug)]
35pub struct VirtioDeviceInfo {
36    /// Device type.
37    pub device_type: VirtioDeviceType,
38    /// MMIO base address.
39    pub mmio_base: u64,
40    /// MMIO region size.
41    pub mmio_size: u64,
42    /// Assigned IRQ (GSI).
43    pub irq: u32,
44    /// Eventfd for IRQ injection.
45    pub irq_fd: RawFd,
46    /// Eventfd for queue notification.
47    pub notify_fd: RawFd,
48}
49
50impl KvmVm {
51    /// Allocates an MMIO region for a VirtIO device.
52    ///
53    /// Returns the base address for the device.
54    pub(super) fn allocate_mmio_region(&self) -> Result<u64, HypervisorError> {
55        let devices = self
56            .virtio_devices
57            .read()
58            .map_err(|_| HypervisorError::DeviceError("Lock poisoned".to_string()))?;
59
60        if devices.len() >= MAX_VIRTIO_DEVICES {
61            return Err(HypervisorError::DeviceError(
62                "Maximum number of VirtIO devices reached".to_string(),
63            ));
64        }
65
66        // Calculate next available address.
67        let offset = devices.len() as u64 * (VIRTIO_MMIO_SIZE + VIRTIO_MMIO_GAP);
68        Ok(VIRTIO_MMIO_BASE + offset)
69    }
70
71    /// Allocates an IRQ (GSI) for a VirtIO device.
72    pub(super) fn allocate_irq(&self) -> u32 {
73        let offset = self.next_virtio_irq.fetch_add(1, Ordering::SeqCst);
74        VIRTIO_IRQ_BASE + offset
75    }
76
77    /// Creates an eventfd.
78    pub(super) fn create_eventfd() -> Result<RawFd, HypervisorError> {
79        let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
80        if fd < 0 {
81            return Err(HypervisorError::DeviceError(format!(
82                "Failed to create eventfd: {}",
83                std::io::Error::last_os_error()
84            )));
85        }
86        Ok(fd)
87    }
88
89    /// Sets up IOEVENTFD for VirtIO queue notification.
90    ///
91    /// This allows the guest to notify the host about queue updates by writing
92    /// to a specific MMIO address, without causing a VM exit.
93    pub(super) fn setup_ioeventfd(&self, mmio_base: u64) -> Result<RawFd, HypervisorError> {
94        let notify_fd = Self::create_eventfd()?;
95
96        // Register IOEVENTFD at the queue notify register address.
97        // 4 bytes for 32-bit writes to VIRTIO_MMIO_QUEUE_NOTIFY.
98        let notify_addr = mmio_base + VIRTIO_MMIO_QUEUE_NOTIFY;
99
100        self.vm_fd
101            .register_ioeventfd(notify_addr, 4, notify_fd, None)
102            .map_err(|e| {
103                // Clean up the eventfd on failure.
104                unsafe { libc::close(notify_fd) };
105                HypervisorError::DeviceError(format!("Failed to register IOEVENTFD: {}", e))
106            })?;
107
108        tracing::debug!(
109            "Registered IOEVENTFD at {:#x} with fd={}",
110            notify_addr,
111            notify_fd
112        );
113
114        Ok(notify_fd)
115    }
116
117    /// Returns a copy of the attached VirtIO devices info.
118    pub fn virtio_devices(&self) -> Result<Vec<VirtioDeviceInfo>, HypervisorError> {
119        let devices = self
120            .virtio_devices
121            .read()
122            .map_err(|_| HypervisorError::DeviceError("Lock poisoned".to_string()))?;
123
124        Ok(devices
125            .iter()
126            .map(|d| VirtioDeviceInfo {
127                device_type: d.device_type.clone(),
128                mmio_base: d.mmio_base,
129                mmio_size: d.mmio_size,
130                irq: d.irq,
131                irq_fd: d.irq_fd,
132                notify_fd: d.notify_fd,
133            })
134            .collect())
135    }
136}
137
138/// Serializes device configuration to bytes for snapshot storage.
139pub(super) fn bincode_serialize_device_config(device: &VirtioDeviceInfo) -> Vec<u8> {
140    // Simple serialization of device config for snapshot purposes.
141    // A full implementation would use serde/bincode.
142    let mut bytes = Vec::new();
143
144    // Serialize device type as u8
145    let type_byte = match device.device_type {
146        VirtioDeviceType::Block => 0u8,
147        VirtioDeviceType::Net => 1,
148        VirtioDeviceType::Console => 2,
149        VirtioDeviceType::Rng => 3,
150        VirtioDeviceType::Balloon => 4,
151        VirtioDeviceType::Fs => 5,
152        VirtioDeviceType::Vsock => 6,
153        VirtioDeviceType::Gpu => 7,
154    };
155    bytes.push(type_byte);
156
157    // Serialize MMIO base and size
158    bytes.extend_from_slice(&device.mmio_base.to_le_bytes());
159    bytes.extend_from_slice(&device.mmio_size.to_le_bytes());
160
161    // Serialize IRQ
162    bytes.extend_from_slice(&device.irq.to_le_bytes());
163
164    bytes
165}