Contents

Understanding Access Levels – ARM Cortex-M

This blog post focuses on explaining the privileged and unprivileged access levels available in ARM Cortex-M core. With an assembly code, it demonstrates the use of these access levels and explains how to switch between them.

Introduction

ARM Cortex-M core provides a number of features to support RTOS based systems. One of such features is separation of code execution into Privileged and Unprivileged Access Levels. In this post, let’s go little deeper into ARM Cortex-M access levels.

What Are Access Levels?

ARM Cortex-M provides two different code execution levels called access levels – Privileged and Unprivileged. The unprivileged access level restricts access to some system registers and protected memory regions. The processor either ignores or generates a fault condition for any attempt to access these restricted resources from unprivileged code. The privileged access level can access all resources and memory regions in the processor.

Operating Modes

ARM Cortex-M also provides two different modes of operation - Thread mode and Handler mode. When the processor is executing an exception handler, it is in Handler mode. In all other cases it is in Thread mode. Handler mode always uses privileged access level. Thus the handler code can manipulate system resources and protected memory regions. Application code uses the Thread mode. It can use either privileged or unprivileged access level. Thread mode with privileged access level is the default when processor starts up. Most applications do not need strong separation between user tasks and system tasks.

/images/priv-unpriv-armcm.png

Do We Need Access Levels?

Access levels are important to sandbox application tasks and system tasks. Classic example for such systems is an RTOS based system. Systems without RTOS can also benefit by restricting access to critical code sections.

Consider an embedded system which stores some credentials in protected part of memory. The system designer may put these credentials in protected part of memory. Thus code running in unprivileged level won’t be able to gain access to the credentials.

As another example, a system may be using SysTick timer to carry out some periodic task. To change the periodicity of the timer at run time, you need to manipulate SysTick control registers. An error in application code may manipulate these control registers.

A typical RTOS uses SysTick timer for context switching. This may affect real time performance of the system or lead to a system crash. For more details about SysTick timer in ARM Cortex-M, refer to this post.

Another real world example is the FreeRTOS-MPU port of FreeRTOS. It uses MPU and privileged access levels to protect certain regions of memory. Tho operation of MPU is closely related to access levels, I will not discuss it here. It deserves another blog post.

Controlling Access Level

Let’s explore the resources and instructions involved in using the access levels. The CONTROL register in ARM Cortex-M core controls privilege level for code execution.

As shown in image below, when bit 0 is at logic 1, it indicates unprivileged level. When this bit is logic 0, it indicates privileged level.

At power on, this register is 0 which means the processor starts in privileged level and thread mode. Setting the nPRIV bit to 1 changes the access level to unprivileged. Once the processor is in unprivileged mode, clearing this bit has no effect in thread mode. Only the handler mode can change the access level from unprivileged to privileged.

ARM Cortex-M Control Register

How to Use Access Levels?

Let’s understand all the stuff about access levels by trying out some code execution. As usual we will use the STM32F4 Discovery board and the demo code is provided on GitHub. Here is a brief explanation of important parts of the code.

Towards top of the file main.s, the statement GBLL USE_SVC declares an assembly variable. GBLL is an assembly directive which declares a Global Logical variable, which means that the variable can be set to either True or False. Next, the SETL {FALSE} statement sets this variable _USE_SVC_ to False. We will see later in this post how this variable is used in the code.

1
2
3
4
5
;File: main.s

    GBLL      USE_SVC      ;Declare an assembly variable
	
    USE_SVC   SETL    {FALSE}

As mentioned earlier, the processor starts in privileged access level. This makes it easier for simple applications as you don’t need to switch to privileged level. But, switching from unprivileged to privileged level is not straightforward. We will shortly explore this.

Switching to Unprivileged Access Level

1
2
3
;Start in unprivileged mode
    MOV	   R0, #01
    MSR    CONTROL, R0

In our code, the first two instructions MOV R0, #01 and MSR CONTROL, R0 set bit 0 of CONTROL register. This switches the processor access level to unprivileged. The instruction MSR is an instruction to write to the special registers. The image below shows access level switching from Privileged to Unprivileged.

Access Level Privileged by default

Access Level Changed to Unprivileged

Switching Back to Privileged Access Level via Control register

It is not possible to switch back to privileged level except from an exception handler.  The processor ignores the code below but it does not lead to any fault condition though.

1
2
3
4
5
;Getting back to privileged mode via write to CONTROL register isn't possible now 
;Writes to CONTROL register are simply ignored
    
    MOV R0, #0x00 
    MSR CONTROL, R0</pre>

Correct way to switch back to privileged access level is via an exception handler. We will see it later in this post.

Accessing Special Registers in Unprivileged Access Level
If code accesses restricted registers from unprivileged level, the processor either ignores it or generates a fault condition
Access to Special Registers Ignored

Let’s try to access some special registers in unprivileged level.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
; Writes to some special registers are ignored in unprivileged mode
	
    MOV    R0, #0x01

    MSR	   BASEPRI, R0
    MSR	   PRIMASK, R0
    MSR	   FAULTMASK, R0
	
; CPS instructions to write to these registers have no effect in unprivileged mode

    CPSIE  i
    CPSID  i
    CPSIE  f
    CPSID  f

; Reads from these registers return zero
    MRS	   R0, BASEPRI
    MRS	   R0, PRIMASK
    MRS	   R0, FAULTMASK</pre>

In unprivileged access level, the processor ignores attempt to write to special registers such as BASEPRI, PRIMSK and FAULTMASK. In our code shown above, we try to write 0x01 to these registers using MSR instruction. But the registers remain at their original value 0x00 as you can see in the image below. Also CPS instruction to enable / disable faults and interrupts do not have any effect on these registers in unprivileged access level. I will avoid details of CPS instruction for brevity.

Access to special registers ignored in unprivileged level

Access to Special Registers Leading to Fault Condition

Let’s try to manipulate the SysTick timer registers.

1
2
3
4
5
6
; Writing to SysTick control register 
; results in Fault in unprivileged mode

    LDR	   R1,  =SYSTICK_RELOADR
    LDR	   R2,	=SYST_RELOAD_500MS
    STR	   R2,  [R1]

In the code section above, the instruction STR R2, [R1] attempts to write to SysTick reload register. This results in a fault condition because of the access from unprivileged level. The fault is actually a bus fault but it leads to hard fault handler as the bus fault is disabled by default.

Hard Fault Handler

The Hard fault handler switches on LED connected to PD13 on STM32F4 to show system fault. It then stays in an infinite loop. Only a system reset can take it out of fault condition. There are other ways to exit the fault handler and resume executing normal code though.

If we try to access SysTick control register, it will lead to a fault as well. In fact as the processor is in fault already it does not reach the code to change SysTick Control register.

1
2
3
4
5
6
7
8
9
; Writing to SysTick reload register 
; results in Fault in unprivileged mode

    MOV	   R7,  #0x00
	
    LDR	   R1,  =SYSTICK_CONTROLR
    LDR    R0,  [R1]
    ORR.W  R0,  #ENABLE_SYSTICK
    STR	   R0,  [R1]</pre>

If you would like to know details about SysTick operation, please refer this post

Switching Back to Privileged Access Level via Exception Handler

Recall that during an exception handler, processor is always in Privileged access level.

One way to access special registers is via the exception handlers. This will keep the application code always in unprivileged level. When needed, the application can access services of the OS to manipulate special registers or protected memory. This is the scheme used with SVC exception mechanism as demonstrated in this post.

The other way is to switch the access level to privileged from an exception handler itself. This involves writing to the CONTROL register from within an exception handler. This will switch the processor to privileged level for the rest of the application code. The code presented in this blog post uses this method. To see this in action, you will need to recompile the code with the USE_SVC variable set to True.

1
2
3
4
5
;File: main.s

    GBLL       USE_SVC      ;Declare an assembly variable
	
    USE_SVC    SETL    {TRUE}

After you rebuild the code, download it to the board and enter debug mode.

Call for Service

When this USE_SVC is True, the code executes SVC instruction with number 0x05 as shown below. If you are not familiar with SVC, this post will help you.

1
2
3
4
; Correct way of switching back to privileged level
    IF USE_SVC = {TRUE}
    SVC	#0x05
    ENDIF</pre>

Executing an SVC instruction triggers the exception handler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
SVC_Handler PROC

; Extract the SVC parameter and store in R0
...
...

    CMP	   R0, #05
    LDREQ  R1, =Service_Call_5
    BEQ	   DoneService
	
    LDR	   R1, =Service_Call_Default
	
DoneService	
...
...
    ENDP

Since the SVC number in our case is 0x05, it goes on to call function Service_Call_5.

1
2
3
4
5
6
7
Service_Call_5  FUNCTION
	
    MOV	   R0, #00
    MSR	   CONTROL, R0

    BX	   LR
    ENDFUNC

The service function switches code execution level to privileged by clearing nPRIV bit of CONTROL register. Even after the processor exits exception handler, the access level stays as Privileged. You can step through the code and see for yourself that special registers get written to and read from. Likewise, SysTick registers manipulation does not lead to any fault condition.

Finally the SysTick timer handler code should start blinking LED on the STM32F4 Discovery board.

Is that all?

Restriction in unprivileged access level is not limited to the registers discussed here. The NVIC and System Control Block also has limited access in unprivileged access level. Besides, when you protect certain parts of memory by using the MPU, only privileged code can access the protected memory region. Moreover, the LDR and STR instructions can execute at unprivileged level if added with a suffix T (For e.g. LDRT, STRT etc.). You can refer to the Cortex-M4 Devices Generic Users Guide for more details.

Access Levels in Action

Here is a demo of the code execution in both unprivileged and privileged access levels.

Conclusion

Privileged and Unprivileged software execution levels is an important feature in ARM Cortex-M. The separation of access levels along with SVC mechanism makes ARM Cortex-M a powerful candidate for RTOS and other complex systems.

I hope you got a good idea of how the access levels work in the ARM Cortex-M processor. I encourage you to explore it further and try your hands on assembly programming as well. Have fun!