System control
This exercise looks at the instructions for accessing system and special registers. System and special registers control the operation of the processor, for example cache configuration. Typically, system and special registers are programmed in start-up code, before switching to a C environment.
In Data processing and flow control and Access memory a startup.s
file was provided, containing a very minimal reset handler. In this exercise, you will implement this file yourself. If you have a problem, look at the startup.s that is provided for the other examples as a reference.
Get started
Like with Access memory, a framework project is provided to get you started. Follow these steps:
- Import the 3_sys_regs project into the Arm Development Studio.
main.c
A simple “hello world” C program.startup.s
This is the startup code that we will complete in the exercise.- Open startup.s.
- Detection of which core is being run on
We will run the example on a multiprocessor model, but the example is not written to be multi-threaded and needs to just run on core 0 (affinity 0.0.0.0). The startup code needs to check the ID of the core. If the ID of the core is not 0.0.0.0 then software should put the core to sleep using the WFI instruction. - Clearing floating-point trap
The floating-point traps are Unknown at reset, but the C compiler assumes that floating-point operations are available before the C library initialization code (__main
) is called. The startup code needs to ensure that the traps on accesses to the FPU are cleared. - Install the EL3 vector table
Unlike earlier versions of the Arm architecture, in AArch64 there is no default vector table location. Software must install a vector table before the first exceptions are generated. This example does not use exceptions itself, but it is good practice to install a simple vector table to capture unexpected exceptions. TCPAC -
Trap lower Exception level accesses toCPTR_EL2
andCPACR_EL1
TAM -
Trap lower Exception level accesses to AMUTTA -
Trap accesses to trace registersTFP -
Trap EL3 use of FP registers- Write a sequence which will set
CPTR_EL3
to 0. - Implement code that reads
MPIDR_EL1
and check the affinity value, putting the core to sleep if not 0.0.0.0. - What is the format of the
MPIDR_EL1
register? - How will you extract and compare the full affinity value?
- What will happen if the secondary cores are unexpectedly woken from standby?
ADR Xd, <label>
LDR Xd, =<label>
- Complete the code to set the EL3 vector table location, using either of these instructions.
LDR Xd, <label>
Returns inXd
the value at <label>LDR Xd, =<label>
Returns inXd
the address of <label>- Right-click on the project and select Build Project to build the project.
- Check for any errors. If there are any, correct them and try to rebuild the project.
- Use the A64 – sys reg.launch script in the project to launch the model.
- 0.0.0.0 Cortex-A72, core 0
- 0.0.0.1 Cortex-A72, core 1
- 0.0.1.0 Cortex-A53, core 0
- 0.0.1.1 Cortex-A53, core 1
- 0.0.1.2 Cortex-A53, core 2
- 0.0.1.3 Cortex-A53, core 3
- ARM_Cortex-A72_0: 0x0000000080000000 -> affinity 0.0.0.0
- ARM_Cortex-A72_1: 0x0000000080000001 -> affinity 0.0.0.1
- Step through the first few instructions of the image that is connected to core 0, to confirm that it is correctly checking the ID.
- Disconnect and re-launch the model, this time stepping through the image on core 1 to compare the results.
The imported project then appears in the Project Explorer pane, as you can see in this screenshot:
Within the **sys_reg**
Implement the startup code
The framework code is shown here:
.global start64 .type start64, @function start64: // Check which core is running // ---------------------------- // Core 0.0.0.0 should continue to execute // All other cores should be put into sleep (WFI) // // Your code here // // Disable trapping of CPTR_EL2 accesses or use of Adv.SIMD/FPU // ------------------------------------------------------------- // // Your code here // // Install EL3 vector table // ------------------------- // // Your code here // // The effect of changes to the system registers are // only guaranteed to be visible after a context // synchronization event. See the Barriers guide ISB // Branch to scatter loading and C library init code // ------------------------------------------------- .global __main B __main
This example runs in EL3. For this exercise, we do not consider what would be necessary to switch Exception levels. There are three pieces of functionality that we need to implement:
Floating point traps
The comment in startup.s tells us that the register that controls the FPU traps is CPTR_EL3. Let’s start by looking at the register description.
The register contains several trap controls:
If you read the description of each field, you will find that a value of 0 means do not trap. Therefore, in this case we want to set the register to 0.
Detect which core the software is running on
Each core has a unique affinity number, formatted as four 8-bit fields, as you can see here:
<aff3>.<aff2>.<aff1>.<aff0>
The affinity of a core can be read from the MPIDR_EL1
register. Unlike Data processing and flow control and Access memory, this exercise uses a model that contains multiple cores. However, the software is only written to run on one core. This means that the startup code must check which core it is running on, and if it is not core 0.0.0.0, the code should put the core to sleep using a WFI
.
Things to consider:
Install a vector table
The provided project includes a simple vector table at the end of startup.s
. The format of the table, and how exceptions are handled more generally, is beyond the scope of these exercises. For more information, refer to Exception model.
For this exercise, we need to write the address of the vector table into the Vector Table Base Address register (VBAR_EL3). To do this, we need to know the address of the vector table. Let’s look at the vector table in the project, which is shown in the following code:
.global vector_table vector_table: // ------------------------------------------------------------ // Current EL with SP0 // ------------------------------------------------------------ .balign 128 sync_current_el_sp0: B . // Synchronous …
The label vector_table
marks the start of the table. We need to write the address of this label to VBAR_EL3
.
There are two pseudo instructions which allow you to get the address of a label:
ADR
only works for labels that are within the same compilation unit. LDR
can also be used for imported global symbols.
Note: There are two similar operations for LDR:
Run the completed image
When you have completed the function, you can test it using the Fixed Virtual Platform (FVP) models that are provided with Arm Development Studio.
Like in Access memory, the Console tab shows the build messages. If the project builds successfully, the output will look like what you can see in the following screenshot:
Note: If you look at the link command that is being issued for the image, it includes “—entry=start64”. This tells the compiler to set the entry point of the image to the label start64, which is the beginning of the startup code. The entry point is the address that the PC will be set to when the image is loaded into the simulation.
When you have successfully built your image, you can try it out. Follow these steps:
The model used for this exercise contains a dual-core Cortex-A72 processor and a quad-core Cortex-A53 processor. The affinity values for these cores are:
The debugger is configured to connect to the two Cortex-A72 cores, as you can see in the following screenshot:
The different debugger panes, like Source view and Register view, can only show information for one core at a time. The Debug Control pane selects which core’s information is currently being displayed. In the preceding screenshot, you can see that core 0, ARM_Cortex-A72_0, is selected. If core 1 is selected, the Debug Control pane looks like what you can see in this screenshot:
Using the Register pane, we can manually check the MPIR_EL1 (AArch64 -> System -> ID) value that is reported by each core:
When you are satisfied that your code is working, run the image. The output from the simulator is shown in the Target Console tab. The output for a successful run looks like this code:
terminal_0: Listening for serial connection on port 5000 terminal_1: Listening for serial connection on port 5001 terminal_2: Listening for serial connection on port 5002 terminal_3: Listening for serial connection on port 5003 CADI server started listening to port 7000 Info: FVP_Base_Cortex_A72x2_A53x4: CADI Debug Server started for ARM Models... CADI server is reported on port 7000 Hello world
The startup file for this example is basic, and a real image would perform more initialization. To explore this topic, see the Bare Metal Boot guide.