(placeholder)

JayStation2 Dev Blog

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