Developing the Context Switch handler

What is missing from the abort handler’s context saving is the values in SPSR_abt and R14_svr. We didn’t need to save them in the uDebugger abort handler because we only needed to save the context temporarily. We could rely on the fact that R14_svr and SPSR_abt where not going change while we were in the abort handler. We could leave them where they were and not save them to the stack.

However to switch context from one task to another task it is critical to also save these values. Also using the abort handler’s stack to save values is fine as long as we are in the abort handler. But it is not a good place to store context for the long term. Also it would be hard to manage contexts that came from different tasks on the same stack. A better place to save a task’s context is on the task’s own stack.

The way we will do this is by first saving a few registers on the exception handler’s stack so we can have a few registers to work with. Then switch modes to the Supervisor mode where we will be able access the task’s stack pointer.

Once we have access to the task’s stack we can save all the other registers including the task’s LR and CPSR to it’s stack. By doing that we will have saved the task’s entire context to its own stack. Once it’s there we can leave it there indefinitely.

As long as we keep track of the location where we saved the context we will be able to resume the task any time we want by fully restore the task’s context.

Timer for exceptions

One difference is that for this exercise we’re going to use a timer interrupt to cause the exception. Therefore our handler will be an IRQ mode handler instead of an Abort mode. Each time timer fires it will cause an IRQ exception which will call our handler. The handler will then save the context and switch to another task.

Exception Handler Steps

What follows is the steps we will take to save a task’s full context onto its own stack.

Fix up LR

This is the same as before. The task’s PC that is saved in R14_svc (LR) is +4 from the instruction we will want to return to. To fix this substruct 4 from LR.

Scratch Registers

We’ll need some scratch register for the exception handler code. Three registers will be enough. So save R0, R1, and R2 to the IRQ stack. We’ll retrieve these saved values later after we switch modes.

Save SPSR, LR, and SP

We will need the values of the SPSR, LR, and stack pointer of the IRQ mode after we switch modes. We will save them in R0, R1, and R2 repectively:

R0 = SPSR_irq       // The interrupted task's CPSR
R1 = R14_irq        // The interrutped task's PC value from LR
R2 = R13_irq        // The IRQ mode's stack pointer. It is pointing
                    // to where we saved R0, R1, and R2.

Reset the IRQ stack pointer

At this point we’ve done everything we need to in the IRQ mode and need to switch to the Supervisor mode. However before we switch we need to first reset the IRQ mode stack pointer.

We changed it’s value when we saved R0-R2 to the IRQ stack. We need to set it back to it’s original value so that it will be in the right place for the next time. Since we push 3 registers, we changed it by 12 bytes. To set it back we add 12 btyes to R13_irq. We add because stacks grow down in memory. Adding moves the stack pointer back up in memory.

Switch to Supervisor mode

Since what we want is to save the context on the task’s own stack we will need access to the task’s stack pointer. The task’s stack pointer is saved in the R13 of the mode the task was running in. That mode was the superviser mode. By switching modes we can get to the task’s stack through the stack pointer stored in the R13_svr. We will use the MSR instruction to switch to the supervisor. We will also want to make sure interrupts are off.

MSR cpsr_c, #(MODE_SVR | INT_DISABLED)

Start saving register to the task’s stack

Now that we are in the supervisor mode and we can access the task’s stack through R13_svr we can start saving the context to the stack. We will want to be careful about the order in which we save values to the stack. We want to make sure everything end up in the right place so that it will be easy to restore the context later.

The LDMFD expects to find the register ordered from high register in the highest memory location through the lowest register in the lowest location. To achieve this will start by saving the task’s PC first followed by all the other register values in descending order.

Recall that we saved the PC value to R1 from the IRQ mode’s R14_irq. So now we can push that value onto the stack first:

STMFD SP!, {R1}

Next we will save the task’s LR which is in R14_svr. After that we can then push R3-R12 onto the stack.

We had to leave of R0-R2 since they no longer contain their original values. We saved their original values on the IRQ stack. We can get to them through R2 which is pointing the location where we saved on the IRQ stack.

Copy values from IRQ stack to the Supervisor stack

Earlier we saved the values in R0, R1, and R2 to the IRQ stack. Then we saved the IRQ stack pointer (SP) to R2. Now we can use R2 to copy those values to the supervisor stack. We will do this by using R2 stack pointer.

We can pop the three values off the IRQ stack into three free registers then push the values from those registers to the supevisor stack. We can use R3-R5 for the registers. This works because we already saved R3-R5 to the supervisor stack.

LDMFD R2!, {R3-R5}      // Load R3-R5 with values IRQ stack
STMFD SP!, {R3-R5}      // Write values to the SVR stack

At this point we have now saved the task’s original R0-R15 on to the task’s own stack. There is only one thing mssing.

Save the task’s CPSR

The only thing left is to save the original value of the task’s CPSR to the stack. Fortunately that value was in the IRQ mode’s SPSR when we first entered the IRQ handler.

Recall that we saved the value of SPRS_irq to R0. Now all we have to do is save R0 to the stack. Once we have done that we have saved the entire context that existed when the task was interrupt to the task’s own stack.

Saving the stack pointer for later

The task’s context can remain on its stack indefinitely which means we are free to switch to another task if we want.

The only thing we must keep track of to restore the task later is the position of the the task’s stack pointer after having saved the context to it. The stack pointer tells us where all the data we need to restor the task is stored.

All we have to do is save the stack pointer somewhere where we can find it later when we want to restore the task.

We can save it in something as simple as a global variable or a in a list, or in any other data structure as long as we can retrieve it later.

In the context switcher exercise we will use one global integer per task to store the task’s stack pointer.

One the context is saved to the stack and the stack pointer is saved somewhere we can switch to another taks by using its stack pointer to retore its context.

There is a little detail about where the context might have first come from. We will cover that later when we go over how to initial a task’s stack for context switching. But for now assume that any task we want to switch to already has had its context saved to its stack.

Restore a task

To restore a task we start by retrieving the stack pointer value that pointers to where the context was saved and set SP_svr to that value. SP_svr will then point to the top of the stack where the context was saved.

The first step in restoring the context is to set the SPSR_svr correctly. We do this by popping off the CPSR value off the stack where we saved it as the last step of saving the context. Setting the SPSR will ensure the CPSR is reset correctly when we resume the task. The SPSR will be copied to the CPSR automatically when we use the ^ option with the LDMFD instruction.

LDMFD SP!, {R0}     // Pop saved CPSR to R0
MSR SPSR, R0        // Save CPSR to SPSR

With the SPSR correctly set we can pop all the register and resume the task while at the sametime copying the SPSR to the CPSR with one instruction:

LDMFD SP!, {R0-R12, LR, PC}^

At the point we fully restored the task context and resumed running the task exactly where it was interrupted. We have successfully switched contexts.