From 3ffe81bdf63fff79fed639f4121886fcfb553dc9 Mon Sep 17 00:00:00 2001
From: Stefan Lankes <slankes@eonerc.rwth-aachen.de>
Date: Sun, 30 Apr 2023 00:16:05 +0200
Subject: [PATCH] initialize interrupt controller

---
 src/arch/aarch64/kernel/interrupts.rs | 366 ++++++++++++++++++++++++--
 1 file changed, 338 insertions(+), 28 deletions(-)

diff --git a/src/arch/aarch64/kernel/interrupts.rs b/src/arch/aarch64/kernel/interrupts.rs
index bec9ec918..48794923c 100644
--- a/src/arch/aarch64/kernel/interrupts.rs
+++ b/src/arch/aarch64/kernel/interrupts.rs
@@ -1,10 +1,79 @@
 use core::arch::asm;
 
-const IRQ_FLAG_F: usize = 1 << 6;
-const IRQ_FLAG_I: usize = 1 << 7;
-const IRQ_FLAG_A: usize = 1 << 8;
+use aarch64::regs::*;
+use hermit_dtb::Dtb;
+use hermit_sync::{InterruptTicketMutex, OnceCell};
+use tock_registers::interfaces::Readable;
 
-/// Enable Interrupts
+use crate::arch::aarch64::kernel::boot_info;
+use crate::arch::aarch64::kernel::scheduler::State;
+use crate::arch::aarch64::mm::paging::{
+	self, virt_to_phys, BasePageSize, PageSize, PageTableEntryFlags,
+};
+use crate::arch::aarch64::mm::{virtualmem, PhysAddr, VirtAddr};
+use crate::errno::EFAULT;
+use crate::scheduler::CoreId;
+use crate::sys_exit;
+
+pub const IST_SIZE: usize = 8 * BasePageSize::SIZE as usize;
+
+/*
+ * GIC Distributor interface register offsets that are common to GICv3 & GICv2
+ */
+
+const GICD_CTLR: usize = 0x0;
+const GICD_TYPER: usize = 0x4;
+const GICD_IIDR: usize = 0x8;
+const GICD_IGROUPR: usize = 0x80;
+const GICD_ISENABLER: usize = 0x100;
+const GICD_ICENABLER: usize = 0x180;
+const GICD_ISPENDR: usize = 0x200;
+const GICD_ICPENDR: usize = 0x280;
+const GICD_ISACTIVER: usize = 0x300;
+const GICD_ICACTIVER: usize = 0x380;
+const GICD_IPRIORITYR: usize = 0x400;
+const GICD_ITARGETSR: usize = 0x800;
+const GICD_ICFGR: usize = 0xC00;
+const GICD_NSACR: usize = 0xE00;
+const GICD_SGIR: usize = 0xF00;
+
+const GICD_CTLR_ENABLEGRP0: u32 = 1 << 0;
+const GICD_CTLR_ENABLEGRP1: u32 = 1 << 1;
+
+/* Physical CPU Interface registers */
+const GICC_CTLR: usize = 0x0;
+const GICC_PMR: usize = 0x4;
+const GICC_BPR: usize = 0x8;
+const GICC_IAR: usize = 0xC;
+const GICC_EOIR: usize = 0x10;
+const GICC_RPR: usize = 0x14;
+const GICC_HPPIR: usize = 0x18;
+const GICC_AHPPIR: usize = 0x28;
+const GICC_IIDR: usize = 0xFC;
+const GICC_DIR: usize = 0x1000;
+const GICC_PRIODROP: usize = GICC_EOIR;
+
+const GICC_CTLR_ENABLEGRP0: u32 = 1 << 0;
+const GICC_CTLR_ENABLEGRP1: u32 = 1 << 1;
+const GICC_CTLR_FIQEN: u32 = 1 << 3;
+const GICC_CTLR_ACKCTL: u32 = 1 << 2;
+
+/// maximum number of interrupt handlers
+const MAX_HANDLERS: usize = 256;
+
+static GICC_ADDRESS: OnceCell<VirtAddr> = OnceCell::new();
+static GICD_ADDRESS: OnceCell<VirtAddr> = OnceCell::new();
+
+/// Number of used supported interrupts
+static NR_IRQS: OnceCell<u32> = OnceCell::new();
+static mut INTERRUPT_HANDLERS: [fn(state: &State); MAX_HANDLERS] =
+	[default_interrupt_handler; MAX_HANDLERS];
+
+fn default_interrupt_handler(_state: &State) {
+	warn!("Entering default interrupt handler");
+}
+
+/// Enable all interrupts
 #[inline]
 pub fn enable() {
 	unsafe {
@@ -31,7 +100,7 @@ pub fn enable_and_wait() {
 	}
 }
 
-/// Disable Interrupts
+/// Disable all interrupts
 #[inline]
 pub fn disable() {
 	unsafe {
@@ -43,53 +112,294 @@ pub fn disable() {
 	}
 }
 
-#[no_mangle]
-pub extern "C" fn irq_install_handler(irq_number: u32, handler: usize) {
+pub fn irq_install_handler(irq_number: u32, handler: fn(state: &State)) {
 	info!("Install handler for interrupt {}", irq_number);
-	// TODO
+	unsafe {
+		INTERRUPT_HANDLERS[irq_number as usize] = handler;
+	}
 }
 
 #[no_mangle]
-pub extern "C" fn do_fiq(_: *const u8) {
-	debug!("Receive fast interrupt\n");
+pub extern "C" fn do_fiq(state: &State) {
+	info!("fiq");
+	let iar = gicc_read(GICC_IAR);
+	let vector: usize = iar as usize & 0x3ff;
 
-	loop {
-		crate::arch::processor::halt()
+	info!("Receive fiq {}", vector);
+
+	if vector < MAX_HANDLERS {
+		unsafe {
+			INTERRUPT_HANDLERS[vector](state);
+		}
 	}
+
+	gicc_write(GICC_EOIR, iar.try_into().unwrap());
 }
 
 #[no_mangle]
-pub extern "C" fn do_irq(_: *const u8) {
-	debug!("Receive interrupt\n");
+pub extern "C" fn do_irq(_state: &State) {
+	let iar = gicc_read(GICC_IAR);
+	let vector = iar & 0x3ff;
 
-	loop {
-		crate::arch::processor::halt()
-	}
+	info!("Receive interrupt {}", vector);
+
+	gicc_write(GICC_EOIR, iar);
 }
 
 #[no_mangle]
-pub extern "C" fn do_sync(_: *const u8) {
-	debug!("Receive synchronous exception\n");
+pub extern "C" fn do_sync(state: &State) {
+	info!("{:#012x?}", state);
+	let iar = gicc_read(GICC_IAR);
+	let esr = ESR_EL1.get();
+	let ec = esr >> 26;
+	let iss = esr & 0xFFFFFF;
+	let pc = ELR_EL1.get();
+
+	/* data abort from lower or current level */
+	if (ec == 0b100100) || (ec == 0b100101) {
+		/* check if value in far_el1 is valid */
+		if (iss & (1 << 10)) == 0 {
+			/* read far_el1 register, which holds the faulting virtual address */
+			let far = FAR_EL1.get();
+
+			// add page fault handler
 
-	loop {
-		crate::arch::processor::halt()
+			error!("Unable to handle page fault at {:#x}", far);
+			error!("Exception return address {:#x}", ELR_EL1.get());
+			error!("Thread ID register {:#x}", TPIDR_EL0.get());
+			error!("Table Base Register {:#x}", TTBR0_EL1.get());
+			error!("Exception Syndrome Register {:#x}", esr);
+
+			// send EOI
+			gicc_write(GICC_EOIR, iar);
+			sys_exit(-EFAULT);
+		} else {
+			error!("Unknown exception");
+		}
+	} else if ec == 0x3c {
+		error!("Trap to debugger, PC={:#x}", pc);
+	} else {
+		error!("Unsupported exception class: {:#x}, PC={:#x}", ec, pc);
 	}
 }
 
 #[no_mangle]
-pub extern "C" fn do_bad_mode(_: *const u8, reason: u32) {
+pub extern "C" fn do_bad_mode(_state: &State, reason: u32) -> ! {
 	error!("Receive unhandled exception: {}\n", reason);
 
-	loop {
-		crate::arch::processor::halt()
-	}
+	sys_exit(-EFAULT);
 }
 
 #[no_mangle]
-pub extern "C" fn do_error(_: *const u8) {
+pub extern "C" fn do_error(_state: &State) -> ! {
 	error!("Receive error interrupt\n");
 
-	loop {
-		crate::arch::processor::halt()
+	sys_exit(-EFAULT);
+}
+
+#[inline]
+fn gicd_read(off: usize) -> u32 {
+	let value: u32;
+
+	// we have to use inline assembly to guarantee 32bit memory access
+	unsafe {
+		asm!("ldar {value:w}, [{addr}]",
+			value = out(reg) value,
+			addr = in(reg) (GICD_ADDRESS.get().unwrap().as_usize() + off),
+			options(nostack, readonly),
+		);
+	}
+
+	value
+}
+
+#[inline]
+fn gicd_write(off: usize, value: u32) {
+	// we have to use inline assembly to guarantee 32bit memory access
+	unsafe {
+		asm!("str {value:w}, [{addr}]",
+			value = in(reg) value,
+			addr = in(reg) (GICD_ADDRESS.get().unwrap().as_usize() + off),
+			options(nostack),
+		);
+	}
+}
+
+#[inline]
+fn gicc_read(off: usize) -> u32 {
+	let value: u32;
+
+	// we have to use inline assembly to guarantee 32bit memory access
+	unsafe {
+		asm!("ldar {value:w}, [{addr}]",
+			value = out(reg) value,
+			addr = in(reg) (GICC_ADDRESS.get().unwrap().as_usize() + off),
+			options(nostack, readonly),
+		);
 	}
+
+	value
+}
+
+#[inline]
+fn gicc_write(off: usize, value: u32) {
+	// we have to use inline assembly to guarantee 32bit memory access
+	unsafe {
+		asm!("str {value:w}, [{addr}]",
+			value = in(reg) value,
+			addr = in(reg) (GICC_ADDRESS.get().unwrap().as_usize() + off),
+			options(nostack),
+		);
+	}
+}
+
+/// Global enable forwarding interrupts from distributor to cpu interface
+fn gicd_enable() {
+	gicd_write(GICD_CTLR, GICD_CTLR_ENABLEGRP0 | GICD_CTLR_ENABLEGRP1);
+}
+
+/// Global disable forwarding interrupts from distributor to cpu interface
+fn gicd_disable() {
+	gicd_write(GICD_CTLR, 0);
+}
+
+/// Global enable signalling of interrupt from the cpu interface
+fn gicc_enable() {
+	gicc_write(
+		GICC_CTLR,
+		GICC_CTLR_ENABLEGRP0 | GICC_CTLR_ENABLEGRP1 | GICC_CTLR_FIQEN | GICC_CTLR_ACKCTL,
+	);
+}
+
+/// Global disable signalling of interrupt from the cpu interface
+fn gicc_disable() {
+	gicc_write(GICC_CTLR, 0);
+}
+
+fn gicc_set_priority(priority: u32) {
+	gicc_write(GICC_PMR, priority & 0xFF);
+}
+
+static MASK_LOCK: InterruptTicketMutex<()> = InterruptTicketMutex::new(());
+
+pub fn mask_interrupt(vector: u32) -> Result<(), ()> {
+	if vector < *NR_IRQS.get().unwrap() && vector < MAX_HANDLERS.try_into().unwrap() {
+		let _guard = MASK_LOCK.lock();
+
+		let regoff = GICD_ICENABLER + 4 * (vector as usize / 32);
+		gicd_write(regoff, 1 << (vector % 32));
+
+		Ok(())
+	} else {
+		Err(())
+	}
+}
+
+pub fn unmask_interrupt(vector: u32) -> Result<(), ()> {
+	if vector < *NR_IRQS.get().unwrap() && vector < MAX_HANDLERS.try_into().unwrap() {
+		let _guard = MASK_LOCK.lock();
+
+		let regoff = GICD_ISENABLER + 4 * (vector as usize / 32);
+		gicd_write(regoff, 1 << (vector % 32));
+		Ok(())
+	} else {
+		Err(())
+	}
+}
+
+pub fn set_oneshot_timer(wakeup_time: Option<u64>) {
+	todo!("set_oneshot_timer stub");
+}
+
+pub fn wakeup_core(core_to_wakeup: CoreId) {
+	todo!("wakeup_core stub");
+}
+
+pub fn init() {
+	info!("Intialize generic interrupt controller");
+
+	let dtb = unsafe {
+		Dtb::from_raw(boot_info().hardware_info.device_tree.unwrap().get() as *const u8)
+			.expect(".dtb file has invalid header")
+	};
+
+	let reg = dtb.get_property("/intc", "reg").unwrap();
+	let (slice, residual_slice) = reg.split_at(core::mem::size_of::<u64>());
+	let gicd_start = PhysAddr(u64::from_be_bytes(slice.try_into().unwrap()));
+	let (slice, residual_slice) = residual_slice.split_at(core::mem::size_of::<u64>());
+	let gicd_size = u64::from_be_bytes(slice.try_into().unwrap());
+	let (slice, residual_slice) = residual_slice.split_at(core::mem::size_of::<u64>());
+	let gicc_start = PhysAddr(u64::from_be_bytes(slice.try_into().unwrap()));
+	let (slice, _residual_slice) = residual_slice.split_at(core::mem::size_of::<u64>());
+	let gicc_size = u64::from_be_bytes(slice.try_into().unwrap());
+
+	info!(
+		"Found GIC Distributor interface at {:#X} (size {:#X})",
+		gicd_start, gicd_size
+	);
+	info!(
+		"Found generic interrupt controller at {:#X} (size {:#X})",
+		gicc_start, gicc_size
+	);
+
+	let gicd_address =
+		virtualmem::allocate_aligned(gicd_size.try_into().unwrap(), 0x10000).unwrap();
+	GICD_ADDRESS.set(gicd_address).unwrap();
+	debug!("Mapping GIC Distributor interface to virtual address {gicd_address:p}",);
+
+	let mut flags = PageTableEntryFlags::empty();
+	flags.device().writable().execute_disable();
+	paging::map::<BasePageSize>(
+		gicd_address,
+		gicd_start,
+		(gicd_size / BasePageSize::SIZE).try_into().unwrap(),
+		flags,
+	);
+
+	let gicc_address =
+		virtualmem::allocate_aligned(gicc_size.try_into().unwrap(), 0x10000).unwrap();
+	GICC_ADDRESS.set(gicc_address).unwrap();
+	debug!("Mapping generic interrupt controller to virtual address {gicc_address:p}",);
+	paging::map::<BasePageSize>(
+		gicc_address,
+		gicc_start,
+		(gicc_size / BasePageSize::SIZE).try_into().unwrap(),
+		flags,
+	);
+
+	gicc_disable();
+	gicd_disable();
+
+	let nr_irqs = ((gicd_read(GICD_TYPER) & 0x1f) + 1) * 32;
+	info!("Number of supported interrupts {}", nr_irqs);
+	NR_IRQS.set(nr_irqs).unwrap();
+
+	gicd_write(GICD_ICENABLER, 0xffff0000);
+	gicd_write(GICD_ISENABLER, 0x0000ffff);
+	gicd_write(GICD_ICPENDR, 0xffffffff);
+	gicd_write(GICD_IGROUPR, 0);
+
+	for i in 0..32 / 4 {
+		gicd_write(GICD_IPRIORITYR + i * 4, 0x80808080);
+	}
+
+	for i in 32 / 16..nr_irqs / 16 {
+		gicd_write(GICD_NSACR + i as usize * 4, 0xffffffff);
+	}
+
+	for i in 32 / 32..nr_irqs / 32 {
+		gicd_write(GICD_ICENABLER + i as usize * 4, 0xffffffff);
+		gicd_write(GICD_ICPENDR + i as usize * 4, 0xffffffff);
+		gicd_write(GICD_IGROUPR + i as usize * 4, 0);
+	}
+
+	for i in 32 / 4..nr_irqs / 4 {
+		gicd_write(GICD_ITARGETSR + i as usize * 4, 0);
+		gicd_write(GICD_IPRIORITYR + i as usize * 4, 0x80808080);
+	}
+
+	gicd_enable();
+
+	gicc_set_priority(0xF0);
+	gicc_enable();
 }
-- 
GitLab