6. Access Levels and T bit
1. Access Level Switching
As we saw in Section 3, the Cortex-M4 supports two access levels: Privileged and Unprivileged. The access level is determined by the CONTROL register. The CONTROL register is a special register that is only accessible in privileged mode. It has the following bit fields:
For now, we only care about bit 0, the nPRIV bit. This bit determines the access level of the processor. If the bit is set to 0, the processor is in privileged mode. If the bit is set to 1, the processor is in unprivileged mode. We can access this bit using the MRS
(move from special register) and MSR
(move to special register) instructions.
void change_access_level_unpriv(void)
{
//read
__asm volatile ("MRS R0,CONTROL");
//modify
__asm volatile ("ORR R0,R0,#0x01");
//write
__asm volatile ("MSR CONTROL,R0");
}
and in the main function. We can call this function to switch to unprivileged mode.
int main(void)
{
change_access_level_unpriv();
generate_interrupt();
for(;;);
}
Where the generate_interrupt()
function is defined as:
void generate_interrupt()
{
uint32_t *pSTIR = (uint32_t*)0xE000EF00;
uint32_t *pISER0 = (uint32_t*)0xE000E100;
//enable IRQ3 interrupt
*pISER0 |= ( 1 << 3);
//generate an interrupt from software for IRQ3
*pSTIR = (3 & 0x1FF);
}
This function triggers an IRQ3 interrupt manually. It uses the memory-mapped registers pSTIR
and pISER0
for software trigger and interrupt set-enable, respectively.
*pISER0 |= ( 1 << 3);
enables the IRQ3 interrupt.*pSTIR = (3 & 0x1FF);
triggers IRQ3.
However, since we are already in unprivileged mode, the processor will throw a UsageFault exception. This is because the CONTROL register is only accessible in privileged mode. The processor will enter the UsageFault handler, which is defined as:
void HardFault_Handler(void)
{
printf("Hard fault detected\n");
while(1);
}
Build and run the code in debug mode, we can see this fault in the Fault Analyzer window:
2. Importance of T bit of EPSR
Introduction
The Execution Program Status Register (EPSR) is a special register in ARM Cortex-M processors that contains various status bits that reflect the state of the processor. The EPSR is not directly accessible, but its individual fields can be accessed or modified through other means like specific instructions or special function calls.
One important bit in the EPSR is the T-bit (Thumb state bit). The T-bit indicates whether the processor is in ARM or Thumb state.
- If the T-bit is set (
1
), the processor executes in Thumb state. - If it is cleared (
0
), the processor is in ARM state.
In the context of Cortex-M series processors, they only support the Thumb instruction set, so the T-bit should always be set to 1
for proper operation. If somehow this bit is cleared, and an attempt is made to fetch an ARM instruction, it will result in an exception like a fault condition.
The T-bit typically gets set correctly when branching to an address, provided the least significant bit of the target address is set. For example, using a branch instruction to an odd-numbered address like 0x1001
would set the T-bit, allowing the CPU to correctly interpret the following instructions as Thumb instructions.
Relationship to Program Counter (PC)
The PC holds the address of the next instruction to be executed. In Cortex-M series, when the program loads a value into the PC for a branch or jump, the least significant bit (Bit[0]) of that value determines the state of the T-bit.
Here's what it means in practical terms:
-
If the loaded address has its least significant bit set (
1
), it indicates that the target code is Thumb code. The processor will automatically set the T-bit, ensuring that the processor will interpret subsequent instructions as Thumb instructions. -
If, hypothetically, the loaded address has the least significant bit cleared (
0
), the processor would clear the T-bit. However, since Cortex-M processors only support Thumb mode, doing so would lead to undefined behavior or a fault condition. -
The compiler usually takes care of setting the least significant bit (LSB) to
1
when generating addresses for function calls, jumps, or interrupt vectors in Thumb mode. This is particularly true for Cortex-M processors that only support Thumb mode. The setting of the LSB happens at compile time. The compiler generates machine code with the proper settings to ensure the processor will be in the right state to execute the next instruction.For example, consider a function starting at address
0x2000
. In the function table or when performing a branch, this would be recorded as0x2001
to set the T-bit. However, the function's machine code would still start at0x2000
and occupy subsequent addresses (0x2002
,0x2004
, etc.) without any "gaps." -
So we don't really need to worry about the T-bit in most cases. However, in rare cases where we need to manually set the PC, we need to ensure that the LSB is set to
1
to set the T-bit.
Example
Here is a simple example to demonstrate the importance of the T-bit.
typedef void (*fun_ptr)(void);
void do_nothing(void) {};
/* This function executes in THREAD MODE+ PRIV ACCESS LEVEL of the processor */
int main(void)
{
fun_ptr f1 = do_nothing;
/* storing some address in the function pointer variable */
fun_ptr f2 = (void*)0x080001e8;
f1(); // This is okay. The compiler will adjust the LSB to be 1.
f2(); // This will lead to a fault because the LSB is 0, and ARM Cortex-M does not support that.
for(;;);
}
void HardFault_Handler(void)
{
while(1);
}
Here, although the function do_nothing
lives at the address 08000204
according to the disassembly:
08000204 <do_nothing>:
typedef void (*fun_ptr)(void);
void do_nothing(void) {};
8000204: b480 push {r7}
8000206: af00 add r7, sp, #0
8000208: bf00 nop
800020a: 46bd mov sp, r7
800020c: bc80 pop {r7}
800020e: 4770 bx lr
When it is stored in the function pointer f1
, the compiler automatically sets the LSB to 1
to set the T-bit. This can be seen in the register window, where the value of f1
is stored in R3:
However, when we manually set the function pointer f2
to 0x080001e8
, the T-bit is not set. When we call f2()
, the processor will try to execute the instruction at 0x080001e8
as an ARM instruction, which will lead to a fault.