1

I am going through different mode of ARM processor. I want to check the processor state(ex: register values) while it is in different mode.

So can someone help me to find out sample code to put processor in different mode ?

For example I found a code for undefined mode: asm volatile (".short 0xffff\n");

artless noise
  • 21,212
  • 6
  • 68
  • 105
Rahul
  • 1,607
  • 3
  • 23
  • 41
  • Which mode you are in to start with? which arm core you are talking about? Is this on an OS? – auselen Mar 10 '14 at 09:30
  • 1
    I have to try all the 7 modes, undefined I have already covered. I am working on Android, ARMv7 family. – Rahul Mar 10 '14 at 09:52
  • Use SVC to switch to supervisor mode then you can switch to other modes. // So you are doing all this without an OS? – auselen Mar 10 '14 at 09:56
  • @auselen can you tell me what exactly you mean by without an OS? And if you can suggest some sample code for switching mode that would be great. – Rahul Mar 10 '14 at 10:35
  • Like if you are doing this under Linux? Just search for svc instruction and read cortex-a programming manual (free book from arm) – auselen Mar 10 '14 at 10:42
  • I already refering the pdf you are mentioning, it tells how can i do it in assembly language, not in C language. Also that pdf talks more about handling the SVC mode, not generating it. – Rahul Mar 10 '14 at 11:03
  • `svc 0` would put you to SVC mode. – auselen Mar 10 '14 at 11:07

2 Answers2

7

If you wish to test the modes from user space, this is a difficult question. There maybe no way to go to FIQ mode, if there is no FIQ peripheral in the system. Your system may not be using Monitor mode at all, etc. To get to abort mode, you can use an invalid pointer, or use mmap. However, to answer all mode switches from user space would be book like (or impossible) without assistance from the kernel. Creating a test module with a /proc or /sys file and using the techniques below to implement the kernel code would be the most straight forward method.

You should be aware that not all mode switch transitions are allowed. For instance, you may never switch from user mode to any other mode except through the exception mechanisms. Another issue is that each ARM mode has banked registers. One of these is the very important sp (or stack pointer) and lr (or link register) that is fundamental to 'C' code.

It is generally more safe to use a macro of in-line assembler to bound your test snippets, than to use function calls. The test code must not call external routines. This can be difficult as using floating point, etc may result in the compiler inserting hidden sub-routine calls. You should inspect the generated assembler and provide comments for others.

 /* Get the current mode for restoration. */
 static inline unsigned int get_cpsr(void)
 {
     unsigned int rval;
     asm (" mrs %0, cpsr\n" : "=r" (rval));
     return rval;
 }

You can put this in a header file. The compiler will inline the code so, you will just get the msr instruction placed within a routine.

To change the mode use a define like,

 /* Change the mode */
 #define change_mode(mode) asm("cps %0" : : "I"(mode))

Tangr's has the correct mode defines,

#define MODE_USR        0x10   /* Never use this one, as there is no way back! */
#define MODE_FIQ        0x11   /* banked r8-r14 */
#define MODE_IRQ        0x12
#define MODE_SVC        0x13
#define MODE_MON        0x16
#define MODE_ABT        0x17
#define MODE_UND        0x1B
#define MODE_SYS        0x1F   /* Same as user... */

You also need to restore the previous mode,

#define restore_mode(mode) \
     mode &= 0x1f; \
     asm(" msr cpsr, %0\n" : : "r"(mode) : "cc")

Put this together as follows,

  void test_abort(void)
  {
     unsigned int old_mode = get_cpsr() & 0x1f;
     change_mode(MODE_ABT);
     /* Your test code here... must not call functions. */
     restore_mode(old_mode);
  }

This answers your question directly. However, due to all of the difficulties, it is often easier to write assembler to achieve a test. I believe you were trying to leverage the existing Linux code to test all the modes. This is not an ARM-Linux design goal and without modifying the source, it will be very difficult to achieve and highly system specific if it is.

Community
  • 1
  • 1
artless noise
  • 21,212
  • 6
  • 68
  • 105
  • To the OP: if you're trying to go to usermode to read its registers, don't. If memory serves me right, there are special forms of load and store instructions that work directly with userspace registers from a more privileged mode. – tangrs Mar 10 '14 at 22:55
  • @artlessnoise first of all thanks for the reply.. I tried your approach, but whatever is the mode I am getting the same thing in my dump '<2>[ 69.118189] Bad mode in interrupt handler detected <0>[ 69.122969] Internal error: Oops - bad mode: 0 [#1] PREEMPT' – Rahul Mar 11 '14 at 06:27
  • @Rahul ...you're not running this code in the Linux kernel are you? The kernel is very intricately tied together. There is usually no need for low-level operations like changing CPU modes when working with kernel code. Edit: you are. Why? – tangrs Mar 11 '14 at 11:44
  • @tangrs as i mentioned earlier, I dont want use assembly, I have kernel code, i am keeping this code snippet in a sysfs. I just want to check what are the different register value during a processor mode. i know it may not be useful at all, but I am doing this just for learning purpose. – Rahul Mar 11 '14 at 11:48
  • @Rahul It's a lot more difficult than you think. Working with kernel code isn't the same as writing bare metal code. You have to worry about how the kernel works with your architecture as well as the architecture details. Normally, you shouldn't change the configuration of the architecture without the kernel knowing. Here, you're changing the CPU mode without Linux expecting it. The kernel is right to panic here since certain code in the kernel is written under the assumption that it only executes in certain CPU states. – tangrs Mar 11 '14 at 12:49
  • See [fiqasm.S](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/arch/arm/kernel/fiqasm.S) for a working example of inspecting registers in assembler. You may re-use the same code and add `sp` and `lr` for inspection/read only. The assembler is not that difficult. It does all the steps I outlined above for you already. Your issue *Bad mode in interrupt handler* means you need to disable interrupts when entering the mode. Use `cpsid aif, %0` instead of `cps %0` for **change_mode()**. – artless noise Mar 11 '14 at 14:39
2

You'll need inline assembly for this since C has no built in way to achieve what you want. This isn't exactly the most efficient way to do this (my GCC still generates a useless mov r3, #0 instruction).

You're probably better off implementing the function in bare assembly.

#define MODE_USR        0x10
#define MODE_FIQ        0x11
#define MODE_IRQ        0x12
#define MODE_SVC        0x13
#define MODE_ABT        0x17
#define MODE_UND        0x1B
#define MODE_SYS        0x1F

#define MODE_MASK       0x1F

void switch_mode(int mode) {
        register unsigned long tmp = 0;

        mode &= ~MODE_MASK;

        asm volatile(
                "mrs %[tmp], cpsr_all \n"
                "bic %[tmp], %[tmp], %[mask] \n"
                "orr %[tmp], %[tmp], %[mode] \n"
                "msr cpsr_all, %[tmp] \n"
                : : [mode] "r" (mode), [mask] "I" (MODE_MASK), [tmp] "r" (tmp)
        );
}

The function just sets the mode bits in the program status register. You can find the constants in the ARM reference manual for your exact architecture.

Here is the code generated by my compiler:

00000000 <switch_mode>:
   0:   e3c0001f    bic r0, r0, #31   ; sane-ify the input
   4:   e3a03000    mov r3, #0        ; useless instruction generated by gcc
   8:   e10f3000    mrs r3, CPSR      ; saves the current psr
   c:   e3c3301f    bic r3, r3, #31   ; clear mode bits
  10:   e1833000    orr r3, r3, r0    ; set mode bits to the inputted bits
  14:   e129f003    msr CPSR_fc, r3   ; set current psr to the modified one
  18:   e12fff1e    bx  lr
tangrs
  • 9,709
  • 1
  • 38
  • 53
  • 1
    `tmp = 0` causes the `mov r3, #0`; gcc does not interpret your assembler to know that the `"r" (tmp)` input is not needed. Make it an early clobber output and do not initialize it for this case. As well as this answer, [Trustzone monitor mode with ifar...](http://stackoverflow.com/questions/22080918/trustzone-monitor-mode-and-ifar-ifsr-dfar-dfsr) has a code snippet to change modes (as well as an early clobber example). The `cpsXX` instruction can be used for the ArmV7 cpu. Restore before return is safer; he should be aware that **stack**, etc change. For example, `bx lr` won't function. – artless noise Mar 10 '14 at 15:16
  • Some good points. I was aiming for the simplest and most concise answer. The OP did mention he was using an armv7 system. – tangrs Mar 10 '14 at 22:53