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.

5
  • 1
    Can you share what you have done/tried so far? It would help to answer your question. Commented Jun 1, 2016 at 18:19
  • Why would you need to switch to SVC handler to do the context switch ? It does not have more priviledge. Commented Jun 1, 2016 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. Commented Jun 1, 2016 at 23:17
  • @PhilDulac Let me know if that helps. Commented Jun 1, 2016 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? Commented Jun 2, 2016 at 6:42

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

5 Comments

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.
I see this: /var/folders/qb/njcg85_123s4jqr_n0kkt71w0000gn/T//ccStXA95.s:39: Error: selected processor does not support ARM mode `cpsid aif,#18'
Is there anyway to get around it? @artless-noise
Use the gcc option -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.
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.