CS452 - Real-Time Programming - Winter 2018

Lecture 6 - Context Switch Details

Public Service Annoucements

  1. Due date for kernel 1: 27 January, 2018
  2. Comments from a0:

ARM 920T core

Features

  1. Processor status register
  2. Sixteen 32-bit registers
  3. There are seven processor modes, of which four are used in the course.
  4. There are seven exceptions, of which three are used explicitly in our kernels.
    Exception

    Type

    Modes

    Called from

    Mode at

    Completion

    Instruction

    Address

    Reset hardware supervisor 0x00
    Software interrupt any supervisor 0x08
    Ordinary interrupt any IRQ 0x18

    1. We are concerned right now with Reset and Software Interrupt.
    2. The first instruction executed by the CPU after reset is the one at location 0x00000000. Usually it is
                ldr  pc, [pc, #0x18]
              
      0xe590f018 is the hex encoding which you will normally find in addresses 0x00 to 0x1c. You see #0x18 where you might expect to see #0x20 because the evaluation of the offset from pc occurs in the third stage of the pipeline so that the pc has been incremented twice.
    3. RedBoot puts entry points of RedBoot into all the addresses 0x20 to 0x3c.
    4. Loading the pc from memory makes it possible to jump anywhere in the 32 bit address space.

Context Switch

From the point-of-view of the task

What gcc does

We start by modelling what happens in a context switch as a function call to a kernel function. After all, that's what it looks like to the user code.

    ...
    tid = Create( priorityQ, code );
    ...
  

Function call

  1. Save r0-r3 into local variables.
  2. Put arguments into r0-r1.
  3. bl #entry; lr gets pc, pc gets #entry
  4. mov ip, sp; allows multiple store
  5. push {fp, ip, lr} onto stack; possibly other registers
  6. function code gets arguments from the registers
  7. Somehow, execute a service request to the kernel, which provides request parameters, and gets back a return value.
  8. user code, inside Create function puts the return value into r0.
  9. pop {fp, sp, pc} from stack; and any others. This returns from the function.
  10. put r0 (return value) into local variable
Steps 1, 2, 10 are in user code; step 3 is the calling of the Create function; steps 4, 5, 9 are in the function preamble and postamble; steps 6,8 set up arguments for the kernel and retrieve the return value; step 7 is done by the kernel.

It is pretty clear that the whole sequence above is a NOP, except for changing the arguments into the return value, but only if step 7 is a similar NOP. Step 7, however, can't be a function call because we have to change from user to supervisor mode. We need a more exotic NOP.

Using a function call to get into the kernel is blatently incorrect.

Software Interrupt SWI

The software interrupt instruction ( SWI{cond} <immed_24> ). What happens when it is executed?

  1. lr_svc gets address of the following instruction. This is where the kernel will return to.
  2. SPSR_svc gets CPSR. This saves the mode, condition codes, etc.
  3. CPSR[0:4] gets 0b10011. Supervisor mode.
  4. CPSR[5] gets 0. ARM (not Thumb) state.
  5. CPSR[6] gets 1. Fast interrupts disabled.
  6. CPSR[7] gets 1. Normal interrupts disabled.
  7. PC gets 0x08
The problem is that immediately after SWI you have Before the kernel can do any work it must get its own state into the CPU. Before it overwrites the user state it must be saved. Why and where?

The software interrupt has an inverse instruction which, executed immediately following it, undoes its effect. Its most common form is

    movs	pc, lr
  
which has the following effect.
  1. The pc gets the contents of the lr_<mode>.
  2. The cpsr gets the contents of the spsr_<mode>.
All forms of instructions like this are priveleged. (Privilege is needed for changing the low bits of the CPSR. Why?) The inverse depends on the link register and the stack pointer in the calling program being undisturbed.

Software Interrupt, SWI.

To make the software interrupt work you must set low memory correctly. You know what to put in 0x08:

The SWI instruction is encoded in eight ( = four + four ) bits. The CPU ignores the 24-bit immediate value, which can be used by the programmer as another argument, usually identifying the system call.

                          ; In calling code
                          ; Store r0-r3 ; Put arguments into r0-r3
                          ; 0x28 holds the kernel entry point
   swi  n                 ; n identifies the system call
                          ; retrieve return value from r0 ; r1-r3 have useless junk

                          ; In kernel
kernel entry:
			   ; Change to system mode
			   ; Save user state on user stack
			   ; Return to supervisor mode
   ldr    r4, [lr, #-4]    ; gets the request type
                           ; at this point you can get the arguments
                           ; Where are they?
			   ; Retrieve kernel state from kernel stack
			   ; Do kernel work

The sequence

   swi   n
   .
   .
   .
kernel entry:
   movs   pc, lr
is a NOP. The 's' in movs puts the SPSR into CPSR.

Leaving the kernel.

When the previous request has been handled completely, it's time to leave the kernel.

  1. You are in svc mode, executing kernel instructions
  2. Schedule to discover which task runs next.
  3. Enter activate( active )
  4. active is a pointer to a task descriptor (TD*). From it, or the from the user stack get sp_usr
  5. set spsr_svc = cpsr_usr
  6. Store kernel state on kernel stack
  7. get the address of the next instruction of active to be run, its pc
  8. set lr_svc = pc
  9. At this point movs pc, lr would start executing the code of active at the correct instruction, but the register values in the CPU are the kernel's.
  10. Switch to system mode
  11. Load registers from user stack
  12. Return to supervisor mode
  13. Set return value by overwriting r0
  14. Let it go
    movs   pc, lr

Somewhere after movs is the kernel entry.

After the kernel entry is the inverse of this sequence.

At this point you should be able to insert the following sequence

    // Store r0-r4 in memory
    // Insert arguments in r0-r4
    SWI
    // Store r0 in memory
    // Re-load r0-r3
  
anywhere in user code and execute is with nothing happening!


Return to: