These are the instructions for implementing the SwitchingIRQHandler function in src/switch.s Please read through the code in main.c before starting. Especially the initialize_stack function.
The timer exception starts us off in IRQ mode with IRQ stack.
First thing to do is adjust the LR back by 4 so it will be correct when we use it to set the PC.
- Substract 4 from LR
Next we need some registers to use as variables. Lets use R0-R2.
- Push R0-R2 to the stack (IRQ’s)
Now we want to get the task’s CPSR and PC. We get them get them from the IRQ mode’s SPSR and LR.
- Save SPSR to R0
- Save LR to R1
We’ll need to access the data we stored on the IRQ stack. To do that use R2 to store the current value of the IRQ mode’s SP. Then since we have a copy of SP, we can reset sp back to its initial value it will be ready for the next time.
- Save SP to R2
- Add 12 to SP. (The size of three 4 byte values, ie the registers we pushed earlier.)
We’re ready to switch the the Supervisor mode and start saving the task’s context.
- Use msr to switch to the SVC mode with interrupts off.
Now that we’ve switched the SVC mode, the SP points to the task stack. We are ready to start saving the task’s context to it’s own stack. We want to save the context in the same order as the initialize_stack function.
- Save the PC to the stack. The PC was store in R0, so push R0 to the stack.
- Push LR to the stack.
- Push R3-R12 to the stack.
We can’t push R0-R2 because we’ve used them as variables. We need to get the values they had from the IRQ stack were we stored them. To do that we can pop them off the IRQ stack using R2 into R3-R5 which are now free to use since we already stored they values to the task’s stack.
To get the values from the IRQ stack we use R2 since it has a copy the IRQ’s stack pointer.
- Use ldmfd to pop 3 values from the IRQ’s stack into R3-R5 using R2 as the stack pointer.
Store R3-R5 to the task’s stack.
- Use stmfd to push R3-R5 to the task’s stack by using SP.
Push the task’s CPSR to the task’s stack. We stored the CPSR in R0. So push R0 to the stack.
- use stmfd to push R0 to the task’s stack by using SP as the base register.
Now we want to call the scheduler function in main.c. It will reset the stack and currentSP to the other task’s stack poiner. The scheduler has one parameter for the task’s stack pointer. We need to pass in the stack pointer through R0.
- Store SP in R0
Now call the scheduler. It will set currentSP and reset the timer.
- branch with link to scheduler
After the call currentSP has the value of the new task’s stack pointer. To get it we first load the address of currentSP into R0 then deference R0 into SP.
- Load R0 with currentSP
- Load SP with [R0]
Now SP points to the stored context of the new task we’re going to switch to. The first step to restore the context is to set SPSR with the first value on the stack.
- Pop first value into R0.
- Use msr to save R0 to SPSR
Ok, we’re ready to resume the task. All we have to do is pop R0-R12, LR, and PC while resetting the CPSR.
- ldmfd sp!, {r0-r12, lr, pc}^
The context switch is now complete and the new task is running.