Skip to content

ARM Arch

thanos edited this page Mar 25, 2021 · 5 revisions

ARM Arch

For the boards we are mainly using, there is reference to 3 main architectures:

  • ARMv6
  • ARMv7
  • ARMv8

ARMv6 vs ARMv7

The ARMv6 and ARMv7 are very similar and every reference to one usually applies to the other, except where specifically mentioned.

Main ARMv6 and ARMv7 differences:

  • VFP (Vector Floating Point)
    • ARMv6: VFPv2
    • ARMv7: VFPv3
  • Thumb technology:
    • ARMv6: Thumb (First Version)
    • ARMv7: Thumb-2
  • NEON extension:
    • ARMv6: Does not include NEON
    • ARMv7: Includes NEON

ARMv7 vs ARMv8

The ARMv8 arch is very different from the other two (aarch64, Exception Levels, ...), so we have different documentation and implementations.

Main ARMv8 and ARMv7 differences:

  • Architecture:
    • ARMv7: aarch32
    • ARMv8: aarch32 and aarch64 (Backwards compatible with ARMv7)
  • Exception/Privilege Levels:
    • ARMv7: Privilege Levels (PL0, PL1, PL2)
    • ARMv8: Exception Levels (EL0, EL1, EL2, EL3)
  • Processor state:
    • ARMv7: Current Program Status Register (CPSR)
    • ARMv8: Number of system registers that define the proccessors state: PSTATE (accessed with msr instruction):
      • CurrentEL: Holds the current Exception level
      • DAIF: Specifies the current interrupt mask bits.
      • NZCV: Holds the condition flags.
      • SPSel: At EL1 or higher, this selects between the SP for the current Exception level and SP_EL0.
  • System registers:
    • ARMv7: System registers are typically accessed through coprocessor 15 (CP15) operations
    • ARMv8: System registers accessed using MSR and MRS instructions
  • The System Control Register (SCTLR):
    • ARMv7: Not available
    • ARMv8: Register that controls standard memory, system facilities and provides status information for functions that are implemented in the core.
  • Instructions sets:
    • ARMv7: Thumb (T32 - 16 bit) and AArch32 (A32 - 32 bit)
    • ARMv8: AArch64 (A64 - 64 bit) or A32 and T32

ARM Exception Levels

The new model that the Armv8-A architecture introduced is the exception model. It enables the concept of privilege that modern software expects. An example of this is the split between the operating system kernel, which has a high level of access to system resources, and user applications, which have a more limited ability to configure the system.

Armv8-A enables this split by implementing different levels of privilege. The current level of privilege can only change when the processor takes or returns from an exception. Therefore, these privilege levels are referred to as Exception levels in the Armv8-A architecture. Each Exception level is numbered, and the higher levels of privilege have higher numbers.

Levels in Increasing Privilege order:

  • EL0 = Applications.
  • EL1 = OS kernel and associated functions that are typically described as privileged.
  • EL2 = Hypervisor.
  • EL3 = Firmware

A common usage model has application code running at EL0, with an operating system running at EL1. EL2 is used by a hypervisor, with EL3 being reserved by low-level firmware and security code.

There are two types of privilege relevant to this topic. The first is privilege in the memory system, and the second is privilege from the point of view of accessing processor resources. Both are affected by the current Exception level.

Memory Privilege

Armv8-A implements a virtual memory system, in which a Memory Management Unit (MMU) allows software to assign attributes to regions of memory. These attributes include read/write permissions, which can be configured with two degrees of freedom. This configuration allows separate access permissions for privileged and unprivileged accesses.

Memory access initiated when the processor is executing in EL0 will be checked against the Unprivileged access permissions. Memory accesses from EL1, EL2 and EL3 will be checked against the privileged access permissions.

Because this memory configuration is programmed by software using the MMU's translation tables, you should consider the privilege necessary to program those tables. The MMU configuration is stored in System registers, and the ability to access those registers is also controlled by the current Exception level.

Register Access

Configuration settings for Armv8-A processors are held in a series of registers known as System registers. The combination of settings in the System registers define the current processor Context. Access to the System registers is controlled by the current Exception level.

The name of the System register indicates the lowest Exception level from which that register can be accessed. For instance, TTBR0_EL1 is the register that holds the base address of the translation table used by EL0 and EL1. This register cannot be accessed from EL0, and any attempt to do so will cause an exception to be generated.

The architecture has many registers with conceptually similar functions that have names that differ only by their Exception level suffix. These are independent, individual registers that have their own encodings in the instruction set and will be implemented separately in hardware.

For example, the following registers all perform MMU configuration for different translation regimes. The registers have similar names to reflect that they perform similar tasks, but they are entirely independent registers with their own access semantics:

  • SCTLR_EL1 - Top level system control for EL0 and EL1
  • SCTLR_EL2 - Top level system control for EL2
  • SCTLR_EL3 - Top level system control for EL3

Note 1: EL1 and EL0 share the same MMU configuration and control is restricted to privileged code running at EL1. Therefore there is no SCTLR_EL0 and all control is from the EL1 accessible register. This model is generally followed for other control registers.

Note 2: Higher Exception levels have the privilege to access registers that control lower levels. For example, EL2 has the privilege to access SCTLR_EL1 if necessary.

Raspberry Pi

Raspberry Pi 0,1,2

There is no support for EL Privilege and Exception levels for arm v6, arm v7 (aarch32).

Raspberry Pi 3,4

There is support for the 4 EL Privilege and Exception levels for arm v8 (aarch64).

ARM Interrupts

ARMv6-ARMv7 Interrupts

An exception can be caused by the execution of an exception generating instruction or triggered as a response to a system behavior such as an interrupt, alignment fault or memory system fault. Synchronous and asynchronous exceptions can occur within the architecture.

The following types of exception are system related:

  • Supervisor calls that applications use to request a service from the underlying operating system. Using the SVC instruction, the application can instigate a supervisor call for a service requiring privileged access to the system.
  • Instruction execution related errors.
  • Data memory access errors on any load or store.
  • Usage faults from a variety of execution state related errors, such as executing an UNDEFINED instruction.

On the Raspberry Pi, when an exception occurs, a specific address is loaded into the program counter register, branching execution to this point. At this location, the kernel developer needs to write branch instructions to routines that handle the exceptions. This set of addresses, also known as the Vector Table, starts at address 0. Below is a table that describes each exception:

Address Exception Name Exception Source Action to take
0x00 Reset Hardware Reset Restart the Kernel
0x04 Undefined instruction Attempted to execute a meaningless instruction Kill the offending program
0x08 Software Interrupt (SWI) Software wants to execute a privileged operation Perform the opertation and return to the caller
0x0C Prefetch Abort Bad memory access of an instruction Kill the offending program
0x10 Data Abort Bad memory access of data Kill the offending program
0x14 Reserved Reserved Reserved
0x18 Interrupt Request (IRQ) Hardware wants to make the CPU aware of something Find out which hardware triggered the interrupt and take appropriate action
0x1C Fast Interrupt Request (FIQ) One select hardware can do the above faster than all others Find out which hardware triggered the interrupt and take appropriate action

ARMv8 Interrupts

In ARM.v8 architecture, interrupts are part of a more general term: exceptions. There are 4 types of exceptions, as defined at section D1.13 of the AArch64-Reference-Manual:

  • Synchronous exception:
    • Exceptions of this type are always caused by the currently executed instruction. For example, you can use str instruction to store some data at an unexistent memory location. In this case, a synchronous exception is generated. Synchronous exceptions also can be used to generate a "software interrupt". Software interrupt is a synchronous exception that is generated on purpose by svc instruction.
  • IRQ (Interrupt Request):
    • Those are normal interrupts. They are always asynchronous, which means that they have nothing to do with the currently executed instruction. In contrast to synchronous exceptions, they are always not generated by the processor itself, but by external hardware.
  • FIQ (Fast Interrupt Request):
    • This type of exception is called "fast interrupts" and exist solely for the purpose of prioritizing exceptions. It is possible to configure some interrupts as "normal" and other as "fast". Fast interrupts will be signaled first and will be handled by a separate exception handler. Linux doesn't use fast interrupts and we also are not going to do so.
  • SError (System Error):
    • Like IRQ and FIQ, SError exceptions are asynchronous and are generated by external hardware. Unlike IRQ and FIQ, SError always indicates some error condition. Here you can find an example explaining when SError can be generated.

Exception vectors

  • Each exception type needs its own handler.
  • Also, separate handlers should be defined for each different execution state, in which exception is generated.

There are 4 execution states that are interesting from the exception handling standpoint.

If we are working at EL1 those states can be defined as follows:

  1. EL1t Exception is taken from EL1 while stack pointer was shared with EL0. This happens when SPSel register holds the value 0.
  2. EL1h Exception is taken from EL1 at the time when dedicated stack pointer was allocated for EL1. This means that SPSel holds the value 1 and this is the mode that we are currently using.
  3. EL0_64 Exception is taken from EL0 executing in 64-bit mode.
  4. EL0_32 Exception is taken from EL0 executing in 32-bit mode.

In total, we need to define 16 exception handlers (4 exception levels multiplied by 4 execution states), even if we don't want to use them all, because we want to be able to print a meaningful error message in case something goes wrong, and we are in the wrong exception level-state:

  • EL1t -> Sync, IRQ, FIQ, SError
  • EL1h -> Sync, IRQ, FIQ, SError
  • EL0_64 -> Sync, IRQ, FIQ, SError
  • EL0_32 -> Sync, IRQ, FIQ, SError

Vector Table

A special structure that holds addresses of all exception handlers is called exception vector table or just vector table. The structure of a vector table is defined in Table D1-5 Vector offsets from vector table base address at section D1.10.2. Each exception vector can ocupy 0x80 bytes maximum.

A vector table occupies a number of word-aligned addresses (word = 32 bits) in memory, starting at the vector base address.

Each Exception level has an associated Vector Base Address Register (VBAR), that defines the exception base address for the table at that Exception level.

For working the OS kernel in EL1, we are interested in the VBAR_EL1 register.

The offsets for each exception types, are defined in the table:

Exception taken from Synchronous IRQ or vIRQ FIQ or vFIQ SError or vSError
Current Exception level with SP_EL0 0x000 0x080 0x100 0x180
Current Exception level with SP_ELx, x>0. 0x200 0x280 0x300 0x380
Lower Exception level, where the implemented level immediately lower than the target level is using AArch64. 0x400 0x480 0x500 0x580
Lower Exception level, where the implemented level immediately lower than the target level is using AArch32. 0x600 0x680 0x700 0x780

Exception Classes - Errors

If the exception is a synchronous exception or an SError interrupt, information characterizing the reason for the exception is saved in the ESR_ELx at the Exception level the exception is taken to, as defined at section D1.10.4.

The information saved is determined at the time the exception is taken, and is not changed as a result of the explicit synchronization that takes place at the start of taking the exception.

Exception Return

In the Armv8-A architecture, an exception return is always to the same Exception level or a lower Exception level.

An exception return is used for:

  • A return to a previously executing thread.
  • Entry to a new execution thread. For example:
    • The initialization of a hypervisor by a Secure monitor.
    • The initialization of an operating system by a hypervisor.
    • Application entry from an operating system or hypervisor.

ELR_ELx and SPSR_ELx are the ELR_ELx and SPSR_ELx at the Exception level the exception is returning from. The exception return makes this ELR_ELx and SPSR_ELx UNKNOWN, as defined at section D1.11.

If we are working at the EL1 level:

  • ESR (esr_el1)
    • Exception Syndrome Register (ESR) contains detailed information about what causes an exception.
  • ELR (elr_el1)
    • Exception Link Register (ELR) contains the address of the instruction that had been executed when the exception was generated (for synchronous interrupts).

Exceptions Initialization

In order for the exception handling to work, we inform the processor about the address of our vector table, by setting the vbar_el1 (Vector Base Address Register) to the vector table address.

In code:

	adr x0, vectors		/* load VBAR_EL1 with virtual */
	msr vbar_el1, x0	/* vector table address */

Exceptions Masking - Unmasking

The processor must be able to mask and unmask the interrupts (disable and enable), in order to do critical tasks, like saving the state of the processor in an interrupt handler. That's why whenever an exception handler is executed, the processor automatically disables all types of interrupts.

This process can also be done manually from code. The interrupts does not have to be masked for the whole duration of the exception handler. It is legal to unmask interrupts after you saved processor state and therefore it is also legal to have nested interrupts. For simplicity we can omit this feature and mask all interrupts in an exception handler.

ARM processor state has 4 bits that are responsible for holding mask status for different types of interrupts. Those bits which are defined in section B1.2.2, are the following:

  • D: Masks debug exceptions. These are a special type of synchronous exceptions. For obvious reasons, it is not possible to mask all synchronous exceptions, but it is convenient to have a separate flag that can mask debug exceptions.
  • A: Masks SErrors. It is called A because SErrors sometimes are called asynchronous aborts.
  • I: Masks IRQs
  • F: Masks FIQs

The registers responsible for changing interrupt mask status are called daifclr and daifset. We can set and clear specific bits according to the exception we want to mask (set), or unmask (clear):

  • daif{clr,set}:
    • D: Un/Masks debug exceptions (Bit 0)
    • A: Un/Masks SErrors (Bit 1)
    • I: Un/Masks IRQs (Bit 2)
    • F: Un/Masks FIQs (Bit 3)

References

Previous Page

Bootloader

Next Page

Raspberry Pi Hardware

Clone this wiki locally