use alloc::{sync::Arc, vec::Vec};
use core::ops::Range;
#[cfg(target_arch = "aarch64")]
use arm_vgic::Vgic;
use ax_errno::{AxResult, ax_err};
use ax_kspin::SpinNoIrq as Mutex;
#[cfg(target_arch = "aarch64")]
use ax_memory_addr::PhysAddr;
use ax_memory_addr::is_aligned_4k;
use axaddrspace::{
GuestPhysAddr, GuestPhysAddrRange,
device::{AccessWidth, DeviceAddrRange, Port, PortRange, SysRegAddr, SysRegAddrRange},
};
#[cfg(target_arch = "x86_64")]
use axdevice_base::map_device_of_type;
use axdevice_base::{BaseDeviceOps, BaseMmioDeviceOps, BasePortDeviceOps, BaseSysRegDeviceOps};
use axvmconfig::{EmulatedDeviceConfig, EmulatedDeviceType};
#[cfg(target_arch = "riscv64")]
use riscv_vplic::VPlicGlobal;
#[cfg(target_arch = "x86_64")]
use x86_vlapic::{EmulatedIoApic, EmulatedPit, EmulatedSerialPort, IoApicInterrupt};
use crate::{AxVmDeviceConfig, range_alloc::RangeAllocator};
pub struct AxEmuDevices<R: DeviceAddrRange> {
emu_devices: Vec<Arc<dyn BaseDeviceOps<R>>>,
}
impl<R: DeviceAddrRange + 'static> AxEmuDevices<R> {
pub fn new() -> Self {
Self {
emu_devices: Vec::new(),
}
}
pub fn add_dev(&mut self, dev: Arc<dyn BaseDeviceOps<R>>) {
self.emu_devices.push(dev);
}
pub fn find_dev(&self, addr: R::Addr) -> Option<Arc<dyn BaseDeviceOps<R>>> {
self.emu_devices
.iter()
.find(|&dev| dev.address_range().contains(addr))
.cloned()
}
pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn BaseDeviceOps<R>>> {
self.emu_devices.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Arc<dyn BaseDeviceOps<R>>> {
self.emu_devices.iter_mut()
}
}
type AxEmuMmioDevices = AxEmuDevices<GuestPhysAddrRange>;
type AxEmuSysRegDevices = AxEmuDevices<SysRegAddrRange>;
type AxEmuPortDevices = AxEmuDevices<PortRange>;
pub struct AxVmDevices {
emu_mmio_devices: AxEmuMmioDevices,
emu_sys_reg_devices: AxEmuSysRegDevices,
emu_port_devices: AxEmuPortDevices,
ivc_channel: Option<Mutex<RangeAllocator>>,
}
#[inline]
fn log_device_io(
addr_type: &'static str,
addr: impl core::fmt::LowerHex,
addr_range: impl core::fmt::LowerHex,
read: bool,
width: AccessWidth,
) {
let rw = if read { "read" } else { "write" };
trace!("emu_device {rw}: {addr_type} {addr:#x} in range {addr_range:#x} with width {width:?}")
}
#[inline]
fn panic_device_not_found(
addr_type: &'static str,
addr: impl core::fmt::LowerHex,
read: bool,
width: AccessWidth,
) -> ! {
let rw = if read { "read" } else { "write" };
error!(
"emu_device {rw} failed: device not found for {addr_type} {addr:#x} with width {width:?}"
);
panic!("emu_device not found");
}
impl AxVmDevices {
pub fn new(config: AxVmDeviceConfig) -> Self {
let mut this = Self {
emu_mmio_devices: AxEmuMmioDevices::new(),
emu_sys_reg_devices: AxEmuSysRegDevices::new(),
emu_port_devices: AxEmuPortDevices::new(),
ivc_channel: None,
};
Self::init(&mut this, &config.emu_configs);
this
}
fn init(this: &mut Self, emu_configs: &Vec<EmulatedDeviceConfig>) {
for config in emu_configs {
match config.emu_type {
EmulatedDeviceType::InterruptController => {
#[cfg(target_arch = "aarch64")]
{
this.add_mmio_dev(Arc::new(Vgic::new()));
}
#[cfg(not(target_arch = "aarch64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::GPPTRedistributor => {
#[cfg(target_arch = "aarch64")]
{
const GPPT_GICR_ARG_ERR_MSG: &str =
"expect 3 args for gppt redistributor (cpu_num, stride, pcpu_id)";
let cpu_num = config
.cfg_list
.first()
.copied()
.expect(GPPT_GICR_ARG_ERR_MSG);
let stride = config
.cfg_list
.get(1)
.copied()
.expect(GPPT_GICR_ARG_ERR_MSG);
let pcpu_id = config
.cfg_list
.get(2)
.copied()
.expect(GPPT_GICR_ARG_ERR_MSG);
for i in 0..cpu_num {
let addr = config.base_gpa + i * stride;
let size = config.length;
#[allow(clippy::arc_with_non_send_sync)]
this.add_mmio_dev(Arc::new(arm_vgic::v3::vgicr::VGicR::new(
addr.into(),
Some(size),
pcpu_id + i,
)));
info!(
"GPPT Redistributor initialized for vCPU {i} with base GPA \
{addr:#x} and length {size:#x}"
);
}
}
#[cfg(not(target_arch = "aarch64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::GPPTDistributor => {
#[cfg(target_arch = "aarch64")]
{
#[allow(clippy::arc_with_non_send_sync)]
this.add_mmio_dev(Arc::new(arm_vgic::v3::vgicd::VGicD::new(
config.base_gpa.into(),
Some(config.length),
)));
info!(
"GPPT Distributor initialized with base GPA {base_gpa:#x} and length \
{length:#x}",
base_gpa = config.base_gpa,
length = config.length
);
}
#[cfg(not(target_arch = "aarch64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::GPPTITS => {
#[cfg(target_arch = "aarch64")]
{
let host_gits_base = config
.cfg_list
.first()
.copied()
.map(PhysAddr::from_usize)
.expect("expect 1 arg for gppt its (host_gits_base)");
#[allow(clippy::arc_with_non_send_sync)]
this.add_mmio_dev(Arc::new(arm_vgic::v3::gits::Gits::new(
config.base_gpa.into(),
Some(config.length),
host_gits_base,
false,
)));
info!(
"GPPT ITS initialized with base GPA {base_gpa:#x} and length \
{length:#x}, host GITS base {host_gits_base:#x}",
base_gpa = config.base_gpa,
length = config.length,
host_gits_base = host_gits_base
);
}
#[cfg(not(target_arch = "aarch64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::PPPTGlobal => {
#[cfg(target_arch = "riscv64")]
{
let context_num = config
.cfg_list
.first()
.copied()
.expect("expect 1 arg for pppt global (context_num)");
this.add_mmio_dev(Arc::new(VPlicGlobal::new(
config.base_gpa.into(),
Some(config.length),
context_num, )));
info!(
"Partial PLIC Passthrough Global initialized with base GPA {:#x} and \
length {:#x}",
config.base_gpa, config.length
);
}
#[cfg(not(target_arch = "riscv64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::Console => {
#[cfg(target_arch = "x86_64")]
{
this.add_port_dev(Arc::new(EmulatedSerialPort::new()));
info!("x86 16550 serial initialized for ports 0x3f8..=0x3ff");
}
#[cfg(not(target_arch = "x86_64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::X86IoApic => {
#[cfg(target_arch = "x86_64")]
{
this.add_mmio_dev(Arc::new(EmulatedIoApic::new(
config.base_gpa.into(),
Some(config.length),
)));
info!(
"x86 IO APIC initialized with base GPA {:#x} and length {:#x}",
config.base_gpa, config.length
);
}
#[cfg(not(target_arch = "x86_64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::X86Pit => {
#[cfg(target_arch = "x86_64")]
{
this.add_port_dev(Arc::new(EmulatedPit::new()));
info!("x86 PIT initialized for ports 0x40..=0x43 and 0x61");
}
#[cfg(not(target_arch = "x86_64"))]
{
warn!(
"emu type: {} is not supported on this platform",
config.emu_type
);
}
}
EmulatedDeviceType::IVCChannel => {
if this.ivc_channel.is_none() {
this.ivc_channel = Some(Mutex::new(RangeAllocator::new(Range {
start: config.base_gpa,
end: config.base_gpa + config.length,
})));
info!(
"IVCChannel initialized with base GPA {base_gpa:#x} and length \
{length:#x}",
base_gpa = config.base_gpa,
length = config.length
);
} else {
warn!("IVCChannel already initialized, ignoring additional config");
}
}
_ => {
warn!(
"Emulated device {}'s type {:?} is not supported yet",
config.name, config.emu_type
);
}
}
}
}
pub fn alloc_ivc_channel(&self, size: usize) -> AxResult<GuestPhysAddr> {
if size == 0 {
return ax_err!(InvalidInput, "Size must be greater than 0");
}
if !is_aligned_4k(size) {
return ax_err!(InvalidInput, "Size must be aligned to 4K");
}
if let Some(allocator) = &self.ivc_channel {
allocator
.lock()
.allocate_range(size)
.ok_or_else(|| {
warn!("Failed to allocate IVC channel range with size {size:#x}");
ax_errno::ax_err_type!(NoMemory, "IVC channel allocation failed")
})
.map(|range| {
debug!("Allocated IVC channel range: {range:x?}");
GuestPhysAddr::from_usize(range.start)
})
} else {
ax_err!(InvalidInput, "IVC channel not exists")
}
}
pub fn release_ivc_channel(&self, addr: GuestPhysAddr, size: usize) -> AxResult {
if size == 0 {
return ax_err!(InvalidInput, "Size must be greater than 0");
}
if !is_aligned_4k(size) {
return ax_err!(InvalidInput, "Size must be aligned to 4K");
}
if let Some(allocator) = &self.ivc_channel {
let range = addr.as_usize()..addr.as_usize() + size;
if allocator.lock().free_range(range.clone()) {
debug!("Released IVC channel range: {range:x?}");
Ok(())
} else {
ax_err!(InvalidInput, "Invalid IVC channel range")
}
} else {
ax_err!(InvalidInput, "IVC channel not exists")
}
}
pub fn add_mmio_dev(&mut self, dev: Arc<dyn BaseMmioDeviceOps>) {
self.emu_mmio_devices.add_dev(dev);
}
pub fn add_sys_reg_dev(&mut self, dev: Arc<dyn BaseSysRegDeviceOps>) {
self.emu_sys_reg_devices.add_dev(dev);
}
pub fn add_port_dev(&mut self, dev: Arc<dyn BasePortDeviceOps>) {
self.emu_port_devices.add_dev(dev);
}
pub fn iter_mmio_dev(&self) -> impl Iterator<Item = &Arc<dyn BaseMmioDeviceOps>> {
self.emu_mmio_devices.iter()
}
pub fn iter_sys_reg_dev(&self) -> impl Iterator<Item = &Arc<dyn BaseSysRegDeviceOps>> {
self.emu_sys_reg_devices.iter()
}
pub fn iter_port_dev(&self) -> impl Iterator<Item = &Arc<dyn BasePortDeviceOps>> {
self.emu_port_devices.iter()
}
#[cfg(target_arch = "x86_64")]
pub fn x86_ioapic_vector_for_gsi(&self, gsi: usize) -> Option<u8> {
self.emu_mmio_devices.iter().find_map(|dev| {
map_device_of_type(dev, |ioapic: &EmulatedIoApic| ioapic.vector_for_gsi(gsi)).flatten()
})
}
#[cfg(target_arch = "x86_64")]
pub fn x86_ioapic_assert_gsi(&self, gsi: usize) -> Option<IoApicInterrupt> {
self.emu_mmio_devices.iter().find_map(|dev| {
map_device_of_type(dev, |ioapic: &EmulatedIoApic| ioapic.assert_gsi(gsi)).flatten()
})
}
#[cfg(target_arch = "x86_64")]
pub fn x86_ioapic_end_of_interrupt(&self, vector: u8) -> Option<IoApicInterrupt> {
self.emu_mmio_devices.iter().find_map(|dev| {
map_device_of_type(dev, |ioapic: &EmulatedIoApic| {
ioapic.end_of_interrupt(vector)
})
.flatten()
})
}
#[cfg(target_arch = "x86_64")]
pub fn x86_pit_consume_irq0_if_due(&self, now_ns: u64) -> bool {
self.emu_port_devices.iter().any(|dev| {
map_device_of_type(dev, |pit: &EmulatedPit| pit.consume_irq0_if_due(now_ns))
.unwrap_or(false)
})
}
#[cfg(target_arch = "x86_64")]
pub fn x86_serial_poll_irq(&self) -> bool {
self.emu_port_devices.iter().any(|dev| {
map_device_of_type(dev, |serial: &EmulatedSerialPort| serial.poll_irq())
.unwrap_or(false)
})
}
pub fn iter_mut_mmio_dev(&mut self) -> impl Iterator<Item = &mut Arc<dyn BaseMmioDeviceOps>> {
self.emu_mmio_devices.iter_mut()
}
pub fn iter_mut_sys_reg_dev(
&mut self,
) -> impl Iterator<Item = &mut Arc<dyn BaseSysRegDeviceOps>> {
self.emu_sys_reg_devices.iter_mut()
}
pub fn iter_mut_port_dev(&mut self) -> impl Iterator<Item = &mut Arc<dyn BasePortDeviceOps>> {
self.emu_port_devices.iter_mut()
}
pub fn find_mmio_dev(&self, ipa: GuestPhysAddr) -> Option<Arc<dyn BaseMmioDeviceOps>> {
self.emu_mmio_devices.find_dev(ipa)
}
pub fn find_sys_reg_dev(
&self,
sys_reg_addr: SysRegAddr,
) -> Option<Arc<dyn BaseSysRegDeviceOps>> {
self.emu_sys_reg_devices.find_dev(sys_reg_addr)
}
pub fn find_port_dev(&self, port: Port) -> Option<Arc<dyn BasePortDeviceOps>> {
self.emu_port_devices.find_dev(port)
}
pub fn handle_mmio_read(&self, addr: GuestPhysAddr, width: AccessWidth) -> AxResult<usize> {
if let Some(emu_dev) = self.find_mmio_dev(addr) {
log_device_io("mmio", addr, emu_dev.address_range(), true, width);
return emu_dev.handle_read(addr, width);
}
panic_device_not_found("mmio", addr, true, width);
}
pub fn handle_mmio_write(
&self,
addr: GuestPhysAddr,
width: AccessWidth,
val: usize,
) -> AxResult {
if let Some(emu_dev) = self.find_mmio_dev(addr) {
log_device_io("mmio", addr, emu_dev.address_range(), false, width);
return emu_dev.handle_write(addr, width, val);
}
panic_device_not_found("mmio", addr, false, width);
}
pub fn handle_sys_reg_read(&self, addr: SysRegAddr, width: AccessWidth) -> AxResult<usize> {
if let Some(emu_dev) = self.find_sys_reg_dev(addr) {
log_device_io("sys_reg", addr.0, emu_dev.address_range(), true, width);
return emu_dev.handle_read(addr, width);
}
panic_device_not_found("sys_reg", addr, true, width);
}
pub fn handle_sys_reg_write(
&self,
addr: SysRegAddr,
width: AccessWidth,
val: usize,
) -> AxResult {
if let Some(emu_dev) = self.find_sys_reg_dev(addr) {
log_device_io("sys_reg", addr.0, emu_dev.address_range(), false, width);
return emu_dev.handle_write(addr, width, val);
}
panic_device_not_found("sys_reg", addr, false, width);
}
pub fn handle_port_read(&self, port: Port, width: AccessWidth) -> AxResult<usize> {
if let Some(emu_dev) = self.find_port_dev(port) {
log_device_io("port", port.0, emu_dev.address_range(), true, width);
return emu_dev.handle_read(port, width);
}
panic_device_not_found("port", port, true, width);
}
pub fn handle_port_write(&self, port: Port, width: AccessWidth, val: usize) -> AxResult {
if let Some(emu_dev) = self.find_port_dev(port) {
log_device_io("port", port.0, emu_dev.address_range(), false, width);
return emu_dev.handle_write(port, width, val);
}
panic_device_not_found("port", port, false, width);
}
}