OMAP Exceptions
After basically doing nothing at all for a week apart from eat, sleep, and whine on the internet, I finally got off my arse (figuratively speaking only, sigh) and did a little more hacking.
I got a little exception handling bolted onto the FORTH code so at least now when it crashes I have a hint where it was, as well as don't have to reset the machine. I'd given up on it after wasting hours trying to get anything working a week ago ... all because of a silly missing .align directive. Bummer. And I wasted a few today too.
And I don't even need it anymore anyway!
ABORT: Exception Prefetch Abort pc: 52423020 sr: 200001D0 r0: 8000E5C8 r1: 00000082 r2: 8000E5C0 r3: 00000046 r4: 8000E5D8 r5: 8000E61C r6: 8001E5BC r7: 8000E5C0 r8: 8000C008 r9: 00000000 r10: 8000E5D4 r11: 8000D190 r12: 8000C094 r13: 8000E194 00000000 00020010 000008D4 8000B45C 00000000 00020010 000008E1 80009024 r14: 800092CC r15: 00000000
Well isn't that impressive. Pity it doesn't really help with the bugs in my code. Although at least it does then simply jump back to the FORTH interpreter loop, so at least I don't have to resort to a hardware reset to continue.
I also changed the forth interpreter to run in user mode. Not because I really need to, but because I was too lazy to handle the exception stack pointer properly - since it was running in supervisor mode it uses the same stack pointer as the segfault-type exceptions. Probably it should run in system mode ...
Exception Setup
The following is just info for the few who might be looking for it - it took me a little while to dig it all up solely because I kept looking in the wrong places. It's not terribly interesting unless you're writing a kernel with no prior experience, it's not good code, and it's not even correct in many cases, but maybe there's something useful here for somebody.
The 'exception vectors' on the beagle are stored in the last few bytes of the omap's onboard 64K of RAM, and it is a jump table, not a function pointer array. Although they're actually setup so that there's room to use a function array immediately afterward by doing a pc-relative load, since 4 bytes isn't enough to do much else. The vectors start at 0x4020ffc8, although the Cortex-A8 also has a system control coprocessor register (c12) to move it.
So the init function just copies a 'prototype' image of the code across:
// initialise exception vectors .global ex_init ex_init: ldr r2,=ex_vect ldr r3,=0x4020ffc8 ldr r1,=v_end_vect 1: ldr r0,[r2],#4 str r0,[r3],#4 cmp r2,r1 blo 1b bx lr ex_vect: ldr pc, v_undefined_instruction ldr pc, v_software_interrupt ldr pc, v_prefetch_abort ldr pc, v_data_abort ldr pc, v_not_used ldr pc, v_irq ldr pc, v_fiq v_undefined_instruction: .word ex_undefined_instruction v_software_interrupt: .word ex_software_interrupt v_prefetch_abort: .word ex_prefetch_abort v_data_abort: .word ex_data_abort v_not_used: .word ex_not_used v_irq: .word ex_irq v_fiq: .word ex_fiq v_end_vect:
Where the ex_* labels mark the exception handlers themselves.
NOTE: it's missing the stuff to muck about with the caches, which is pretty important when you're writing code as data ... but for now it works. Perhaps I could avoid the code copy by just copying the address table ... I just don't know if I can assume that, or just use the system control register to move the table.
This is the first thing being done after Das U-Boot has executed the 'kernel image' - the only other thing i'm doing is setting stack pointer somewhere 'known'. I'm not entirely sure of the whole system state at this point (perhaps 'cpu powered up, and in supervisor mode' is all one can assume), so I don't even know if/what caches are even enabled for example.
Exception usage
Since all I want to do is dump some state and reset the interpreter, my exception handlers all do the same thing (apart from recording which one was invoked), but normally each exception type needs the correct return operations at least. It doesn't handle irqs properly either (right now i'd just like to know if they happen for no reason).
For simplicity my exception handlers just set a specific stack pointer on entry to do their work. That's because each processor mode and a few of the exceptions have their own shadow stack and link registers, and the only way to initialise them is to be in the right mode first. Again, too lazy.
After that they save all the register state to memory, and then call some C to dump the state, before jumping back to the FORTH ABORT code, with a reset back to user mode.
So an example of a specific handler (although I use a macro):
ex_prefetch_abort: ldr sp,=EX_STACK push { r0 } ldr r0,=3 b ex_handle
Then it executes:
ex_handle: push { r0 } ldr r0, =ex_regsave+4 // save original registers stm r0, { r1-r14 }^ // save exception type pop { r1 } str r1,[r0, #-16] pop { r1 } // save pc, sr, and r0 str lr, [r0, #-8] str r1, [r0, #-4] mrs r1, spsr str r1, [r0, #-12] // display something about it sub r0,r0,#16 bl exception_dump // Now we want to reset everything and jump back to the forth loop ldr r14,=DOABORT movs pc,r14
The final movs
also restores the CPSR and thus the processor mode.
Note: This is by no means particularly pretty or nice code. It was just the first thing I got working!
And the data-structure for passing to C:
.data ex_type: .word 0 ex_regsr: .word 0 ex_regpc: .word 0 ex_regsave: .word 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
And finally the C code.
struct exception_detail { uint32 type; uint32 sr; uint32 pc; uint32 reg[16]; }; const char const *extypes[8] = { "Reset", "Undefined Instruction", "Software Interrupt", "Prefetch Abort", "Data Abort", "Not used", "IRQ", "FIQ" }; void exception_dump(struct exception_detail *e) { int i; send("\r\n\r\nABORT: Exception "); send(extypes[e->type]); ... }