use core::ptr;
use ax_kspin::SpinNoIrq;
use ax_memory_addr::PhysAddr;
use axdevice_base::{AccessWidth, BaseDeviceOps};
use axvm_types::{GuestPhysAddr, GuestPhysAddrRange, 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,
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,
cpu_id,
host_gicr_base_this_cpu,
regs: SpinNoIrq::new(VGicRRegs { propbaser: 0 }),
}
}
}
impl BaseDeviceOps<GuestPhysAddrRange> for VGicR {
fn emu_type(&self) -> axdevice_base::EmuDeviceType {
axdevice_base::EmuDeviceType::GPPTRedistributor
}
fn address_range(&self) -> GuestPhysAddrRange {
GuestPhysAddrRange::from_start_size(self.addr, self.size)
}
fn handle_read(
&self,
addr: <GuestPhysAddrRange as axdevice_base::DeviceAddrRange>::Addr,
width: AccessWidth,
) -> ax_errno::AxResult<usize> {
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
);
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);
}
}
}
fn handle_write(
&self,
addr: <GuestPhysAddrRange as axdevice_base::DeviceAddrRange>::Addr,
width: AccessWidth,
value: usize,
) -> ax_errno::AxResult<()> {
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
);
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);
}
}
}
}
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);
}