use rvm_types::{RvmError, RvmResult};
const GICD_BASE: usize = 0x0800_0000;
const GICC_BASE: usize = 0x0801_0000;
const GICD_CTLR: usize = 0x000;
const GICD_ISENABLER: usize = 0x100;
const GICD_ICENABLER: usize = 0x180;
const GICD_IPRIORITYR: usize = 0x400;
const GICD_ITARGETSR: usize = 0x800;
const GICC_CTLR: usize = 0x000;
const GICC_PMR: usize = 0x004;
const GICC_IAR: usize = 0x00C;
const GICC_EOIR: usize = 0x010;
const MAX_IRQ: u32 = 1020;
pub const IRQ_SPURIOUS: u32 = 1023;
#[inline]
unsafe fn gic_write(base: usize, offset: usize, val: u32) {
let addr = (base + offset) as *mut u32;
unsafe {
core::ptr::write_volatile(addr, val);
}
}
#[inline]
unsafe fn gic_read(base: usize, offset: usize) -> u32 {
let addr = (base + offset) as *const u32;
unsafe { core::ptr::read_volatile(addr) }
}
pub unsafe fn gic_init() {
unsafe {
gic_write(GICD_BASE, GICD_CTLR, 0x3);
gic_write(GICC_BASE, GICC_CTLR, 0x3);
gic_write(GICC_BASE, GICC_PMR, 0xFF);
}
}
pub unsafe fn gic_enable_irq(irq: u32) -> RvmResult<()> {
if irq > MAX_IRQ {
return Err(RvmError::InternalError);
}
let reg_index = (irq / 32) as usize;
let bit = 1u32 << (irq % 32);
unsafe {
gic_write(GICD_BASE, GICD_ISENABLER + reg_index * 4, bit);
let prio_reg = (irq / 4) as usize;
let prio_shift = (irq % 4) * 8;
let prio_val = 0xA0u32 << prio_shift;
let current = gic_read(GICD_BASE, GICD_IPRIORITYR + prio_reg * 4);
let mask = !(0xFFu32 << prio_shift);
gic_write(
GICD_BASE,
GICD_IPRIORITYR + prio_reg * 4,
(current & mask) | prio_val,
);
let target_reg = (irq / 4) as usize;
let target_shift = (irq % 4) * 8;
let target_val = 0x01u32 << target_shift;
let current = gic_read(GICD_BASE, GICD_ITARGETSR + target_reg * 4);
let mask = !(0xFFu32 << target_shift);
gic_write(
GICD_BASE,
GICD_ITARGETSR + target_reg * 4,
(current & mask) | target_val,
);
}
Ok(())
}
pub unsafe fn gic_disable_irq(irq: u32) -> RvmResult<()> {
if irq > MAX_IRQ {
return Err(RvmError::InternalError);
}
let reg_index = (irq / 32) as usize;
let bit = 1u32 << (irq % 32);
unsafe {
gic_write(GICD_BASE, GICD_ICENABLER + reg_index * 4, bit);
}
Ok(())
}
#[inline]
pub unsafe fn gic_ack() -> u32 {
unsafe { gic_read(GICC_BASE, GICC_IAR) & 0x3FF }
}
#[inline]
pub unsafe fn gic_eoi(irq: u32) {
unsafe {
gic_write(GICC_BASE, GICC_EOIR, irq);
}
}
pub struct Aarch64Gic {
initialized: bool,
}
impl Aarch64Gic {
#[must_use]
pub const fn new() -> Self {
Self { initialized: false }
}
pub unsafe fn init(&mut self) {
unsafe {
gic_init();
}
self.initialized = true;
}
}
impl crate::InterruptOps for Aarch64Gic {
fn enable(&mut self, irq: u32) -> RvmResult<()> {
if !self.initialized {
return Err(RvmError::InternalError);
}
unsafe { gic_enable_irq(irq) }
}
fn disable(&mut self, irq: u32) -> RvmResult<()> {
if !self.initialized {
return Err(RvmError::InternalError);
}
unsafe { gic_disable_irq(irq) }
}
fn acknowledge(&mut self) -> Option<u32> {
if !self.initialized {
return None;
}
let irq = unsafe { gic_ack() };
if irq == IRQ_SPURIOUS {
None
} else {
Some(irq)
}
}
fn end_of_interrupt(&mut self, irq: u32) {
if !self.initialized {
return;
}
unsafe {
gic_eoi(irq);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::InterruptOps;
#[test]
fn test_constants() {
assert_eq!(GICD_BASE, 0x0800_0000);
assert_eq!(GICC_BASE, 0x0801_0000);
assert_eq!(MAX_IRQ, 1020);
assert_eq!(IRQ_SPURIOUS, 1023);
}
#[test]
fn test_gic_new() {
let gic = Aarch64Gic::new();
assert!(!gic.initialized);
}
#[test]
fn test_enable_before_init_fails() {
let mut gic = Aarch64Gic::new();
assert!(gic.enable(30).is_err());
}
#[test]
fn test_acknowledge_before_init_returns_none() {
let mut gic = Aarch64Gic::new();
assert_eq!(gic.acknowledge(), None);
}
}