2

I'm writing thread-switch code in which the kernel dumps relevant state to memory, but the actual thread switch occurs entirely in user mode.

This works fine, except in the case where the preemption point occurs inside a Thumb If-Then (IT) block. In that case, I cannot work out how to restore ITSTATE (i.e. bits [15:10] and [26:25] of the CPSR), since these bits are user RAZ/WI, and in any case it probably doesn't make sense given the semantics of ITSTATE.

What (if any) is the correct way to restore this state without trapping into the kernel?

I have considered rewinding execution and continuing from the IT instruction, but apart from being quite heavy-weight, I'm not sure if this is always possible.

EDIT: interested in ARMv7-A

xac
  • 23
  • 3

2 Answers2

1

Architecturally*, none of the execution state bits in the CPSR can be read or written in any mode (with the exception of the CPSR.E bit in privileged modes only, and even that is deprecated). The only way to modify them outside of normal execution is via the SPSR in an exception return, which anticipates returning directly to the relevant instruction inside the IT block.

As you've mentioned, restarting the entire IT block is neither practical nor safe, mostly due to memory accesses - whilst in most cases you could probably statically analyse the instructions to reverse any register modifications, any shared memory could mean a repeated load returns a different value, or a repeated store could corrupt some other process that's read and modified the original value in the meantime. As for memory-mapped I/O, unexpectedly repeating any access could potentially be hazardous for any number of reasons.

Another potential idea would be to JIT a new IT block based on ITSTATE from the SPSR and the remaining instructions at the return address, since Thumb-2 means you have LDR PC,... to get back to the original code from anywhere with registers intact. However, even that's still going to break things if the conditional instructions do any PC-relative calculations.

* on anything that has a CPSR and supports Thumb-2, at any rate

Notlikethat
  • 20,095
  • 3
  • 40
  • 77
  • Besides the memory, you would have to undo any register operations as they will have been saved *mid-IT-block*. Even that seems difficult. Especially when the register may have been clobbered by *mid-IT-block* and you have no clue what it was. – artless noise Sep 26 '14 at 17:06
  • I don't think clobbered registers are a problem because you don't need to exactly reconstruct the original register state, but rather any state that, when the IT instructions are applied, produce the current state. – xac Sep 27 '14 at 00:41
  • Perhaps in the case of memory access it would be better to roll forward instead. In either case this really needs an ISA emulator, which is not ideal. – xac Sep 27 '14 at 00:44
0

CPSR is automatically stored on stack on exception entry, and it's restored from stack on exception return. I may not see your exact problem, but generally there's nothing you need to do for this context switching to work - assuming it's done in an exception, you just store remaining registers on the stack, switch stack pointer, unstack remaining registers and just return from the exception... I also don't know what's the exact architecture you are using, but here is a very simple context switch for ARMv7-M (Cortex-M3/M4): https://github.com/DISTORTEC/distortos/blob/master/source/architecture/ARM/ARMv7-M/ARMv7-M-PendSV_Handler.cpp

Freddie Chopin
  • 8,440
  • 2
  • 28
  • 58
  • He was trying to do this from **user space**. I think you are talking about an exception mode. I wonder how `setjmp/longjmp` work on an Cortex-M? I guess the function call can never occur in the middle of a conditional block. I think only co-operative scheduling can work on the Cortex-M. [co-routines in c](http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html) and [proto-threads](http://dunkels.com/adam/pt/index.html). – artless noise Sep 26 '14 at 17:08
  • 1
    setjmp()/longjmp() work almost the same as exceptions, but the stacking/unstacking of the registers done automatically in case of exception is done manually. Here is a code from newlib - https://sourceware.org/git/gitweb.cgi?p=newlib.git;a=blob;f=newlib/libc/machine/arm/setjmp.S;h=a65fbabb9926f64557bea3d3d336bb1843ae6ae2;hb=ebbb290f6e15e71b140a911c0de530453b9f8443 - there are no modifications to CPSR. Cortex-M can have preemptive scheduling - the project I linked is a fully preemptive scheduler for such chip (; – Freddie Chopin Sep 26 '14 at 17:19
  • Yes, my question was kind of a statement. You need to handle the `CPSR` (or whatever the Cortex-M calls it) for pre-emptive scheduler and hence can not use *user mode*. For co-operative scheduling, you can do as `setjmp/longjmp` and restrict context switches to non-IT-blocks and avoid the problem. – artless noise Sep 26 '14 at 17:49