use core::ptr;
use ax_kspin::SpinNoIrq;
use ax_memory_addr::PhysAddr;
use axdevice_base::{
AccessWidth, BusAccess, BusKind, BusResponse, Device, DeviceAccess, DeviceError, DeviceResult,
Resource,
};
use axvm_types::{GuestPhysAddr, HostPhysAddr};
use log::{debug, trace};
use spin::Once;
use super::{
registers::*,
utils::{perform_mmio_read, perform_mmio_write},
};
use crate::host;
pub const DEFAULT_SIZE_PER_GICR: usize = 0x20000;
pub struct VGicRRegs {
pub propbaser: usize,
}
pub struct VGicR {
pub addr: GuestPhysAddr,
pub size: usize,
resources: [Resource; 1],
pub cpu_id: usize,
pub host_gicr_base_this_cpu: HostPhysAddr,
pub regs: SpinNoIrq<VGicRRegs>,
}
impl VGicR {
fn with_regs<R>(&self, f: impl FnOnce(&VGicRRegs) -> R) -> R {
f(&self.regs.lock())
}
fn with_regs_mut<R>(&self, f: impl FnOnce(&mut VGicRRegs) -> R) -> R {
f(&mut self.regs.lock())
}
pub fn new(addr: GuestPhysAddr, size: Option<usize>, cpu_id: usize) -> Self {
let size = size.unwrap_or(DEFAULT_SIZE_PER_GICR);
let host_gicr_base_this_cpu = crate::api_reexp::get_host_gicr_base() + cpu_id * size;
Self {
addr,
size,
resources: [Resource::MmioRange {
base: addr.as_usize() as u64,
size: size as u64,
}],
cpu_id,
host_gicr_base_this_cpu,
regs: SpinNoIrq::new(VGicRRegs { propbaser: 0 }),
}
}
}
impl VGicR {
fn contains(&self, addr: GuestPhysAddr) -> bool {
let base = self.addr.as_usize();
let end = base.saturating_add(self.size);
let addr = addr.as_usize();
addr >= base && addr < end
}
pub fn read_register(&self, addr: GuestPhysAddr, width: AccessWidth) -> DeviceResult<usize> {
if !self.contains(addr) {
return Err(DeviceError::OutOfRange {
addr: addr.as_usize() as u64,
});
}
let gicr_base = self.host_gicr_base_this_cpu;
let reg = addr - self.addr;
debug!(
"vGICR ({} @ {:#x}) read reg {:#x} width {:?}",
self.cpu_id, self.addr, reg, width
);
let result = match reg {
GICR_CTLR => {
perform_mmio_read(gicr_base + reg, width)
}
GICR_TYPER => {
let mut value = perform_mmio_read(gicr_base + reg, width)?;
if true {
value |= GICR_TYPER_LAST;
}
Ok(value)
}
GICR_IIDR | GICR_IMPL_DEF_IDENT_REGS_START..=GICR_IMPL_DEF_IDENT_REGS_END => {
perform_mmio_read(gicr_base + reg, width)
}
GICR_PENDBASER => {
perform_mmio_read(gicr_base + reg, width)
}
GICR_PROPBASER => {
Ok(self.with_regs(|r| r.propbaser))
}
GICR_SYNCR => {
Ok(0)
}
GICR_SETLPIR | GICR_CLRLPIR | GICR_INVALLR => perform_mmio_read(gicr_base + reg, width),
reg if reg == GICR_STATUSR
|| reg == GICR_WAKER
|| reg == GICR_IGROUPR
|| reg == GICR_ISENABLER
|| reg == GICR_ICENABLER
|| reg == GICR_ISPENDR
|| reg == GICR_ICPENDR
|| reg == GICR_ISACTIVER
|| reg == GICR_ICACTIVER
|| reg == GICR_IGRPMODR
|| GICR_IPRIORITYR_RANGE.contains(®)
|| GICR_ICFGR_RANGE.contains(®) =>
{
perform_mmio_read(gicr_base + reg, width)
}
_ => {
todo!("vgicr read unimplemented for reg {:#x}", reg);
}
};
Ok(result?)
}
pub fn write_register(
&self,
addr: GuestPhysAddr,
width: AccessWidth,
value: usize,
) -> DeviceResult<()> {
if !self.contains(addr) {
return Err(DeviceError::OutOfRange {
addr: addr.as_usize() as u64,
});
}
let gicr_base = self.host_gicr_base_this_cpu;
let reg = addr - self.addr;
debug!(
"vGICR ({} @ {:#x}) write reg {:#x} width {:?} value {:#x}",
self.cpu_id, self.addr, reg, width, value
);
let result = match reg {
GICR_CTLR => {
perform_mmio_write(gicr_base + reg, width, value)
}
GICR_PENDBASER => {
perform_mmio_write(gicr_base + reg, width, value)
}
GICR_PROPBASER => {
self.with_regs_mut(|r| r.propbaser = value);
Ok(())
}
GICR_SETLPIR | GICR_CLRLPIR | GICR_INVALLR => {
perform_mmio_write(gicr_base + reg, width, value)
}
GICR_INVLPIR => {
enable_one_lpi((value & 0xffffffff) - 8192); Ok(())
}
reg if reg == GICR_STATUSR
|| reg == GICR_WAKER
|| reg == GICR_IGROUPR
|| reg == GICR_ISENABLER
|| reg == GICR_ICENABLER
|| reg == GICR_ISPENDR
|| reg == GICR_ICPENDR
|| reg == GICR_ISACTIVER
|| reg == GICR_ICACTIVER
|| reg == GICR_IGRPMODR
|| GICR_IPRIORITYR_RANGE.contains(®)
|| GICR_ICFGR_RANGE.contains(®) =>
{
let mut value = value;
if reg == GICR_ICENABLER {
value &= !(1 << MAINTENACE_INTERRUPT);
}
perform_mmio_write(gicr_base + reg, width, value)
}
_ => {
todo!("vgicr write unimplemented for reg {:#x}", reg);
}
};
Ok(result?)
}
}
impl Device for VGicR {
fn name(&self) -> &str {
"aarch64-gic-redistributor"
}
fn resources(&self) -> &[Resource] {
&self.resources
}
fn access(
&self,
access: &BusAccess,
_context: &mut dyn DeviceAccess,
) -> Result<BusResponse, DeviceError> {
if access.kind != BusKind::Mmio {
return Err(DeviceError::OutOfRange { addr: access.addr });
}
let addr = GuestPhysAddr::from_usize(access.addr as usize);
if access.is_read {
self.read_register(addr, access.width)
.map(|value| BusResponse::Read {
value: value as u64,
})
} else {
self.write_register(addr, access.width, access.data as usize)
.map(|_| BusResponse::Write)
}
}
}
pub struct LpiPropTable {
frame: PhysAddr,
frame_pages: usize,
_host_gicr_base: HostPhysAddr,
}
impl Drop for LpiPropTable {
fn drop(&mut self) {
trace!("LpiPropTable dropped, frame: {:?}", self.frame);
host::dealloc_contiguous_frames(self.frame, self.frame_pages);
}
}
impl LpiPropTable {
fn new(
host_gicd_typer: u32,
host_gicr_base: HostPhysAddr,
size_per_gicr: Option<usize>,
cpu_num: usize,
) -> Self {
let size_per_gicr = size_per_gicr.unwrap_or(DEFAULT_SIZE_PER_GICR);
let id_bits = (host_gicd_typer >> 19) & 0x1f;
let page_num: usize = ((1 << (id_bits + 1)) - 8192) / ax_memory_addr::PAGE_SIZE_4K;
debug!(
"Creating LPI prop table: id_bits: {id_bits}, page_num: {page_num}, size_per_gicr: \
{size_per_gicr}"
);
let f = host::alloc_contiguous_frames(page_num, ax_memory_addr::PAGE_SIZE_4K)
.expect("Failed to allocate contiguous frames for LPI prop table");
let propreg = f.as_usize() | 0x78f;
for id in 0..cpu_num {
let propbaser = host_gicr_base + id * size_per_gicr + GICR_PROPBASER;
let propbaser = host::phys_to_virt(propbaser);
debug!("Setting propbaser for CPU {id}: {propbaser:#x} -> {propreg:#x}");
unsafe {
ptr::write_volatile(propbaser.as_mut_ptr_of::<u64>(), propreg as _);
}
}
Self {
frame: f,
frame_pages: page_num,
_host_gicr_base: host_gicr_base,
}
}
fn enable_one_lpi(&self, lpi: usize) {
debug!("Enabling one LPI: {lpi}");
let addr = self.frame + lpi;
let val = 0b1;
let addr = host::phys_to_virt(addr);
unsafe {
ptr::write_volatile(addr.as_mut_ptr_of::<u8>(), val);
}
}
}
pub static LPT: Once<SpinNoIrq<LpiPropTable>> = Once::new();
pub fn get_lpt(
host_gicd_typer: u32,
host_gicr_base: HostPhysAddr,
size_per_gicr: Option<usize>,
) -> &'static SpinNoIrq<LpiPropTable> {
if !LPT.is_completed() {
LPT.call_once(|| {
SpinNoIrq::new(LpiPropTable::new(
host_gicd_typer,
host_gicr_base,
size_per_gicr,
host::host_cpu_num(),
))
});
}
LPT.get().unwrap()
}
pub fn enable_one_lpi(lpi: usize) {
let lpt = get_lpt(
crate::api_reexp::read_vgicd_typer(),
crate::api_reexp::get_host_gicr_base(),
None, );
let lpt = lpt.lock();
lpt.enable_one_lpi(lpi);
}