Axvisor 模拟设备框架
Axvisor 的模拟设备由 Hypervisor 在软件中实现,客户机通过 MMIO、x86 Port I/O 或架构系统寄存器访问这些设备。用户配置只描述稳定 ID、model 名和设备语义参数,地址、中断、MSI、host IRQ 与固件 identity 均由 machine profile、host snapshot 和设备图统一规划。
这套框架主要分布在 virtualization/axdevice_base、virtualization/axdevice、virtualization/axvmconfig 和 virtualization/axvm 中。本文以现有代码为准,说明配置解析、设备型号注册、设备图构建、资源规划、运行时注册、访问分派、直接内存访问授权、中断连接、固件生成以及各架构现有设备实现。架构能力与设备能力为什么采用不同的分层方法,见《AxVM 分层能力接口设计》。
设备体系不因架构接口重构而机械拆分。同一设备模型必须先声明命名资源,再消费由同一计划签发的资源完成构建;资源需求、申请方式、节点种类和解析后资源表示规划状态与所有权,适合使用封闭数据类型。设备访问、轮询、中断控制和生命周期等“能做什么”才使用可选能力接口。这个边界保证设备主线继续保持“设备图解析 → 一次性资源申请 → 原子注册 → 封存运行期”,也保留原有失败回滚语义。
1. 代码组成
模拟设备没有集中在单一目录。公共访问接口与运行时保持架构无关;GIC、PLIC、IOAPIC、PCH-PIC、fw_cfg、串口和 host replacement 等设备由 AxVM 的架构 prepare 阶段加入同一张设备图。
1.1 核心 crate
核心依赖方向可以概括为 axvm -> axdevice -> axdevice_base;axvmconfig 位于配置边界,负责把 TOML 解析为不含数字硬件资源的请求。
| 位置 | 主要内容 | 运行阶段 |
|---|---|---|
virtualization/axdevice_base/src/lib.rs | Device、DeviceAccess、DeviceContext、GuestMemoryAccess、Resource、grant、IRQ 与 MSI 基础接口 | 注册与访问热路径 |
virtualization/axdevice/src/model.rs | DeviceModel、DeviceFirmwareSpec | 设备声明与构建 |
virtualization/axdevice/src/graph/* | DeviceNodeSpec、DeviceGraphBuilder、ResolvedDeviceGraph | VM prepare |
virtualization/axdevice/src/resources/* | DeviceRequirements、ResourcePools、VmResourcePlanner、claim/lease | 资源规划与构建校验 |
virtualization/axdevice/src/device.rs | DeviceRuntime、资源索引、总线分派、grant 校验 | VM prepare 与 VM-exit |
virtualization/axdevice/src/registration.rs | DeviceBundle、pollable、DMA pollable、lifecycle、interrupt-controller 能力 | 设备构建与 VM 生命周期 |
virtualization/axdevice/src/fw_cfg/*、serial/*、x86/* | 通用和 x86 设备实现 | 设备构建与访问 |
virtualization/axvmconfig/src/lib.rs | GuestDevices、VirtualDeviceRequest 配置入口 | TOML 解析 |
virtualization/axvm/src/configured* | 空 ConfiguredDeviceCatalog、显式注册与 configured device 构造 | 配置请求转设备图节点 |
virtualization/axvm/src/configured/devices/* | AxVM-owned IVC、virtio-blk、virtio-net model 与 runtime glue | 配置实例化、设备构建与访问 |
virtualization/axvm/src/vm/prepare/device_plan/* | 设备图合成、guest RAM 保留、host passthrough 节点、资源池接入 | VM prepare |
virtualization/axvm/src/arch/* | 架构默认节点、资源池、固件 plan、VM-exit 接入 | VM prepare 与 vCPU 运行期 |
下图按 VM 边界划分各层职责。箭头表示构建期注入或运行时调用,不表示完整 Rust 依赖图。
设备实现仍只依赖公共契约。VM 内存、定时器、vCPU 唤醒、VM 停止请求和中断控制器等能力通过窄接口注入,而不是把 AxVM 对象交给设备。
1.2 配置入口与内置 model
GuestConfig 使用 [devices] 下的三类设备选择:passthrough、disabled 和 virtual。普通虚拟设备通过 [[devices.virtual]] 声明,配置项由稳定 ID、model 名和 model 自己解释的 options 组成。
[[devices.virtual]]
id = "console0"
model = "pl011-mmio"
clock_hz = 48000000
backend = { type = "host-console" }
[[devices.virtual]]
id = "ivc0"
model = "ivc-channel"
普通虚拟设备配置不得填写 base_gpa、mmio_base、pio_base、irq_id、msi_device_id、msi_event_id、lpi_id 等框架资源字段。这些值如果来自用户,会在 VirtualDeviceRequest::validate() 中被拒绝;如果确实需要固定资源,必须由 machine profile、host firmware snapshot 或架构内部节点产生 FixedDeviceBindings 或 fixed DeviceRequirement。
AxVM 公共注册入口显式注册的用户可选 model 如下。
| model | 设备语义 | 资源声明 |
|---|---|---|
pl011-mmio | PL011 串口 | MMIO registers + wired IRQ irq |
uart16550-mmio | MMIO 16550 串口 | MMIO registers + wired IRQ irq |
uart16550-pio | x86 PIO 16550 串口 | PIO registers + wired IRQ irq |
ivc-channel | Axvisor IVC 共享窗口与通知端点 | MMIO registers + wired IRQ notify |
virtio-blk | VirtIO MMIO 块设备,或现代 VirtIO PCI 同步 ramdisk | MMIO mmio + wired IRQ irq,或 PCI host/BAR0 + endpoint-owned INTA + DmaGrant |
virtio-net | VirtIO MMIO 网卡与 AxVM 内部交换机端口 | auto MMIO mmio + wired IRQ irq |
ConfiguredDeviceCatalog::new() 创建空 catalog。Axvisor 构造 VM 参数时显式调用 axvm::machine::register_devices(),但不拥有上述通用设备的 model、backend 或 runtime 实现。未注册 model 返回 UnknownVirtualDeviceModel,prepare 阶段会明确失败。
1.3 总体流程
从 TOML 到一次设备访问,主路径分为 prepare 和 VM-exit 两段。prepare 生成静态设备图和资源计划;VM-exit 热路径只构造包含 source vCPU 的不可变请求、查找索引、创建能力上下文,并调用 Device::read() 或 Device::write()。
host MMIO passthrough 不是模拟设备访问路径。passthrough 和 passthrough_addresses 会被规范化成 HostPassthroughMapping 节点,参与资源冲突规划,然后在地址空间准备阶段映射。x86 的 passthrough_ports 是例外:端口访问不能用 stage-2 映射表达,因此会创建 HostPortPassthroughDeviceModel,最终仍由 DeviceRuntime 分派到宿主 in/out 适配器。
2. 配置与 model 注册
设备框架把“用户想要什么设备”和“这个设备落在哪个地址/中断”分成两个阶段。用户只提交 VirtualDeviceRequest;catalog 把 request 变成持有 Arc<dyn DeviceModel> 的 DeviceNodeSpec;资源规划器再根据 model 的 slot 声明分配资源。
2.1 VirtualDeviceRequest
VirtualDeviceRequest 的 TOML 边界只有两个框架字段:id 和 model。其余字段全部保存在 options: toml::Table 中,由具体 model 自己用 serde(deny_unknown_fields) 解析。
| 字段 | 含义 | 校验 |
|---|---|---|
id | VM 内稳定设备身份,用于 graph 排序、资源计划和诊断 | 非空;ASCII 字母数字或 -、_、.、@ |
model | catalog 中注册的模型名 | 非空;小写字母数字或 -、. |
| options | 设备语义参数,例如串口 clock、backend、寄存器布局 | 由 model 类型化解析;未知字段通常失败 |
同一 VM 中 devices.virtual 的 ID 必须唯一。console0 是保留的默认串口 ID,但它不是不可变设备:用户可以用同 ID 完整替换默认串口的 model/options。
2.2 默认串口
每台 VM 始终有一个 console0。如果用户没有显式配置,AxVM 根据 machine profile 和 host firmware snapshot 创建默认请求。
| 架构 | 默认来源 |
|---|---|
| AArch64 / RISC-V | 优先使用 host FDT 选择的 UART 与固件 identity |
| x86_64 / LoongArch64 | 优先使用 host ACPI SPCR;否则使用 machine fallback |
当用户配置的 console0 与默认 model/transport 兼容时,保留 host 或 machine 提供的固定地址、IRQ 和固件 identity;当 model 不兼容时,它变成普通自动分配的虚拟串口。同 ID 不做逐字段 TOML merge,而是完整替换请求。每台 VM 最多只能有一个 host-console backend owner;额外串口默认使用 null backend,除非显式声明。
[[devices.virtual]]
id = "console0"
model = "uart16550-mmio"
backend = { type = "host-console" }
[[devices.virtual]]
id = "serial1"
model = "uart16550-pio"
backend = { type = "null" }
2.3 ConfiguredDeviceCatalog
ConfiguredDeviceCatalog 保存 model -> (owner, ConfiguredModelRegistration)。catalog 本身始终为空;每个 owning layer 通过普通 Rust 代码调用 register(owner, registration)。AxVM 的公共注册文件使用 显式 mod 和 register() 调用装入 serial、IVC、virtio-blk 与 virtio-net。重复 model 的错误同时报告第一个 owner 和冲突 owner。
pub struct ConfiguredModelRegistration {
pub model: &'static str,
pub create: ConfiguredModelConstructor,
}
不使用注册宏、build script 扫描、linker section 或兼容 alias。新增只使用现有资源与固件 contribution 的 AxVM 通用设备时,只修改 configured/devices/<device>.rs 和同目录 mod.rs 中的显式 mod/register() 两处;Axvisor 只提供 GuestConfig、串口 backend factory 等 VM 参数。
构造函数接收已验证的 DeviceNodeId、原始 request 和 DeviceInstantiationContext。它负责解析 options、选择默认 wired/MSI 域、接入固定资源绑定,并返回一个 DeviceNodeSpec。普通构造函数不直接分配地址或中断,也不接触 DeviceRuntime。
2.4 DeviceInstantiationContext
DeviceInstantiationContext 是配置实例化阶段能看到的 VM 侧信息。它只暴露必要的稳定能力,避免普通设备依赖架构 enum 或裸 IRQ。
| 方法或字段 | 含义 |
|---|---|
vm_id() | 本 VM ID;供需要 VM 本地 backend 的设备使用 |
default_wired_controller() | 默认 wired interrupt controller ID |
default_wired_controller_node() | 需要作为 graph 依赖的控制器节点 ID |
fixed_bindings() | machine/host 生成的固定 MMIO/PIO/IRQ 绑定 |
firmware_binding() | host replacement 需要保留的 FDT/ACPI identity |
serial_profile() | 默认串口模型、transport、clock 与寄存器布局 |
serial_backend_factory() | 创建 host-console backend |
host_console_by_default() | 该串口在无显式 backend 时是否拥有 host-console |
console0 的默认固定资源和固件 identity 就是通过这个 context 交给串口 model 的。普通额外串口通常拿到空 FixedDeviceBindings,因此资源来自自动池。
3. 设备图与资源规划
设备图是模拟设备框架的中心。它把架构内部设备、用户配置设备、host replacement、firmware-only 节点和 host passthrough 保留统一放入一个拓扑,再一次性规划资源。
3.1 DeviceNodeSpec
DeviceNodeSpec 是未封存的设备图节点。节点 ID 是 VM 内稳定身份;节点 kind 表示运行时所有权和固件语义。
| kind | 含义 | 是否构建 runtime 设备 |
|---|---|---|
Virtual | 完全由 VMM 实现的普通虚拟设备 | 是 |
HostReplacement | 保留 host 固件 identity 和资源,但 runtime 是虚拟状态机 | 是 |
HostPassthrough | 真实 host MMIO 被映射给 guest | 否 |
FirmwareOnly | 只进入固件图,或作为 dependency/container | 否 |
runtime-backed 节点持有 Arc<dyn DeviceModel>。HostPassthrough 节点只持有固定资源声明和 HostPassthroughMapping;FirmwareOnly 节点持有空 requirements。
节点可记录两个拓扑关系。
| 字段 | 用途 |
|---|---|
parent | 固件层级父节点 |
dependencies | 构建顺序依赖,例如普通设备依赖默认中断控制器 |
DeviceGraphBuilder::declare() 会校验依赖存在性、重复依赖和环,然后按确定性拓扑序调用每个 runtime model 的 requirements()。
3.2 DeviceModel
DeviceModel 是设备声明、固件描述和运行时构建的唯一对象。资源计划不能在 build 阶段换成另一个配置,因为同一个 Arc<dyn DeviceModel> 从声明阶段保留到构建阶段。
这里刻意不把“声明资源”和“构建设备”拆成两个互不关联的接口。二者由同一个模型拥有,才能保证构建消费的正是声明阶段参与冲突检查和确定性分配的那份需求。分层的目标是收敛能力与所有权,而不是让每个方法各自成为一层。
pub trait DeviceModel: Send + Sync {
fn requirements(&self) -> DeviceManagerResult<DeviceRequirements>;
fn firmware(&self) -> DeviceFirmwareSpec;
fn build(&self, context: &mut DeviceBuildContext<'_>) -> DeviceManagerResult<DeviceBundle>;
}
requirements() 只声明命名 slot 和资源需求;firmware() 必须显式返回 None 或 Interfaces { fdt, acpi },并用同一批 slot 描述 typed FDT/ACPI contribution;build() 只能通过 DeviceBuildContext 消费规划好的资源,然后返回原子 DeviceBundle。DeviceNodeSpec 创建时对 firmware() 求值一次并冻结;declare/resolve/composer 不再回调 model。Interfaces 两侧都为 None,或已声明一侧却给出空 contribution,都会在 declaration 阶段失败。
3.3 资源需求类型
每个 model 用 ResourceSlot 给自己的资源命名。slot 是 model 内部稳定 ABI,同一 model 的 requirements() 中不能重复声明。
DeviceRequirement | 资源含义 | 分配请求 |
|---|---|---|
Mmio | 客户机物理地址窗口 | Auto 或固定 base |
Pio | x86 Port I/O 范围 | Auto 或固定 base |
WiredIrq | 某个虚拟中断控制器的输入线 | Auto 或固定 controller input |
HostIrq | 物理 IRQ 身份,供 passthrough/replacement 路径使用 | Auto 或固 定 host IRQ |
Msi | ITS/message controller 中连续 MSI event/LPI 范围 | DeviceID/EventID/LPI 可分别固定或自动 |
MMIO 和 PIO 要求非零长度和 2 的幂对齐;MSI count 必须非零。资源 slot 错误会在 prepare 阶段失败,而不是运行期猜测默认值。
3.4 资源池与规划顺序
每个架构提供自己的 ResourcePools。资源池包含自动范围、固定范围 allowlist,以及被 guest RAM 或物理中断占用的保留项。
| 架构 | 自动 MMIO | 自动 PIO | 自动 wired 输入 | MSI |
|---|---|---|---|---|
| x86_64 | 0x8000_0000..0xc000_0000 | 0x1000..0x5000 | GSI 5..16 | 无默认 MSI 池 |
| AArch64 | 0x0b00_0000..0x1000_0000 | 无 | SPI 32..32+spi_count | GICv3 ITS 存在时提供 DeviceID/EventID/LPI 池 |
| RISC-V | 0x1100_0000..0x2000_0000 | 无 | PLIC source 1..1024 | 无默认 MSI 池 |
| LoongArch64 | 0x3000_0000..0x4000_0000 | 无 | PCH-PIC input 20..32 | 无默认 MSI 池 |
VmResourcePlanner 的规划是确定性的:
- 收集所有
DevicePlanRequest; - 按 device ID 排序并拒绝重复 ID;
- 将 fixed 需求排在 auto 需求之前;
- 在同类需求内按 device ID 和 slot 名排序;
- fixed 资源必须落在 allowlist 内且不冲突;
- auto 资源从对应自动池中 lowest-first 分配;
- 全部成功后才发布
VmResourcePlan。
这个顺序保证调整 TOML 中普通设备的顺序不会改变稳定 ID 对应的资源结果。失败时规划状态不会泄露到 runtime。
3.5 Guest RAM、host passthrough 与 replacement
VmDevicePlan::build() 会先把 guest RAM 作为 MMIO 保留项加入资源池,避免虚拟设备自动分配到 RAM 区间。随后将架构/配置节点中固定 MMIO 范围收集为 replacement ranges,再把 host passthrough 设备切成不覆盖 replacement 的 HostPassthrough 节点。
这种顺序有两个重要结果:
- 普通虚拟设备和 host passthrough 使用同一套冲突检查,MMIO 重叠会在 prepare 阶段失败。
- host replacement 覆盖的 host MMIO 不会再被映射成 passthrough,避免同一地址既由设备模拟又直通给客户机。
4. 运行时构建与注册
资源规划完成后,ResolvedDeviceGraph 同时服务固件生成和 runtime 构建。非 runtime 节点的 fixed 资源会被转换为 VM 生命周期内的 lease;runtime 节点则在构建设备时消费自己的 one-shot claim。
4.1 DeviceBuildContext
DeviceRuntimeBuilder::build_graph_node() 对每个 runtime 节点执行以下步骤:
plan.claim_device(node.id())发出该设备的所有 slot claim;- 创建
DeviceBuildContext::planned(interrupt_registry, claims); - 调用原 model 的
build(&mut context); context.finish(bundle)要求所有 slot 都已消费,并把 lease/endpoint 放进 bundle;DeviceRuntime::register_bundle()原子注册 bundle。
DeviceBuildContext 提供的消费接口如下。
| 方法 | 消费资源 | 返回值 |
|---|---|---|
mmio(slot) | MMIO claim | (base, size) |
pio(slot) | PIO claim | (base, size) |
host_irq(slot) | host IRQ claim | HostIrqId |
irq(slot) | wired IRQ claim | 已连接到控制器 input 的 IrqLine |
msi(slot) | count 为 1 的 MSI claim | MsiEndpoint |
msi_range(slot) | 连续 MSI range claim | MsiEndpointRange |
如果 model 声明了某个 slot 却没有在 build 中消费,finish planned device build 会失败;如果 build 尝试读取未声明或种类不匹配的 slot,也会失败。
4.2 DeviceBundle
DeviceBundle 是一个原子注册单元,可以同时包含设备对象、敏感能力授权、pollable、DMA pollable、lifecycle、typed service 和 interrupt-controller 能力。
| Bundle 内容 | 典型实例 |
|---|---|
一个 Device | x86 CMOS、PCI config、LoongArch PCH-PIC |
多个 Device | AArch64 VGIC distributor/redistributor/ITS frontends |
| 设备与 DMA grant | fw_cfg MMIO/PIO 设备 |
| 设备与 stop grant | x86 ACPI PM timer 发起 VM stop 请求 |
| 设备与 interrupt controller | IOAPIC、VGIC、vPLIC、PCH-PIC |
| 设备与 service | x86 PIC/PIT/IOAPIC、AArch64 VGIC runtime、PCH-PIC output port |
| 仅 service | IVC aperture allocator 与 notify endpoint |
| DMA pollable | 需要在 VM 轮询中临时访问 guest memory 的异步设备 |
grant 使用 bundle-local 设备下标记录归属,直到注册成功后才换算为最终 DeviceId。这允许一个 model 一次性提交完整能力组合,而不暴露 DeviceRuntime 内部字段。
4.3 DeviceRuntime
一台 prepare 完成的 VM 保存一个 sealed DeviceRuntime。核心字段如下。
| 字段 | 数据结构 | 保存的内容 |
|---|---|---|
devices | Vec<Arc<dyn Device>> | 已注册设备;下标是最终 DeviceId |
mmio_index | BTreeMap<u64, RangeEntry> | MMIO 起始 GPA、长度和设备下标 |
port_index | BTreeMap<u16, RangeEntry> | x86 I/O port 起始端口、长度和设备下标 |
sysreg_index | BTreeMap<u32, RangeEntry> | 系统寄存器编码、数量和设备下标 |
pollable_devices | Vec<Arc<dyn PollableDeviceOps>> | 普通周期轮询能力 |
dma_pollable_devices | Vec<(DeviceId, Arc<dyn DmaPollableDeviceOps>, DmaGrant)> | 带临时 guest-memory 端口的轮询能力 |
lifecycle_devices | Vec<Arc<dyn DeviceLifecycle>> | reset、suspend、resume 能力 |
services | DeviceServices | VM 内类型化服务 |
planned | PlannedRuntimeResources | interrupt controller、endpoint 和 lease 状态 |
dma_grants 等 | Vec<(DeviceId, Grant)> | 设备与敏感运行时能力的绑定 |
access_ports | RuntimeAccessPorts | VM 侧 timer/wake/stop 适配器 |
sealed | bool | 拓扑是否已冻结 |
注册成功后调用 finish() 会先 verify_consumed(),确保所有规划资源都处于 leased 状态,然后 seal() runtime。运行期可以修改设备内部寄存器、队列或状态机,但不能再注册设备或资源。
4.4 事务注册与回滚
register_bundle() 在写入 runtime 前先校验 bundle 内部关系:grant 下标不能越界或重复、pollable/DMA pollable/lifecycle 不能重复注册、service 不能违反基数约束、planned controller/endpoint/lease 不能冲突。
设备对象按 bundle 内顺序注册。若中途出现地址资源冲突,runtime 会把本 bundle 已追加的设备弹出,并按每个设备的 resources() 删除刚插入的索引;之前已经成功注册的 bundle 不受影响。只有所有设备注册成功后,grant、pollable、lifecycle、service 和 planned 资源才并入 runtime。
PCI endpoint bundle 还必须在 route publication 前完成 endpoint object、validated PCI contract、最终 DeviceId、routed grant、endpoint DmaGrant、IrqLine 和 root binding lease 的整组提交。route 撤回、RoutedAdmissionEpoch 关闭、scoped lease/IRQ permit drain、EndpointBindingGeneration 失效和 owner-side line withdrawal 按固定顺序执行;任何中途失败都保持 closed admission,不能释放仍可能被旧 callback 使用的 runtime handle。
5. 设备与访问模型
运行时只认识统一的 Device trait。设备协议状态保存在具体实现内部,框架通过静态 resources() 建立分派索引,通过 read() 或 write() 处理一次已经解码的客户机访问。
5.1 Device trait
Device 要求实现 Send + Sync,因为同一 VM 的设备 runtime 可能被多个 vCPU 访问。
pub trait Device: Send + Sync {
fn name(&self) -> &str;
fn resources(&self) -> &[Resource];
fn read(
&self,
access: &DeviceAccess,
context: &mut dyn DeviceContext,
) -> Result<u64, DeviceError>;
fn write(
&self,
access: &DeviceAccess,
value: u64,
context: &mut dyn DeviceContext,
) -> Result<(), DeviceError>;
}
resources() 返回的 slice 在设备构造时确定,注册后不再变化。设备内部需要修改的寄存器、FIFO、队列或 pending 状态由实现自己的锁、原子变量或后端状态机保护。
5.2 DeviceAccess 请求
架构 VM-exit 被归一化为不可变的 DeviceAccess。runtime 不解析 VMCS、ESR、trap frame 或具体指令格式。构造函数要求四个字段完整给出,字段保持私有,因此 guest MMIO、PIO 和 SysReg 请求不存在“缺少 source vCPU”的可表示状态。
| 字段或类型 | 可取值 | 说明 |
|---|---|---|
DeviceAccess.source_vcpu() | DeviceVcpuId | 发起本次访问的 VM-local vCPU,不能从 host CPU、任务或 TLS 推导 |
BusKind | Mmio、Port、SysReg | 三类地址域彼此独立 |
AccessWidth | Byte、Word、Dword、Qword | 1、2、4、8 字节 |
DeviceAccess.address() | u64 | 原始总线地址;Port/SysReg 会再收窄校验 |
DeviceAccess.width() | AccessWidth | 本次 transaction 的访问宽度 |
读写方向不再是请求中的布尔字段。read() 只能返回 u64,write() 接收独立的 value: u64 并只能返回 ();不存在运行时响应 variant 混淆,也不存在读写复用数据槽。
source_vcpu 只描述 architectural accessor。中断路由中的 target vCPU 由 GIC/PLIC/APIC 的路由状态决定,两者即使数值相同也不能复用为一个概念。例如 vCPU 0 写 GIC distributor 将 SPI 路由给 vCPU 1 时,请求 source 仍是 0,IRQ target 是 1。
5.3 Resource
注册到 runtime 的实际资源仍是 axdevice_base::Resource。地址范围采用左闭右开区间,size 或 count 不能为零。
| Resource | 地址含义 | 索引与检查 |
|---|---|---|
MmioRange { base, size } | GPA [base, base + size) | 检查 u64 加法溢出和 MMIO 重叠 |
PortRange { base, size } | x86 port [base, base + size) | 结果不能超过 0x10000,检查 PIO 重叠 |
SysReg { addr, count } | 架构寄存器编码范围 | 结果不能超过 u32 编码空间,检查 SysReg 重叠 |
IrqLine { line, trigger } | 虚拟中断输入线资源 | 由设备资源和 planned interrupt endpoint/lease 共同维持归属 |
MMIO、Port 和 SysReg 是不同地址域,数值相同不会冲突。IrqLine.line 表示虚拟控制器输入,不是宿主物理 IRQ、CPU trap vector 或 vCPU 注入向量。
5.4 地址查找
一次地址查找先在对应 BTreeMap 中获取不大于访问地址的最后一个起点,再检查完整访问宽度是否落在该区间内。跨边界访问不会调用设备。
fn lookup_mmio(&self, addr: u64, width: AccessWidth) -> Option<usize> {
let (&base, entry) = self.mmio_index.range(..=addr).next_back()?;
range_contains_access(base, entry.size, addr, width).then_some(entry.slot)
}
如果设备声明 [0x1000, 0x1004),从 0x1002 发起 4 字节访问虽然起始地址位于窗口内,但结束地址越过 0x1004,runtime 会按未命中处理。地址加访问宽度溢出时同样不会命中。
5.5 VM-exit 到 try_read() / try_write()
四个架构的 MMIO fault、x86 I/O instruction 和 AArch64 系统寄存器退出最终都会构造公共访问对象并进入 DeviceRuntime。
DeviceRuntime 只暴露 try_read() 与 try_write() 两个路由入口。未命中分别返回 None 和 false,便于架构 fault handler 继续 stage-2 或未映射总线策略。MMIO、PIO、SysReg 的差异只体现在 DeviceAccess.bus() 和架构退出完成方式, 不形成无 source 的旁路 helper。
6. 访问上下文与运行时能力
设备访问寄存器时可能需要读写 guest memory、设置定时器、唤醒 vCPU 或请求停止 VM。这些运行能力通过一次回调期间的 DeviceContext 和不可伪造 grant 控制;它们不属于描述请求事实的 DeviceAccess。
6.1 DeviceContext、GuestMemoryAccess 与 grant
RuntimeDeviceContext 在栈上创建,生命周期严格限制在一次 Device::read()、Device::write() 或 DmaPollableDeviceOps::poll_dma() 调用内。它同时检查正在访问的 DeviceId 和 grant token 是否与 runtime 注册记录匹配。
| 能力 | Grant | 使用路径 |
|---|---|---|
| guest memory | DmaGrant | read_guest_memory()、write_guest_memory() |
| 定时器 | TimerGrant | schedule_timer() |
| vCPU 唤醒 | WakeGrant | wake_vcpu() |
| VM 停止请求 | StopGrant | request_vm_stop() |
仅持有另一个设备的 grant,或临时创建同类型 grant,都不能获得能力。检查通过后还必须存在相应 VM runtime port,否则仍返回 DeviceError::Unsupported。
6.2 DMA 与 DMA pollable
VM 内存由独立的 GuestMemoryAccess capability 实现。该 trait 不携带 device identity 或 grant;DeviceRuntime 在委托前已经用 DeviceContext 验证当前设备和 DmaGrant。设备不能从 DeviceAccess、source vCPU 或 DeviceContext 反向取得 AxVM。
guest-memory 端口不会长期保存在设备中。guest 写路径可以给 runtime 注入临时 memory port;读路径没有隐式 VM 内存能力。VM 主动调用 poll_dma_devices() 时也会创建一次性 RuntimeDeviceContext。
VirtIO PCI 的 BAR/config callback 使用 routed endpoint context,而不是 root 或 NoopDeviceContext。callback 取得的 DmaGrant 必须属于该 endpoint,且 BME snapshot、binding generation 和 routed admission epoch 均有效。full reset 先关闭旧 epoch、推进 VirtioQueueGeneration 并等待所有 ActivityPermit,再发布 status 0;旧 permit 覆盖到 used/status 和 ISR/INTx publication 或 suppression 的 terminal boundary。
DmaPollableDeviceOps 使用同样的授权模型,只是入口不是总线访问,而是 VM runtime 的周期轮询。实现必须在 poll_dma() 返回前完成 guest-memory 操作,不能保存临时端口。