1

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.

artless noise
  • 21,212
  • 6
  • 68
  • 105
Andrew Milich
  • 39
  • 1
  • 5
  • 1
    Can you share what you have done/tried so far? It would help to answer your question. – PhilDulac Jun 01 '16 at 18:19
  • Why would you need to switch to SVC handler to do the context switch ? It does not have more priviledge. – Dric512 Jun 01 '16 at 19:25
  • @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 Jun 01 '16 at 23:17
  • @PhilDulac Let me know if that helps. – Andrew Milich Jun 01 '16 at 23:17
  • 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 Jun 02 '16 at 06:42

1 Answers1

1

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

Community
  • 1
  • 1
artless noise
  • 21,212
  • 6
  • 68
  • 105
  • Thanks, this helps a lot. How can I actually change lr_irq and sp_irq? I believe I need to do these things to actually be able to go back to a new thread. – Andrew Milich Jun 02 '16 at 20:22
  • I see this: /var/folders/qb/njcg85_123s4jqr_n0kkt71w0000gn/T//ccStXA95.s:39: Error: selected processor does not support ARM mode `cpsid aif,#18' – Andrew Milich Jun 03 '16 at 03:02
  • Is there anyway to get around it? @artless-noise – Andrew Milich Jun 03 '16 at 03:02
  • Use the [gcc option](https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html) `-march=armv6` (which is good for other reasons too). If you want to set `lr_irq`, etc then it is the opposite. Just do `mov lr, %1` and modify the output to input arguments. – artless noise Jun 03 '16 at 17:32
  • Ok, if `-march=armv6` doesn't work (you have ARMv6T1, not T2) then you can remove the CPS and clear the mode bits and set the IRQ mode manually with multiple instructions of 'and/or' masking or use the `msr cpsr_c, mode`. – artless noise Jun 03 '16 at 18:01