Skip to main content

axdevice_base/
lib.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Basic traits and structures for emulated devices in ArceOS hypervisor.
16//!
17//! This crate provides the foundational abstractions for implementing virtual devices
18//! in the [AxVisor](https://github.com/arceos-hypervisor/axvisor) hypervisor. It is
19//! designed for `no_std` environments and supports multiple architectures.
20//!
21//! # Overview
22//!
23//! The crate contains the following key components:
24//!
25//! - [`BaseDeviceOps`]: The core trait that all emulated devices must implement.
26//! - [`EmuDeviceType`]: Enumeration representing the type of emulator devices
27//!   (re-exported from `axvmconfig` crate).
28//! - [`EmulatedDeviceConfig`]: Configuration structure for device initialization.
29//! - Trait aliases for specific device types:
30//!   - [`BaseMmioDeviceOps`]: For MMIO (Memory-Mapped I/O) devices.
31//!   - [`BaseSysRegDeviceOps`]: For system register devices.
32//!   - [`BasePortDeviceOps`]: For port I/O devices.
33//!
34//! # Usage
35//!
36//! To implement a custom emulated device, you need to implement the [`BaseDeviceOps`]
37//! trait with the appropriate address range type:
38//!
39//! ```rust,ignore
40//! use axdevice_base::{BaseDeviceOps, EmuDeviceType};
41//! use axaddrspace::{GuestPhysAddrRange, device::AccessWidth};
42//! use ax_errno::AxResult;
43//!
44//! struct MyDevice {
45//!     base_addr: usize,
46//!     size: usize,
47//! }
48//!
49//! impl BaseDeviceOps<GuestPhysAddrRange> for MyDevice {
50//!     fn emu_type(&self) -> EmuDeviceType {
51//!         EmuDeviceType::Dummy
52//!     }
53//!
54//!     fn address_range(&self) -> GuestPhysAddrRange {
55//!         (self.base_addr..self.base_addr + self.size).try_into().unwrap()
56//!     }
57//!
58//!     fn handle_read(&self, addr: GuestPhysAddr, width: AccessWidth) -> AxResult<usize> {
59//!         // Handle read operation
60//!         Ok(0)
61//!     }
62//!
63//!     fn handle_write(&self, addr: GuestPhysAddr, width: AccessWidth, val: usize) -> AxResult {
64//!         // Handle write operation
65//!         Ok(())
66//!     }
67//! }
68//! ```
69//!
70//! # Feature Flags
71//!
72//! This crate currently has no optional feature flags. All functionality is available
73//! by default.
74
75#![no_std]
76#![feature(trait_alias)]
77// trait_upcasting has been stabilized in Rust 1.86, but we still need a while to update the minimum
78// Rust version of Axvisor.
79#![allow(stable_features)]
80#![feature(trait_upcasting)]
81#![allow(incomplete_features)]
82#![feature(generic_const_exprs)]
83#![warn(missing_docs)]
84
85extern crate alloc;
86
87mod device;
88
89use alloc::{string::String, sync::Arc, vec::Vec};
90use core::any::Any;
91
92pub use ax_errno::AxResult;
93pub use axvm_types::{
94    EmulatedDeviceType as EmuDeviceType, GuestPhysAddr, GuestPhysAddrRange, InterruptTriggerMode,
95    IrqLineId,
96};
97
98pub use crate::device::{
99    AccessWidth, BusAccess, BusKind, BusResponse, DeviceAddr, DeviceAddrRange, DeviceError, Port,
100    PortRange, SysRegAddr, SysRegAddrRange,
101};
102
103/// Represents the configuration of an emulated device for a virtual machine.
104///
105/// This structure holds all the necessary information to initialize and configure
106/// an emulated device, including its memory mapping, interrupt configuration, and
107/// device-specific parameters.
108///
109/// # Fields
110///
111/// - `name`: A human-readable identifier for the device.
112/// - `base_ipa`: The starting address in guest physical address space.
113/// - `length`: The size of the device's address space in bytes.
114/// - `irq_id`: The interrupt line number for device interrupts.
115/// - `emu_type`: Numeric identifier for the device type.
116/// - `cfg_list`: Device-specific configuration parameters.
117///
118/// # Example
119///
120/// ```rust
121/// use axdevice_base::EmulatedDeviceConfig;
122///
123/// let config = EmulatedDeviceConfig {
124///     name: "uart0".into(),
125///     base_ipa: 0x0900_0000,
126///     length: 0x1000,
127///     irq_id: 33,
128///     emu_type: 1,
129///     cfg_list: vec![115200], // baud rate
130/// };
131/// ```
132#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
133pub struct EmulatedDeviceConfig {
134    /// The name of the device.
135    ///
136    /// This is a human-readable identifier used for logging, debugging, and
137    /// device tree generation. It should be unique within a virtual machine.
138    pub name: String,
139
140    /// The base IPA (Intermediate Physical Address) of the device.
141    ///
142    /// This is the starting address in the guest's physical address space
143    /// where the device's registers are mapped. The guest OS will use this
144    /// address to access the device.
145    pub base_ipa: usize,
146
147    /// The length of the device's address space in bytes.
148    ///
149    /// This defines the size of the memory region that the device occupies.
150    /// Any access within `[base_ipa, base_ipa + length)` will be routed to
151    /// this device.
152    pub length: usize,
153
154    /// The IRQ (Interrupt Request) ID of the device.
155    ///
156    /// This is the interrupt line number that the device uses to signal
157    /// events to the guest. The value should correspond to a valid interrupt
158    /// ID in the virtual interrupt controller.
159    pub irq_id: usize,
160
161    /// The type of emulated device.
162    ///
163    /// This numeric value identifies the device type and is used by the
164    /// device manager to instantiate the correct device implementation.
165    /// See [`EmuDeviceType`] for predefined device types.
166    pub emu_type: usize,
167
168    /// Device-specific configuration parameters.
169    ///
170    /// This is a list of configuration values whose meaning depends on the
171    /// specific device type. For example, a UART device might use this to
172    /// specify baud rate, while a virtio device might use it for queue sizes.
173    pub cfg_list: Vec<usize>,
174}
175
176/// The core trait that all emulated devices must implement.
177///
178/// This trait defines the common interface for all virtual devices in the hypervisor.
179/// It provides methods for device identification, address range querying, and
180/// handling read/write operations from the guest.
181///
182/// # Type Parameters
183///
184/// - `R`: The address range type that the device uses. This determines the
185///   addressing scheme (MMIO, port I/O, system registers, etc.).
186///
187/// # Implementation Notes
188///
189/// - All implementations must also implement [`Any`] to support runtime type checking.
190/// - The `handle_read` and `handle_write` methods are called by the hypervisor's
191///   trap handler when the guest accesses the device's address range.
192/// - Implementations should handle concurrent access appropriately if the device
193///   can be accessed from multiple vCPUs.
194///
195/// # Example
196///
197/// See the crate-level documentation for a complete implementation example.
198pub trait BaseDeviceOps<R: DeviceAddrRange>: Any {
199    /// Returns the type of the emulated device.
200    ///
201    /// This is used by the device manager to identify the device type and
202    /// perform type-specific operations.
203    fn emu_type(&self) -> EmuDeviceType;
204
205    /// Returns the address range that this device occupies.
206    ///
207    /// The returned range is used by the hypervisor to route guest memory
208    /// accesses to the appropriate device handler.
209    fn address_range(&self) -> R;
210
211    /// Handles a read operation on the emulated device.
212    ///
213    /// # Arguments
214    ///
215    /// - `addr`: The address within the device's range being read.
216    /// - `width`: The access width (byte, halfword, word, or doubleword).
217    ///
218    /// # Returns
219    ///
220    /// - `Ok(value)`: The value read from the device register.
221    /// - `Err(error)`: An error if the read operation failed.
222    ///
223    /// # Notes
224    ///
225    /// Implementations should respect the `width` parameter and only return
226    /// data of the appropriate size. The returned value should be zero-extended
227    /// if necessary.
228    fn handle_read(&self, addr: R::Addr, width: AccessWidth) -> AxResult<usize>;
229
230    /// Handles a write operation on the emulated device.
231    ///
232    /// # Arguments
233    ///
234    /// - `addr`: The address within the device's range being written.
235    /// - `width`: The access width (byte, halfword, word, or doubleword).
236    /// - `val`: The value to write to the device register.
237    ///
238    /// # Returns
239    ///
240    /// - `Ok(())`: The write operation completed successfully.
241    /// - `Err(error)`: An error if the write operation failed.
242    ///
243    /// # Notes
244    ///
245    /// Implementations should only use the lower bits of `val` corresponding
246    /// to the specified `width`.
247    fn handle_write(&self, addr: R::Addr, width: AccessWidth, val: usize) -> AxResult;
248}
249
250/// Attempts to downcast a device to a specific type and apply a function to it.
251///
252/// This function is useful when you have a trait object (`Arc<dyn BaseDeviceOps<R>>`)
253/// and need to access type-specific methods or data of the underlying concrete type.
254///
255/// # Type Parameters
256///
257/// - `T`: The concrete device type to downcast to. Must implement `BaseDeviceOps<R>`.
258/// - `R`: The address range type.
259/// - `U`: The return type of the mapping function.
260/// - `F`: The function to apply if the downcast succeeds.
261///
262/// # Arguments
263///
264/// - `device`: A reference to the device trait object.
265/// - `f`: A function to call with a reference to the concrete device type.
266///
267/// # Returns
268///
269/// - `Some(result)`: If the device is of type `T`, returns the result of `f`.
270/// - `None`: If the device is not of type `T`.
271///
272/// # Example
273///
274/// ```rust,ignore
275/// use axdevice_base::{BaseDeviceOps, map_device_of_type};
276/// use alloc::sync::Arc;
277///
278/// struct UartDevice {
279///     baud_rate: u32,
280/// }
281///
282/// impl UartDevice {
283///     fn get_baud_rate(&self) -> u32 {
284///         self.baud_rate
285///     }
286/// }
287///
288/// // ... implement BaseDeviceOps for UartDevice ...
289///
290/// fn check_uart_config(device: &Arc<dyn BaseMmioDeviceOps>) {
291///     if let Some(baud_rate) = map_device_of_type(device, |uart: &UartDevice| {
292///         uart.get_baud_rate()
293///     }) {
294///         println!("UART baud rate: {}", baud_rate);
295///     }
296/// }
297/// ```
298#[deprecated(
299    since = "0.5.0",
300    note = "Use Device::as_any().downcast_ref() via MmioDeviceAdapter instead"
301)]
302pub fn map_device_of_type<T: BaseDeviceOps<R>, R: DeviceAddrRange, U, F: FnOnce(&T) -> U>(
303    device: &Arc<dyn BaseDeviceOps<R>>,
304    f: F,
305) -> Option<U> {
306    let any_arc: Arc<dyn Any> = device.clone();
307
308    any_arc.downcast_ref::<T>().map(f)
309}
310
311// Trait aliases are limited yet: https://github.com/rust-lang/rfcs/pull/3437
312
313/// Trait alias for MMIO (Memory-Mapped I/O) device operations.
314///
315/// This is a convenience alias for [`BaseDeviceOps`] with [`GuestPhysAddrRange`]
316/// as the address range type. MMIO devices are the most common type of virtual
317/// devices, where device registers are accessed through memory read/write operations.
318///
319/// # Supported Architectures
320///
321/// MMIO devices are supported on all architectures (x86_64, ARM, RISC-V).
322pub trait BaseMmioDeviceOps = BaseDeviceOps<GuestPhysAddrRange>;
323
324/// Trait alias for system register device operations.
325///
326/// This is a convenience alias for [`BaseDeviceOps`] with [`SysRegAddrRange`]
327/// as the address range type. System register devices are typically used on
328/// ARM architectures to emulate system registers accessed via MSR/MRS instructions.
329///
330/// # Supported Architectures
331///
332/// System register devices are primarily used on ARM/AArch64 architectures.
333pub trait BaseSysRegDeviceOps = BaseDeviceOps<SysRegAddrRange>;
334
335/// Trait alias for port I/O device operations.
336///
337/// This is a convenience alias for [`BaseDeviceOps`] with [`PortRange`]
338/// as the address range type. Port I/O devices are used on x86 architectures
339/// where device registers are accessed via IN/OUT instructions.
340///
341/// # Supported Architectures
342///
343/// Port I/O devices are only used on x86/x86_64 architectures.
344pub trait BasePortDeviceOps = BaseDeviceOps<PortRange>;
345
346// ---------------------------------------------------------------------------
347// New unified device-registration types (device / interrupt framework refactoring)
348// ---------------------------------------------------------------------------
349
350/// Opaque identifier assigned to a device when it is registered into a
351/// [`AxVmDevices`].
352#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
353pub struct DeviceId(u32);
354
355impl DeviceId {
356    /// Creates a new `DeviceId` from a raw `u32`.
357    pub const fn new(id: u32) -> Self {
358        Self(id)
359    }
360
361    /// Returns the raw `u32` value.
362    pub const fn as_u32(self) -> u32 {
363        self.0
364    }
365}
366
367/// Target instruction-set architecture.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum Arch {
370    /// 64-bit ARM (AArch64).
371    AArch64,
372    /// 64-bit RISC-V.
373    Riscv64,
374    /// 64-bit x86 (AMD64 / Intel 64).
375    X86_64,
376    /// 64-bit LoongArch.
377    LoongArch64,
378}
379
380/// A resource that a device declares it needs during registration.
381///
382/// The device manager uses this information for address-range conflict
383/// detection and architecture-suitability checks.
384#[derive(Debug, Clone)]
385pub enum Resource {
386    /// An MMIO address window.
387    MmioRange {
388        /// Start of the window (guest-physical address).
389        base: u64,
390        /// Size of the window in bytes.
391        size: u64,
392    },
393    /// A Port I/O range (x86 only).
394    PortRange {
395        /// Start of the range.
396        base: u16,
397        /// Size of the range in bytes.
398        size: u16,
399    },
400    /// System register range.
401    SysReg {
402        /// Register encoding range start (architecture-specific).
403        addr: u32,
404        /// Number of registers in the range.
405        count: u32,
406    },
407}
408
409/// The reason a resource was rejected as structurally invalid during
410/// validation.
411#[derive(Debug, Clone)]
412pub enum InvalidResourceReason {
413    /// The resource has a size or count of zero.
414    ZeroSized,
415    /// The resource's end address overflows the address space.
416    AddressOverflow,
417    /// The resource extends past the valid bus address range.
418    OutOfBusRange,
419    /// The bus kind of the resource is not supported on the current
420    /// architecture.
421    UnsupportedOnArchitecture,
422    /// The device declared multiple resources of the same bus kind whose
423    /// address ranges overlap each other, which would corrupt the
424    /// dispatch index.
425    OverlappingResources,
426}
427
428/// Errors that can be returned when registering a device.
429#[derive(Debug, Clone)]
430pub enum RegistryError {
431    /// The device declared a resource that is structurally invalid.
432    InvalidResource {
433        /// The invalid resource.
434        resource: Resource,
435        /// Why the resource was rejected.
436        reason: InvalidResourceReason,
437    },
438    /// Two devices claim overlapping address ranges.
439    AddressConflict {
440        /// The resource the new device is attempting to register.
441        resource: Resource,
442        /// The resource already held by an existing device.
443        existing: Resource,
444        /// The device that already owns the conflicting resource.
445        existing_device: DeviceId,
446    },
447    /// The device requested a bus type that the current architecture does
448    /// not support (e.g. Port I/O on AArch64).
449    BusKindNotSupported {
450        /// The unsupported bus kind.
451        kind: BusKind,
452        /// The current target architecture.
453        arch: Arch,
454    },
455    /// The device is not compatible with the current target architecture.
456    ArchNotSupported {
457        /// Human-readable device name (for diagnostics).
458        device_name: String,
459        /// The architecture(s) the device requires.
460        required_arch: Arch,
461        /// The architecture the hypervisor is currently built for.
462        current_arch: Arch,
463    },
464}
465
466/// The unified device trait.
467///
468/// Every emulated device (interrupt controller, UART, virtio-blk, …)
469/// implements this trait.  The device manager calls [`resources`](Device::resources)
470/// at registration time for conflict detection and [`handle`](Device::handle)
471/// on the hot path whenever a vCPU exit is dispatched to this device.
472///
473/// # Downcasting
474///
475/// `Device` extends [`Any`](core::any::Any) so callers can downcast to a
476/// concrete device type via [`as_any`](Device::as_any):
477///
478/// ```ignore
479/// if let Some(vgic) = device.as_any().downcast_ref::<VGicD>() {
480///     vgic.assign_irq(32, cpu_id, (0, 0, 0, cpu_id));
481/// }
482/// ```
483pub trait Device: Send + Sync + Any {
484    /// Returns a human-readable name for this device (used in logging and
485    /// diagnostics).
486    fn name(&self) -> &str;
487
488    /// Returns the resources (MMIO windows, port ranges, system registers)
489    /// this device requires.
490    ///
491    /// The returned slice is a stable snapshot computed at device construction
492    /// time. Callers may read it on both the registration path and the hot
493    /// path without allocation.
494    fn resources(&self) -> &[Resource];
495
496    /// Handles a single bus access.
497    ///
498    /// This is the hot-path entry point called from [`BusRouter::dispatch`].
499    fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError>;
500
501    /// Returns a reference to `self` as `&dyn Any` for downcasting.
502    fn as_any(&self) -> &dyn Any;
503
504    /// Resets the device to its power-on state.
505    #[allow(unused_variables)]
506    fn reset(&mut self) -> Result<(), DeviceError> {
507        Ok(())
508    }
509
510    /// Puts the device into a low-power or suspended state.
511    #[allow(unused_variables)]
512    fn suspend(&mut self) -> Result<(), DeviceError> {
513        Ok(())
514    }
515
516    /// Restores the device from a suspended state.
517    #[allow(unused_variables)]
518    fn resume(&mut self) -> Result<(), DeviceError> {
519        Ok(())
520    }
521}
522
523/// Device registration interface — the build-time / management-path half of a
524/// [`AxVmDevices`].
525///
526/// Used when constructing or reconfiguring a VM; not on the vCPU hot path.
527pub trait DeviceRegistry {
528    /// Registers a device, performing resource conflict detection and
529    /// architecture-suitability checks.
530    ///
531    /// On success the device is assigned a unique [`DeviceId`] and inserted
532    /// into the manager's lookup structures.
533    fn register(&mut self, device: Arc<dyn Device>) -> Result<DeviceId, RegistryError>;
534}
535
536/// Bus dispatch interface — the runtime hot-path half of a
537/// [`AxVmDevices`].
538///
539/// Called on every vCPU exit that targets an emulated device (MMIO / Port /
540/// SysReg).
541pub trait BusRouter {
542    /// Looks up the device responsible for `access` and forwards the access
543    /// to it, returning the result.
544    fn dispatch(&self, access: &BusAccess) -> Result<BusResponse, DeviceError>;
545
546    /// Looks up the device responsible for `access` without handling the
547    /// access.  The caller can then inspect the device or call
548    /// [`Device::handle`] manually.
549    fn lookup(&self, access: &BusAccess) -> Result<Arc<dyn Device>, DeviceError>;
550}
551
552// ---------------------------------------------------------------------------
553// Sub-modules
554// ---------------------------------------------------------------------------
555
556mod adapter;
557mod irq;
558
559pub use adapter::{MmioDeviceAdapter, PortDeviceAdapter, SysRegDeviceAdapter};
560pub use irq::{IrqLine, IrqSink};