Saturday, 30 January 2010

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
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

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

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):

ldr sp,=EX_STACK
push { r0 }
ldr r0,=3
b ex_handle

Then it executes:

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:

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] = {
"Undefined Instruction",
"Software Interrupt",
"Prefetch Abort",
"Data Abort",
"Not used",

void exception_dump(struct exception_detail *e) {
int i;

send("\r\n\r\nABORT: Exception ");

No comments: