summaryrefslogtreecommitdiff
path: root/md/writeup/raspberry5_baremetal_gic_and_timers.md
diff options
context:
space:
mode:
Diffstat (limited to 'md/writeup/raspberry5_baremetal_gic_and_timers.md')
-rw-r--r--md/writeup/raspberry5_baremetal_gic_and_timers.md505
1 files changed, 502 insertions, 3 deletions
diff --git a/md/writeup/raspberry5_baremetal_gic_and_timers.md b/md/writeup/raspberry5_baremetal_gic_and_timers.md
index 4f37be4..f86162f 100644
--- a/md/writeup/raspberry5_baremetal_gic_and_timers.md
+++ b/md/writeup/raspberry5_baremetal_gic_and_timers.md
@@ -268,7 +268,10 @@ vector_table:
ventry dummy_vector
```
-Vector table address can be passed as
+Vector table address need to be configured, each of the execution
+levels can't see higher level registers, so for EL2 and EL3 different
+registers need to be set
+
for EL2
```arm
@@ -284,13 +287,13 @@ isb
```
It took some time to figure out why EL3 part didn't worked, only after
-checking execution level right approach was choosen(it was EL2). Also when I did
+checking execution level right approach was chosen (it was EL2). Also when I did
attempted to run stub I got EL3 rather then EL2.
### IRQ handler
IRQ handler function will be called when generic IRQ is triggered, so it will need
-to handle each of the IRQ's is some way
+to handle each of the IRQ's in some way
```c
void irq_handler() {
@@ -311,6 +314,15 @@ void irq_handler() {
}
```
+### Enabling global interrupts
+
+```arm
+enable_global_interrupts:
+ msr daifclr, #7
+ dmb osh
+ ret
+```
+
## Sumup
There is many parts that's need's to be combined to make IRQ's to work
@@ -324,12 +336,499 @@ There is many parts that's need's to be combined to make IRQ's to work
Some parts needs to be cleaned up
+### AI usage
+
+There was needed some help fro LLM's to figure out why interrupts didn't fired.
+
+First LLM attempt was to blame that IRQ's for BCM timer is wrong, it tried to figure out IRQ's
+used from kernel and blamed those are wrongs. Then it tried to implement generic ARM's
+timer's that wasn't the goal. It's always assumed execution level is EL3, after trying to load
+ARMv8 stub figured out that actually execution level was EL2, as armstub failed and crashed
+kernel that how figured out that its in EL3 rather then EL2.
+
+After this thing's got better but
+issue with IAR register that always showed at 0x3ff no internet search showed how to deal with
+this, one of the things that LLM added is to list all pending IRQ's. As config where enabling all
+IRQ's there is 3 extra IRQ's that popup's 129,141,260, after checking Linux kernel some seems
+related to graphics, default config.txt have HDMI screen setup, so those comes from RP firmware
+and those caused that if kernel loaded second time IAR was 0x3ff. This only can be fixed after restart.
+
+
+Search didn't gave anything how to deal with it but with LLM suggest to reset APR registers,
+cant find much doc's on those. Also suggested the IRQ drain loop. After this before loading kernel
+could get rid of spurious interrupt state. Without LLM, set the all interrupts disabled and only
+enabled ones for timer and things started to go well.
+
## ARMv8 Stub's
Raspberry Pi provide option to boot custom armstub's
+I have tried to modify stub to update to register's from raspi5, but after loading stub, I dont think
+its worked properly.
+
## Source
+Source code doesn't have the UART, linker script and Makefiles as those was explored in previous notes.
+
+__timer-bcm2835.h__
+```c
+#ifndef __TIMER_BCM2835_H
+#define __TIMER_BCM2835_H
+
+#include "io.h"
+
+//typedef unsigned char uint8_t;
+//typedef unsigned short uint16_t;
+//typedef unsigned int uint32_t;
+
+//#define ARM_IO_BASE 0x107C000000
+//#define RP1_IO_BASE 0x1c00000000UL
+#define TM_BCM2835_REG_OFFSET (ARM_IO_BASE+0x7c003000)
+
+
+#define BCM2835_CS 0x00
+#define BCM2835_CLO 0x04
+#define BCM2835_CHI 0x08
+#define BCM2835_C0 0x0c
+#define BCM2835_C1 0x10
+#define BCM2835_C2 0x14
+#define BCM2835_C3 0x18
+
+#define bcm2835_read32(offset) (*(volatile uint32_t *)(TM_BCM2835_REG_OFFSET+offset))
+#define bcm2835_write32(offset,val) (*(volatile uint32_t *)(TM_BCM2835_REG_OFFSET+offset)) = val
+
+#define TIMER_INTERVAL 1000000
+
+#define GIC_SPI_IRQ_BCM2835_C0 96
+#define GIC_SPI_IRQ_BCM2835_C1 97
+#define GIC_SPI_IRQ_BCM2835_C2 98
+#define GIC_SPI_IRQ_BCM2835_C3 99
+
+
+
+void timer_set_cmp1(uint32_t val);
+void timer_get_cmp1();
+void timer_get_cnt();
+void timer_status();
+void timer_clr_stat_c0();
+void timer_clr_stat_c1();
+void timer_clr_stat();
+void timer_start_c0();
+void timer_start_c1();
+void timer_start_c3();
+
+#endif
+
+```
+
+__timer-bcm2835.c__
+```c
+#include "timer-bcm2835.h"
+#include "pl011.h"
+
+void timer_get_cnt() {
+ uint32_t lo = bcm2835_read32(BCM2835_CLO);
+ uint32_t hi = bcm2835_read32(BCM2835_CHI);
+ pl011_putu32(hi);
+ pl011_putu32(lo);
+ pl011_puts("\r\n");
+}
+
+void timer_status() {
+ uint32_t stat = bcm2835_read32(BCM2835_CS);
+ pl011_puts("timer status ");
+ pl011_putu32(stat);
+ pl011_puts("\r\n");
+}
+
+void timer_clr_stat_c1() {
+ bcm2835_write32(BCM2835_CS,0x2);
+}
+void timer_clr_stat_c0() {
+ bcm2835_write32(BCM2835_CS,0x1);
+}
+
+void timer_clr_stat() {
+ bcm2835_write32(BCM2835_CS,0xF);
+}
+
+void timer_start_c0() {
+ uint32_t t_lo = bcm2835_read32(BCM2835_CLO);
+ t_lo += TIMER_INTERVAL;
+ bcm2835_write32(BCM2835_C0, t_lo);
+}
+
+void timer_start_c1() {
+ uint32_t t_lo = bcm2835_read32(BCM2835_CLO);
+ t_lo += TIMER_INTERVAL;
+ bcm2835_write32(BCM2835_C1, t_lo);
+}
+
+void timer_start_c3() {
+ uint32_t t_lo = bcm2835_read32(BCM2835_CLO);
+ t_lo += TIMER_INTERVAL;
+ bcm2835_write32(BCM2835_C3, t_lo);
+}
+
+```
+
+__gic400.h__
+```c
+#ifndef __GIC400_H
+#define __GIC400_H
+
+#include "io.h"
+
+#define GIC400_REG_OFFSET (ARM_IO_BASE+0x0)
+
+#define GIC400_GICD_BASE (GIC400_REG_OFFSET+0x7fff9000)
+#define GIC400_GICC_BASE (GIC400_REG_OFFSET+0x7fffa000)
+
+#define GICD_CTRL (GIC400_GICD_BASE+0x0)
+#define GICD_IDR (GIC400_GICD_BASE+0x8)
+#define GICD_IGROUPR(n) (GIC400_GICD_BASE+0x080+4*(n))
+#define GICD_ISENABLER(n) (GIC400_GICD_BASE+0x100+4*(n))
+#define GICD_ICENABLER(n) (GIC400_GICD_BASE+0x180+4*(n))
+#define GICD_ISPENDR(n) (GIC400_GICD_BASE+0x200+4*(n))
+#define GICD_ICPENDR(n) (GIC400_GICD_BASE+0x280+4*(n))
+#define GICD_ISACTIVER(n) (GIC400_GICD_BASE+0x300+4*(n))
+#define GICD_ICACTIVER(n) (GIC400_GICD_BASE+0x380+4*(n))
+#define GICD_IPRIORITYR(n) (GIC400_GICD_BASE+0x400+4*(n))
+#define GICD_ITARGETSR(n) (GIC400_GICD_BASE+0x800+4*(n))
+#define GICD_ICFGR(n) (GIC400_GICD_BASE+0xC00+4*(n))
+
+#define GICC_CTRL (GIC400_GICC_BASE+0x0)
+#define GICC_PMR (GIC400_GICC_BASE+0x4)
+#define GICC_IAR (GIC400_GICC_BASE+0xC)
+#define GICC_EOIR (GIC400_GICC_BASE+0x10)
+#define GICC_RPR (GIC400_GICC_BASE+0x14) // running priority (read to diagnose)
+#define GICC_APR0 (GIC400_GICC_BASE+0xD0) // active priorities (write 0 to unstick)
+#define GICC_APR1 (GIC400_GICC_BASE+0xD4)
+#define GICC_APR2 (GIC400_GICC_BASE+0xD8)
+#define GICC_APR3 (GIC400_GICC_BASE+0xDC)
+
+
+void gic400_init();
+void gic400_reset();
+uint32_t gic400_version();
+void gic400_enable_irq(unsigned int irqn);
+void gic400_set_pending_irq(unsigned int irqn);
+void gicc_drain(void);
+
+#endif
+```
+
+__gic400.c__
+```c
+#include <gic400.h>
+
+void gicc_drain(void) {
+ uint32_t val;
+ while (((val = ioread32(GICC_IAR)) & 0x3ff) < 1020)
+ iowrite32(GICC_EOIR, val);
+}
+
+void gic400_init() {
+ int i;
+
+ iowrite32(GICD_CTRL, 0);
+ iowrite32(GICC_CTRL, 0);
+
+ for (i = 0; i < 16; i++) {
+ iowrite32(GICD_ICENABLER(i), 0xffffffff); // disable
+ iowrite32(GICD_ICPENDR(i), 0xffffffff); // clear pending
+ iowrite32(GICD_ICACTIVER(i), 0xffffffff); // clear active
+ }
+
+ for (i = 0; i < 16; i++)
+ iowrite32(GICD_IGROUPR(i), 0xffffffff); // all -> Group 1 (NS)
+ for (i = 0; i < 128; i++) {
+ iowrite32(GICD_IPRIORITYR(i), 0xa0a0a0a0); // mid priority
+ iowrite32(GICD_ITARGETSR(i), 0x01010101); // -> CPU0
+ }
+ for (i = 0; i < 32; i++)
+ iowrite32(GICD_ICFGR(i), 0x55); // (SPIs: level-triggered)
+
+ iowrite32(GICD_CTRL, 0x1);
+
+ iowrite32(GICC_PMR, 0xff);
+ iowrite32(GICC_CTRL, 0x3); // EnableGrp0 | EnableGrp1
+
+ iowrite32(GICC_APR0, 0);
+ iowrite32(GICC_APR1, 0);
+ iowrite32(GICC_APR2, 0);
+ iowrite32(GICC_APR3, 0);
+
+ gicc_drain();
+}
+
+uint32_t gic400_version() {
+ return ioread32(GICD_IDR);
+}
+
+void gic400_enable_irq(unsigned int irqn) {
+ iowrite32(GICD_ISENABLER(irqn/32), 1 << (irqn % 32));
+}
+
+void gic400_set_pending_irq(unsigned int irqn) {
+ iowrite32(GICD_ISPENDR(irqn/32), 1 << (irqn % 32));
+}
+```
+
+__boot.S__
+```arm
+// AArch64 mode
+
+// To keep this in the first portion of the binary.
+.section ".text.boot"
+
+// Make _start global.
+.globl _start
+
+// .org 0x80000
+// Entry point for the kernel. Registers:
+// x0 -> 32 bit pointer to DTB in memory (primary core only) / 0 (secondary cores)
+// x1 -> 0
+// x2 -> 0
+// x3 -> 0
+// x4 -> 32 bit kernel entry point, _start location
+_start:
+ // set stack before our code
+ ldr x5, =_start
+ mov sp, x5
+
+ // clear bss
+ ldr x5, =__bss_start
+ ldr w6, =__bss_size
+1: cbz w6, 2f
+ str xzr, [x5], #8
+ sub w6, w6, #1
+ cbnz w6, 1b
+
+
+
+ // jump to C code, should not return
+2:
+ //mrs x0, scr_el3
+ //orr x0, x0, #(1<<1) // SCR_EL3.IRQ — take physical IRQ to EL3
+ //msr scr_el3, x0
+ //isb
+
+ //set vector table (EL2!)
+ ldr x1, =vector_table
+ msr VBAR_EL2, x1
+ isb
+
+ // Route physical IRQ to EL2 (HCR_EL2.IMO = bit 4)
+ mrs x0, hcr_el2
+ orr x0, x0, #(1 << 4) // IMO
+ msr hcr_el2, x0
+ isb
+
+ bl main
+ // for failsafe, halt this core
+halt:
+ wfe
+ b halt
+
+.align 11
+sync_exception_handler:
+ bl kernel_panic
+ eret
+
+.macro ventry label
+ .align 7
+ b \label
+.endm
+
+dummy_vector:
+ b kernel_panic
+
+.align 11
+vector_table:
+ ventry sync_exception_handler
+ ventry handle_irq
+ ventry dummy_vector
+ ventry dummy_vector
+
+ //EL1h
+ ventry dummy_vector
+ ventry handle_irq
+ ventry dummy_vector
+ ventry dummy_vector
+
+ ventry sync_exception_handler
+ ventry handle_irq
+ ventry dummy_vector
+ ventry dummy_vector
+
+ //EL1h
+ ventry dummy_vector
+ ventry handle_irq
+ ventry dummy_vector
+ ventry dummy_vector
+```
+
+__irq.S__
+```arm
+.global enable_global_interrupts
+
+enable_global_interrupts:
+ msr daifclr, #7
+ dmb osh
+ ret
+
+//push all the registers just after the stack pointer
+.macro m__save_curr_context
+ //reserving 16*16 bytes of stack
+ sub sp, sp, #16*16 //30 regs, 8 bytes each
+ stp x0, x1, [sp, #16*0]
+ stp x2, x3, [sp, #16*1]
+ stp x4, x5, [sp, #16*2]
+ stp x6, x7, [sp, #16*3]
+ stp x8, x9, [sp, #16*4]
+ stp x10, x11, [sp, #16*5]
+ stp x12, x13, [sp, #16*6]
+ stp x14, x15, [sp, #16*7]
+ stp x16, x17, [sp, #16*8]
+ stp x18, x19, [sp, #16*9]
+ stp x20, x21, [sp, #16*10]
+ stp x22, x23, [sp, #16*11]
+ stp x24, x25, [sp, #16*12]
+ stp x26, x27, [sp, #16*13]
+ stp x28, x29, [sp, #16*14]
+ str x30, [sp, #16*15]
+.endm
+
+//undo previous operation and start restoring
+.macro m__resume_from_context
+ ldp x0, x1, [sp, #16*0]
+ ldp x2, x3, [sp, #16*1]
+ ldp x4, x5, [sp, #16*2]
+ ldp x6, x7, [sp, #16*3]
+ ldp x8, x9, [sp, #16*4]
+ ldp x10, x11, [sp, #16*5]
+ ldp x12, x13, [sp, #16*6]
+ ldp x14, x15, [sp, #16*7]
+ ldp x16, x17, [sp, #16*8]
+ ldp x18, x19, [sp, #16*9]
+ ldp x20, x21, [sp, #16*10]
+ ldp x22, x23, [sp, #16*11]
+ ldp x24, x25, [sp, #16*12]
+ ldp x26, x27, [sp, #16*13]
+ ldp x28, x29, [sp, #16*14]
+ ldr x30, [sp, #16*15]
+ add sp, sp, #16*16 //reclaim stack
+.endm
+
+.global handle_irq
+handle_irq:
+ m__save_curr_context
+ bl irq_handler
+ m__resume_from_context
+ eret
+```
+
+__kernel.c__
+```c
+#include <pl011.h>
+#include <timer-bcm2835.h>
+#include <gic400.h>
+
+extern void enable_global_interrupts();
+
+void kernel_panic(void) __attribute__ ((noreturn));
+
+void kernel_panic() {
+ pl011_puts("Kernel panic\r\n");
+ while(1) {
+ }
+}
+
+volatile uint32_t spurious_count = 0;
+
+void irq_handler() {
+ uint32_t val = ioread32(GICC_IAR);
+ uint32_t irq = val & 0x3ff;
+
+ if (irq >= 1020) {
+ spurious_count++;
+ return;
+ }
+
+ pl011_puts("Got IRQ ");
+ pl011_putu32(irq);
+ pl011_puts("\r\n");
+ bcm2835_write32(BCM2835_CS, 0xf); // clear all four match flags
+ timer_start_c1();
+
+ iowrite32(GICC_EOIR, val);
+}
+
+static inline uint32_t current_el(void) {
+ uint64_t el;
+ __asm__ volatile("mrs %0, CurrentEL" : "=r"(el));
+ return (uint32_t)((el >> 2) & 0x3);
+}
+
+static void gic_drain(void) {
+ uint32_t val;
+ while (((val = ioread32(GICC_IAR)) & 0x3ff) < 1020)
+ iowrite32(GICC_EOIR, val);
+
+}
+
+static void put_dec(uint32_t v) {
+ char buf[10];
+ int i = 0;
+ if (v == 0) { pl011_putc('0'); return; }
+ while (v) { buf[i++] = '0' + (v % 10); v /= 10; }
+ while (i) pl011_putc(buf[--i]);
+}
+
+static void scan_pending(void) {
+ for (int n = 0; n < 16; n++) {
+ uint32_t p = ioread32(GICD_ISPENDR(n));
+ for (int b = 0; b < 32; b++) {
+ if ((p >> b) & 1) {
+ pl011_puts(" pending IRQ ");
+ put_dec(32 * n + b);
+ pl011_puts("\r\n");
+ }
+ }
+ }
+}
+
+void main()
+{
+ pl011_puts("----------------------------\r\n");
+ pl011_puts("Running at EL");
+ pl011_putu32(current_el());
+ pl011_puts("\r\n");
+
+ gic400_init();
+
+ bcm2835_write32(BCM2835_CS, 0xf);
+ gic400_enable_irq(97);
+ gic400_enable_irq(99);
+ enable_global_interrupts();
+ timer_start_c1();
+ timer_start_c3();
+
+ for (volatile int d = 0; d < 4000000; d++);
+
+ pl011_puts("CS = ");
+ pl011_putu32(bcm2835_read32(BCM2835_CS));
+ pl011_puts("\r\n");
+
+ scan_pending();
+
+ while (1) {
+ __asm__ volatile("wfi");
+ }
+}
+```
+
## Links
https://www.raspberrypi.com/documentation/computers/config_txt.html