EL1 Single-level table

In this section of the guide, we recreate the single-level table at EL3 example, this time running at EL1 in Non-secure state. The single-level table at EL3 and multiple-level table examples run at EL3.

In the examples package, the files are in \el1_stage1\

Enter NS.EL1

The Processing Element (PE) always comes out of reset in the highest implemented Exception level. For our test system, the highest implemented Exception level is EL3. The example therefore needs to include code to switch from EL3 to EL1. Before changing the Exception level, we need to carry out some configuration at EL3.

The first register the example configures is the Secure Configuration Register (SCR_EL3), as you see in the following code:

 // Configure SCR_EL3
 // ------------------
 MOV   x0, #1              // NS=1
 ORR   x0, x0, #(1 << 1)   // IRQ=1 IRQs routed to EL3
 ORR   x0, x0, #(1 << 2)   // FIQ=1 FIQs routed to EL3
 ORR   x0, x0, #(1 << 3)   // EA=1  SError routed to EL3
 ORR   x0, x0, #(1 << 8)   // HCE=1 HVC instructions are enabled
 ORR   x0, x0, #(1 << 10)  // RW=1  Next EL down uses AArch64
 ORR   x0, x0, #(1 << 11)  // ST=1  Secure EL1 can access timers
 MSR   SCR_EL3, x0 

There are many settings in SCR_EL3. Two settings are important for this example:

NS
Controls whether lower Exception levels are Secure or Non-secure
RW
Controls whether the next Exception level uses AArch64 or AArch32

The example sets both bits. This means that lower Exception levels are Non-secure and that EL2 uses AArch64.

We also need to configure the Hypervisor Configuration Register (HCR_EL2). In a real software stack, code running in EL2 would do this. However, to keep the example simple, these registers are programmed from EL3 instead. The code to configure HCR_EL2 is shown here:

 // Configure HCR_EL2
 // ------------------
 ORR   w0, wzr, #(1 << 3)       // FMO=1
 ORR   x0, x0, #(1 << 4)        // IMO=1
 ORR   x0, x0, #(1 << 31)       // RW=1     NS.EL1 is AArch64
                                // TGE=0     Entry to NS.EL1 is possible
                                // VM=0     Stage 2 MMU disabled
 MSR   HCR_EL2, x0

Like with the SCR_EL3, HCR_EL2 contains many controls. For this example, like with the single-level table at EL3 and multiple-level table examples, we are most interested in two settings:

RW
Controls whether EL1 uses AArch64 or AArch32
VM
Enables/disables stage 2 translation at EL1 and EL0

The example disables stage 2 and sets EL1 to use AArch64.

There are other settings in EL2 registers that we need to configure before switching to EL2. These are shown in the following code:

 // Set up VMPIDR_EL2/VPIDR_EL1
 // ---------------------------
 MRS   x0, MIDR_EL1
 MSR   VPIDR_EL2, x0
 MRS   x0, MPIDR_EL1
 MSR   VMPIDR_EL2, x0
 
 
 // Set VMID
 // ---------
 // Although we are not using stage 2 translation, NS.EL1 still cares
 // about the VMID
 MSR   VTTBR_EL2, xzr


 // Set SCTLRs for EL1/2 to safe values
 // ------------------------------------
 MSR   SCTLR_EL2, xzr
 MSR   SCTLR_EL1, xzr

Reads of the MPIDR_EL1 and MIDR_EL2 registers at NS.EL1 return virtual values. The registers which hold these virtual values, VMPIDR_EL2 and VPIDR_EL2, do not have defined reset values. Software should initialize these registers before entering EL1 for the first time. For this example, we are not using virtualization. This means that we can copy the physical values.

Even though stage 2 is disabled, EL1 still uses a Virtual Machine Identifier (VMID). It is good practice to set this to a known value, before entering EL1. This is particularly important when working in a multi-core environment. All the PEs that run within the same NS.EL0/1 translation regime need to use the same VMID.

Finally, there are separate System Control Registers (SCTLR_ELx) for EL3, EL2, and EL1. Only the SCTLR_ELx for the highest implemented Exception level has a known reset value. Software must set the SCTLR_ELx registers for lower Exception levels to known or safe values before entering those Exception levels. This example sets them to 0, which ensures that the MMU for that Exception level is disabled.

Now that the minimum configuration is performed, control pass to NS.EL1, as you see in the following code:

 ADR   x0, el1_entry
 MSR   ELR_EL3, x0

 LDR   x0, =AArch64_EL1_SP1
 MSR   spsr_el3, x0
 
 ERET

// ------------------------------------------------------------
// Enter EL1
// ------------------------------------------------------------

el1_entry:

The only way to move to a lower Exception level is to perform an exception return. Normally, the exception return information is generated as part of taking an exception.. Because this is the part of boot the example instead creates the required information and populates the registers. The example sets the Saved Processor State Register (SPSR_ELx) to indicate EL1 using AArch64, and the Exception Link Register (ELR_ELx) to point to the start of the EL1 code. The ERET instruction then performs the Exception return.

Configure the MMU at EL1

Now that execution has entered EL1, the next step is to configure the MMU. The steps are the same as in the first example, but this time we use _EL1 registers instead of _EL3 registers. For examples, let's look at the following code:

 // Set the Base address
 // ---------------------
 LDR   x0, =tt_l1_base
 MSR   TTBR0_EL1, x0          


 // Set up memory attributes
 // -------------------------
 // This equates to:
 // 0 = b01000100 = Normal, Inner/Outer Non-Cacheable
 // 1 = b11111111 = Normal, Inner/Outer WB/WA/RA
 // 2 = b00000000 = Device-nGnRnE
 MOV   x0, #0x000000000000FF44
 MSR   MAIR_EL1, x0

Unlike EL3, in EL1/0 there are two virtual address regions: one at the bottom of the address space and another at the top of the address space. This is illustrated in the following diagram:

By convention, the lower address region is called User space and the upper region is called Kernel space. However, this is only a convention and you will not see these names used in the Architecture Reference Manual.

For this example, we only configure the lower region. We disable the upper region, using a control in the Translation Control Register (TCR_ELx). The code for this is shown here:

 // Set up TCR_EL1
 // ---------------
 MOV   x0, #0x19             // T0SZ=0b011001 Limits VA space to 39 bits
 ORR   x0, x0, #(0x1 << 8)   // IGRN0=0b01  Walks to TTBR0 are Inner WB/WA
 ORR   x0, x0, #(0x1 << 10)  // OGRN0=0b01  Walks to TTBR0 are Outer WB/WA
 ORR   x0, x0, #(0x3 << 12)  // SH0=0b11   Inner Shareable
 ORR   x0, x0, #(0x1 << 23)  // EPD1=0b1   Disable table walks from TTBR1
                             // TBI0=0b0
                             // TG0=0b00   4KB granule for TTBR0
                             // A1=0     TTBR0 contains the ASID
                             // AS=0     8-bit ASID
                             // IPS=0     32-bit IPA space
 MSR   TCR_EL1, x0

The EPDn bits enable or disable walks from the lower region (EPD0) and the upper region (EPD1). The example only configures the lower region (EPD1==0). Walks to the upper region are disabled (EPD1==1). Because table walks to the upper region are disabled, the example does not need to provide a table pointer in TTBR1_EL1.

The code to generate the translation tables is unchanged from the single-level table at EL3 example.

Non-secure translation regimes

The example in this section of the guide runs in NS.EL1. The translation tables in this example are identical to the translation tables at EL3 in the single-level table at EL3 example. Does this mean that the resulting mappings are the same?

The answer is no. There is an important difference between Secure and Non-secure translation regimes. A Secure translation regime maps virtual addresses to Secure or Non-secure physical addresses that are controlled by the NS bit in the table entries. A Non-secure translation regime only maps to Non-secure physical addresses. The NS bit in the table entries is ignored.

In the single-level table at EL3 example and this example, the NS bit in the table entries is b0 (Secure). At EL3, this causes the outputted address to be Secure. In NS.EL1 the NS bit is ignored, and the outputted address is Non-secure.

Note: On a real system, it is very unlikely that you could run both these two examples, because the memory at physical address 0x8000_0000 would either be Secure or Non-secure. The memory system of a real system would not allow both kinds of access to the memory. However, the FVP model that is used for these examples allows us to control which types of accesses are permitted to DRAM using model parameters.

The table walk

Tracing this example gives a very similar result to the single-level table at EL3 example, as you see in the following code:

93 clk IT (93) 80000174 d5181000 O EL1h_n : MSR   SCTLR_EL1,x0
93 clk R SCTLR_EL1 00000000:00001005
93 clk CACHE FVP_Base_AEMv8A_AEMv8A.cluster0.cpu0.l1dcache LINE 0100 ALLOC 0x000080002000_NS
93 clk CACHE FVP_Base_AEMv8A_AEMv8A.cluster0.l2_cache LINE 0800 ALLOC 0x000080002000_NS
93 clk TTW ITLB LPAE 1:1 000080002010 0000000080000705 : BLOCK ATTRIDX=1 NS=0 AP=0 SH=3 AF=1 nG=0 16E=0 PXN=0 XN=0 ADDR=0x0000000080000000
93 clk TLB FILL FVP_Base_AEMv8A_AEMv8A.cluster0.cpu0.UTLB 1G 0x80000000_NS EL1_n vmid=0:0x0080000000_NS Normal InnerShareable Inner=WriteBackWriteAllocate Outer=WriteBackWriteAllocate xn=0 pxn=0 ContiguousHint=0
93 clk CACHE FVP_Base_AEMv8A_AEMv8A.cluster0.cpu0.l1icache LINE 000a ALLOC 0x000080000140_NS
93 clk CACHE FVP_Base_AEMv8A_AEMv8A.cluster0.l2_cache LINE 0050 ALLOC 0x000080000140_NS

There are, however, some important differences between the single-level table at EL3 example and this example, starting with the MSR, as this code shows:

93 clk IT (93) 80000174 d5181000 O EL1h_n : MSR   SCTLR_EL1,x0

TARMAC records the Exception level and Security state that instructions were executed in. In the previous examples, this was EL3h_s, but this example reports EL1h_n. This means that EL1 is in Non-secure state.

The created TLB entry is also different, as this code shows:

93 clk TLB FILL FVP_Base_AEMv8A_AEMv8A.cluster0.cpu0.UTLB 1G 0x80000000_NS EL1_n vmid=0:0x0080000000_NS Normal InnerShareable Inner=WriteBackWriteAllocate Outer=WriteBackWriteAllocate xn=0 pxn=0 ContiguousHint=0

The trace shows the TLB entry recording the translation regime (EL1_n). The trace also shows the VMID being stored (vmid=0). As explained in Enter NS.EL1, even when stage 2 translation is disabled, the VMID is still recorded for the Non-secure EL1 translation regime.

Previous Next