1
votes

I'm trying to write a context switch in a timer interrupt handler. Currently, the context switch is able to switch between contexts on command (cooperative). In the interrupt handler, I was trying to:

  1. Save the current program counter as the place the old thread needs to keep executing
  2. Switch into SVC mode to actually perform the context switch
  3. Switch back into IRQ mode and change the link register to be the saved PC from the new thread
  4. Return from the IRQ handler to the IRQ link register

I believe I can do the first two properly, but I was wondering: how can I switch back into interrupt mode, or at least modify the SVC R13 and R15 from the interrupt handler?

I'm using an ARM v6 processor; thanks so much for the help!

Edit: here's basically what my switch is:

void interrupt_yield() {
    unsigned int old_mode; 
    __asm__("mrs %0, cpsr" : "=r" (old_mode));
    __asm__("msr cpsr_c, %0" : : "r" (MODE_SVC));

    PUSH_ALL; // Macro for push {r0-r12, lr} 
    __asm__("mov %0, sp" : "=r"(sp));
    manager->threads[manager->current_thread].sp = sp;

    unsigned nt = (manager->current_thread + 1) % manager->thread_counter;
    if (CURRENT_THREAD.status == ACTIVE) {
        CURRENT_THREAD.status = INACTIVE;
    }
    manager->current_thread = nt;

    CURRENT_THREAD.status = ACTIVE;
    SET_SP(CURRENT_THREAD.sp);
    POP_ALL;

    __asm__("msr cpsr, %0" : : "r" (old_mode));
}


void timer_vector() { // This is called by assembly in interrupt mode
    armtimer_clear_interrupt(); // clear timer interrupt
    interrupt_yield(); // Calls above function 
}

The goal is to change the IRQ link register to return to the new function. I can't seem to switch back into interrupt mode, however, to do this.

1 more edit: I never actually switch the IRQ link register; I realize this but am not even switching back into IRQ mode so this is a later problem to fix.

1
Can you share what you have done/tried so far? It would help to answer your question.PhilDulac
Why would you need to switch to SVC handler to do the context switch ? It does not have more priviledge.Dric512
@Dric512 I want to do it in SVC mode in order to change the SVC mode stack pointer, not the IRQ one. I can't just switch into SVC mode and branch to the link register, because, when an interrupt happens, I want to save r15 from the running thread and switch to the stored program counter.Andrew Milich
@PhilDulac Let me know if that helps.Andrew Milich
I am almost entirely sure that lr_irq is the task PC... when the interrupt completes, the LR contains the place to go back to to keep executing what was running before the interrupt (ignoring tasks at all). I'm wondering how to set the service SP and PC from interrupt mode - for example, does pop {sp}^ pop a value in the svc r13?Andrew Milich

1 Answers

1
votes

For the ARMv6 you need to change modes to get the banked registers. Your sample code already has many of the necessary details.

    #define MODE_IRQ 0x12
    #define MODE_SVC 0x13
    unsigned int mode;   /* original mode */

    /* target data... */
    unsigned int lr_irq;
    unsigned int sp_irq;
    unsigned int spsr;

    asm (" mrs %0, cpsr\n"    /* Save mode. */
         " msr cpsr_c,%4 \n"  /* to irq mode */
         " mov %1, lr\n"      /* Get lr_irq */
         " mov %2, sp\n"      /* Get sp_irq */
         " mrs %3, spsr\n"    /* Get spsr_irq */
         " msr cpsr, %0\n"    /* back to old mode */
         : "=&r" (mode), "=r"(lr_irq),
           "=r"(sp_irq), "=r"(spsr)
         : "I" (MODE_IRQ));

gcc will allocate the lr_irq etc to general registers (non-banked) and you can transfer the data across modes. The ARMv7 with virtualization extensions has an instruction to avoid this switch.

You should be aware that the timer interrupt could occur in many contexts. It is probably prudent to at least check the spsr from the IRQ mode and have some debug (assert like) that verifies it is user mode. If this never triggers and you think an IRQ can only happen in user mode then the 'debug' can be removed.

Another method is to do this in the assembler of the IRQ handler and pass them to the interrupt_yield() routine in r0-r2 for instance. The ARM EABI puts parameters in r0-r2 so interrupt yield needs parameters. Once you have this data there should be no need to return to the IRQ mode. I highly recommend this method for production code. The above is good for prototyping.


Related: Explicitly accessing banked registers on ARM