Chapter 6: Core, Interrupted (Part 1)
If you’re not sure how interrupts work, Kanye West is a pretty great example
.section .text.ivt
// vector table
ldr pc, reset_handler
ldr pc, undefined_handler
ldr pc, svc_handler
ldr pc, prefetch_handler
ldr pc, data_handler
ldr pc, unused_handler
ldr pc, irq_handler
ldr pc, fiq_handler
// literal pool. It is literally a pool.
reset_handler: .word js2osIvtDefaultHandler
undefined_handler: .word js2osIvtUndefinedHandler
svc_handler: .word js2osIvtSvcHandler
prefetch_handler: .word js2osIvtDefaultHandler
data_handler: .word js2osIvtDataHandler
unused_handler: .word js2osIvtDefaultHandler
irq_handler: .word js2osIvtIrqHandler
fiq_handler: .word js2osIvtDefaultHandler
js2osIvtDataHandler:
// never coming back, so screw it
mov32 sp, IRQ_STACK_ADDR
sub r14, r14, #4
stmfd sp!, {r0-r12, r14}
// read data fault status register into r4 and fault address register into r5
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360f/BHCEDIAA.html
// interesting bits: [0:3] is fault type, [10] part of status field, [11] is R/W
mrc p15, 0, r4, c5, c0, 0
mrc p15, 0, r5, c6, c0, 0
mov32 r0, fault_specific_reason_msg
and r1, r4, #15
add r0, r0, r1, lsl #5
bl js2osDebugPrintStringAndNum
mov32 r0, fault_addr_msg
mov r1, r5
bl js2osDebugPrintStringAndNum
mov32 r0, fault_rw_message
mov r5, #1
and r1, r5, r4, LSR #11
bl js2osDebugPrintStringAndNum
// todo: dump the registers and show the faulty instruction
// never come back
//ldmfd sp!, {r0-r12, pc}^
data_handler_infinite_loop:
b data_handler_infinite_loop
js2osIvtIrqHandler:
// -4 because pipelining
sub r14, r14, #4
// note: we may not need to save all these. Depends on what we do
stmfd sp!, {r0-r12, r14}
mov32 r1, TIMER_AND_INTERRUPT_BASE
ldr r0, [r1, #TIMER_MASKED_IRQ]
cmp r0, #1
bleq js2osArmTimerHandler
// and come back
ldmfd sp!, {r0-r12, pc}^
js2osArmTimerHandler:
push {lr}
// do something useful... or just turn on the light
ldr r0, =irq_toggle
ldr r1, [r0, #0]
eor r1, r1, #1
str r1, [r0, #0]
cmp r1, #0
bleq js2osTurnOnActLED
blne js2osTurnOffActLED
// clear the timer IRQ
mov32 r1, TIMER_AND_INTERRUPT_BASE
mov r0, #0
str r0, [r1, #TIMER_IRQ_CLEAR]
pop {pc}
irq_toggle: .word 1
.globl js2osEnableIrq
js2osEnableIrq:
mrs r0, cpsr
bic r0, r0, #0x0080
msr cpsr_c, r0
mov pc, lr
.globl js2osDisableIrq
js2osDisableIrq:
mrs r0, cpsr
orr r0, r0, #0x0080
msr cpsr_c, r0
mov pc, lr
.globl js2osEnableFiq
js2osEnableFiq:
mrs r0, cpsr
bic r0, r0, #0x0040
msr cpsr_c, r0
mov pc, lr
.globl js2osDisableFiq
js2osDisableFiq:
mrs r0, cpsr
orr r0, r0, #0x0040
msr cpsr_c, r0
mov pc, lr
// stuff for enabling interrupts.
// SoC manual says ARM interrupt register base address is 0x7E00B000
// assuming 0x7Exxxxxx (GPU view) --> 0x20xxxxxx (RPI1) --> 0x3F000000 (RPI2)
// then thats 0x3F00B000 for us (RPI2)
.globl js2osEnableArmTimerIrq
js2osEnableArmTimerIrq:
mov r0, #1
ldr r1, =TIMER_AND_INTERRUPT_BASE
str r0, [r1, #ENABLE_BASIC_IRQS]
mov pc, lr
Disclaimer: I do not work for Sony. Despite the disturbing percentage of my shirts, jackets, and bookbags that are PlayStation dev-related, I have never worked for Sony. I do, however, have many friends that work at Sony, some of which I hope will call off the corporate lawyers. JayStation is in no way associated with Sony or PlayStation, and any stupid things I say represent only my own ineptitude and silliness.
To save time, I’m going to assume you have a basic idea of what interrupts and exceptions are. If not, feel free to go google it. I’ll still be here when you get back, promise. ... Ready? OK, lets go.
Usual disclaimer: I do not work for Sony. Despite the disturbing percentage of my shirts, jackets, and bookbags that are PlayStation dev-related, I have never worked for Sony. I do, however, have many friends that work at Sony, some of which I hope will call off the corporate lawyers. JayStation is in no way associated with Sony or PlayStation, and any stupid things I say represent only my own ineptitude and silliness.
The basic premise is this. You place a table of eight branch instructions at memory location zero. It can be more or less than eight depending on the system. Each branch is a jump to a specific handler function. When certain exceptions occur, such as a memory exception or undefined instruction, the PC is set to some offset in your table, the branch at that location is executed, and we execute the corresponding handler, and (maybe) go back to what we were doing before the exception. Thats a lot of indirection so lets make it more concrete.
Things get a little more complicated when hypervisors are involved, so for now we will just ignore that. The linker script specifies the .text.ivt section address is zero, so the first thing in memory will be a jump to the reset handler. I won’t go through al of them, but here are some of the more relevant ones:
undefined: This is a handler for undefined instructions, something you’d normally think is a bad thing, but can be quite useful when getting the CPU to store coprocessor registers for you on a context switch. For example, executing a NEON instruction with NEON support disabled.
SVC: supervisor call. If your OS is running an application in user mode and the application wants to do something that requires supervisor access (syscall?), you can’t just enter supervisor mode at will. You have to generate a supervisor interrupt, which puts you into supervisor mode, and then call your function from the handler. In previous versions of ARM this was SWI (software interrupt).
data abort: depending on the configuration of the system, anything from an unaligned access to violating a page’s memory protection can trigger this abort. While JayStation2 OS doesn’t do any paging, this abort is also a good way to swap in memory.
IRQ: interrupt request sent from various bits of the system. This can be from peripherals, devices, timers, etc. At the time of writing this, I am primarily using it for the ARM timer interrupt.
Now lets get into a bit more detail on what happens when an interrupt occurs. On exception the processor should automatically save the Current Process Status Register (CPSR) to the Saved Process Status Register (SPSR), sets LR to where we came from when the exception occurred minus some offset, set the CPSR to the exception mode, and set the PC to jump to the appropriate place in the interrupt vector table. Just to make that more concrete, imagine a process running in user mode triggers a supervisor call interrupt via syscall. Initially the CPSR will indicate user mode. We save that to the SPSR so we can restore it later. The processor then sets the CPSR to supervisor mode so your handler has the right mode to do what it needs to do.
Now lets take a look at some sample handlers.
Enter Text
That is my data abort handler. It prints out the fault reason, address, whether it was a read or write, decodes the instruction that caused the fault, and prints out the registers. You will also notice it does nothing to try and recover from the error, instead opting to hang forever in an infinite loop until the watchdog timer expires and the system is reset. Here is my IRQ handler.
As simple as can be. We check to see if one of the sources of the IRQ interrupt was the timer, and if it was, call the timer handler. On the way out we have to remember to clear the timer interrupt bit, or we’ll just keep getting into the interrupt handler over and over. You’ll also notice that returning from a handler doesn’t always mean jumping back to the value stored in LR. Often its more useful to use an offset from the LR
Exception address what it be .
data abort LR - 8 instruction that caused the abort
FIQ LR - 4 return address from the handler
IRQ LR - 4 return address from the hander
prefetch LR - 4 instruction that caused the prefetch abort
SVC LR next instruction after the SVC instruction
undefined instruction LR next instruction after the undefined instruction
Finally, you enable IRQ and specifically the timer IRQ as follows
Another thing thats pretty useful to know here is about register banking. Some modes have their own copies of registers, so its important to know what you’re accessing. I copied (stole?) the following image from ARM’s site to avoid having to type lots of words
As you can see, r0 through r7 are common to all modes. FIQ has its own copy of r8 through r12, and everyone has their own SP and LR. This becomes useful later when we talk about context switches and threads.
There is one more topic I wanted to discuss, but since this post is getting too long I’ll save it for part 2. It’s to do with interrupt priorities and reducing latency. Its stuff I’m interested in but aren’t yet doing in the OS, so I don’t feel comfortable writing about it quite yet. Stay tuned, next post is about multicore