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
87use alloc::{string::String, sync::Arc, vec::Vec};
88use core::any::Any;
89
90use ax_errno::AxResult;
91use axaddrspace::{
92 GuestPhysAddrRange,
93 device::{AccessWidth, DeviceAddrRange, PortRange, SysRegAddrRange},
94};
95pub use axvmconfig::EmulatedDeviceType as EmuDeviceType;
96
97/// Represents the configuration of an emulated device for a virtual machine.
98///
99/// This structure holds all the necessary information to initialize and configure
100/// an emulated device, including its memory mapping, interrupt configuration, and
101/// device-specific parameters.
102///
103/// # Fields
104///
105/// - `name`: A human-readable identifier for the device.
106/// - `base_ipa`: The starting address in guest physical address space.
107/// - `length`: The size of the device's address space in bytes.
108/// - `irq_id`: The interrupt line number for device interrupts.
109/// - `emu_type`: Numeric identifier for the device type.
110/// - `cfg_list`: Device-specific configuration parameters.
111///
112/// # Example
113///
114/// ```rust
115/// use axdevice_base::EmulatedDeviceConfig;
116///
117/// let config = EmulatedDeviceConfig {
118/// name: "uart0".into(),
119/// base_ipa: 0x0900_0000,
120/// length: 0x1000,
121/// irq_id: 33,
122/// emu_type: 1,
123/// cfg_list: vec![115200], // baud rate
124/// };
125/// ```
126#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
127pub struct EmulatedDeviceConfig {
128 /// The name of the device.
129 ///
130 /// This is a human-readable identifier used for logging, debugging, and
131 /// device tree generation. It should be unique within a virtual machine.
132 pub name: String,
133
134 /// The base IPA (Intermediate Physical Address) of the device.
135 ///
136 /// This is the starting address in the guest's physical address space
137 /// where the device's registers are mapped. The guest OS will use this
138 /// address to access the device.
139 pub base_ipa: usize,
140
141 /// The length of the device's address space in bytes.
142 ///
143 /// This defines the size of the memory region that the device occupies.
144 /// Any access within `[base_ipa, base_ipa + length)` will be routed to
145 /// this device.
146 pub length: usize,
147
148 /// The IRQ (Interrupt Request) ID of the device.
149 ///
150 /// This is the interrupt line number that the device uses to signal
151 /// events to the guest. The value should correspond to a valid interrupt
152 /// ID in the virtual interrupt controller.
153 pub irq_id: usize,
154
155 /// The type of emulated device.
156 ///
157 /// This numeric value identifies the device type and is used by the
158 /// device manager to instantiate the correct device implementation.
159 /// See [`EmuDeviceType`] for predefined device types.
160 pub emu_type: usize,
161
162 /// Device-specific configuration parameters.
163 ///
164 /// This is a list of configuration values whose meaning depends on the
165 /// specific device type. For example, a UART device might use this to
166 /// specify baud rate, while a virtio device might use it for queue sizes.
167 pub cfg_list: Vec<usize>,
168}
169
170/// The core trait that all emulated devices must implement.
171///
172/// This trait defines the common interface for all virtual devices in the hypervisor.
173/// It provides methods for device identification, address range querying, and
174/// handling read/write operations from the guest.
175///
176/// # Type Parameters
177///
178/// - `R`: The address range type that the device uses. This determines the
179/// addressing scheme (MMIO, port I/O, system registers, etc.).
180///
181/// # Implementation Notes
182///
183/// - All implementations must also implement [`Any`] to support runtime type checking.
184/// - The `handle_read` and `handle_write` methods are called by the hypervisor's
185/// trap handler when the guest accesses the device's address range.
186/// - Implementations should handle concurrent access appropriately if the device
187/// can be accessed from multiple vCPUs.
188///
189/// # Example
190///
191/// See the crate-level documentation for a complete implementation example.
192pub trait BaseDeviceOps<R: DeviceAddrRange>: Any {
193 /// Returns the type of the emulated device.
194 ///
195 /// This is used by the device manager to identify the device type and
196 /// perform type-specific operations.
197 fn emu_type(&self) -> EmuDeviceType;
198
199 /// Returns the address range that this device occupies.
200 ///
201 /// The returned range is used by the hypervisor to route guest memory
202 /// accesses to the appropriate device handler.
203 fn address_range(&self) -> R;
204
205 /// Handles a read operation on the emulated device.
206 ///
207 /// # Arguments
208 ///
209 /// - `addr`: The address within the device's range being read.
210 /// - `width`: The access width (byte, halfword, word, or doubleword).
211 ///
212 /// # Returns
213 ///
214 /// - `Ok(value)`: The value read from the device register.
215 /// - `Err(error)`: An error if the read operation failed.
216 ///
217 /// # Notes
218 ///
219 /// Implementations should respect the `width` parameter and only return
220 /// data of the appropriate size. The returned value should be zero-extended
221 /// if necessary.
222 fn handle_read(&self, addr: R::Addr, width: AccessWidth) -> AxResult<usize>;
223
224 /// Handles a write operation on the emulated device.
225 ///
226 /// # Arguments
227 ///
228 /// - `addr`: The address within the device's range being written.
229 /// - `width`: The access width (byte, halfword, word, or doubleword).
230 /// - `val`: The value to write to the device register.
231 ///
232 /// # Returns
233 ///
234 /// - `Ok(())`: The write operation completed successfully.
235 /// - `Err(error)`: An error if the write operation failed.
236 ///
237 /// # Notes
238 ///
239 /// Implementations should only use the lower bits of `val` corresponding
240 /// to the specified `width`.
241 fn handle_write(&self, addr: R::Addr, width: AccessWidth, val: usize) -> AxResult;
242}
243
244/// Attempts to downcast a device to a specific type and apply a function to it.
245///
246/// This function is useful when you have a trait object (`Arc<dyn BaseDeviceOps<R>>`)
247/// and need to access type-specific methods or data of the underlying concrete type.
248///
249/// # Type Parameters
250///
251/// - `T`: The concrete device type to downcast to. Must implement `BaseDeviceOps<R>`.
252/// - `R`: The address range type.
253/// - `U`: The return type of the mapping function.
254/// - `F`: The function to apply if the downcast succeeds.
255///
256/// # Arguments
257///
258/// - `device`: A reference to the device trait object.
259/// - `f`: A function to call with a reference to the concrete device type.
260///
261/// # Returns
262///
263/// - `Some(result)`: If the device is of type `T`, returns the result of `f`.
264/// - `None`: If the device is not of type `T`.
265///
266/// # Example
267///
268/// ```rust,ignore
269/// use axdevice_base::{BaseDeviceOps, map_device_of_type};
270/// use alloc::sync::Arc;
271///
272/// struct UartDevice {
273/// baud_rate: u32,
274/// }
275///
276/// impl UartDevice {
277/// fn get_baud_rate(&self) -> u32 {
278/// self.baud_rate
279/// }
280/// }
281///
282/// // ... implement BaseDeviceOps for UartDevice ...
283///
284/// fn check_uart_config(device: &Arc<dyn BaseMmioDeviceOps>) {
285/// if let Some(baud_rate) = map_device_of_type(device, |uart: &UartDevice| {
286/// uart.get_baud_rate()
287/// }) {
288/// println!("UART baud rate: {}", baud_rate);
289/// }
290/// }
291/// ```
292pub fn map_device_of_type<T: BaseDeviceOps<R>, R: DeviceAddrRange, U, F: FnOnce(&T) -> U>(
293 device: &Arc<dyn BaseDeviceOps<R>>,
294 f: F,
295) -> Option<U> {
296 let any_arc: Arc<dyn Any> = device.clone();
297
298 any_arc.downcast_ref::<T>().map(f)
299}
300
301// Trait aliases are limited yet: https://github.com/rust-lang/rfcs/pull/3437
302
303/// Trait alias for MMIO (Memory-Mapped I/O) device operations.
304///
305/// This is a convenience alias for [`BaseDeviceOps`] with [`GuestPhysAddrRange`]
306/// as the address range type. MMIO devices are the most common type of virtual
307/// devices, where device registers are accessed through memory read/write operations.
308///
309/// # Supported Architectures
310///
311/// MMIO devices are supported on all architectures (x86_64, ARM, RISC-V).
312pub trait BaseMmioDeviceOps = BaseDeviceOps<GuestPhysAddrRange>;
313
314/// Trait alias for system register device operations.
315///
316/// This is a convenience alias for [`BaseDeviceOps`] with [`SysRegAddrRange`]
317/// as the address range type. System register devices are typically used on
318/// ARM architectures to emulate system registers accessed via MSR/MRS instructions.
319///
320/// # Supported Architectures
321///
322/// System register devices are primarily used on ARM/AArch64 architectures.
323pub trait BaseSysRegDeviceOps = BaseDeviceOps<SysRegAddrRange>;
324
325/// Trait alias for port I/O device operations.
326///
327/// This is a convenience alias for [`BaseDeviceOps`] with [`PortRange`]
328/// as the address range type. Port I/O devices are used on x86 architectures
329/// where device registers are accessed via IN/OUT instructions.
330///
331/// # Supported Architectures
332///
333/// Port I/O devices are only used on x86/x86_64 architectures.
334pub trait BasePortDeviceOps = BaseDeviceOps<PortRange>;