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};