ashwio / arm64-sysreg-lib

Header-only C library for reading/writing 64-bit Arm registers, automatically generated by parsing the AArch64 System Register XML.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Arm64 System Register Library

Introduction

arm64-sysreg-lib is a header-only C library for reading/writing 64-bit Arm registers, automatically generated by parsing the AArch64 System Register XML.

Key features:

  • Bit structs defined for 263 system registers
  • Safe values defined for all writeable registers, with all currently/previously RES1 bits set to 1 and all currently/previously RES0 bits cleared to 0
  • Accessors for read, unsafe write, safe write, and read-modify-write sequences defined according to register accessibility
  • All library calls optimise down to an average of between one and four A64 assembly instructions and are inlined with no branches and no static storage
  • Support for both gcc and clang, each with -Wall -Wextra -pedantic -Werror flags and supporting both -std=c99 and -std=c11

Prerequisites

To build the library:

  • Python 3.8+
  • Beautiful Soup 4 (pip3.8 install beautifulsoup4)

To use the library in your own projects:

  • C compiler supporting at least C99 with C11 extensions

Known working compilers:

  • aarch64-none-elf-gcc (GCC) 7.2.0:

    • C99: -Wall -Wextra -pedantic -Werror -std=c99 -O2
    • C11: -Wall -Wextra -pedantic -Werror -std=c11 -O2
  • Apple clang version 11.0.3 (clang-1103.0.32.62): *

    • C99: -Wall -Wextra -pedantic -Werror -std=c99 -O2 --target=aarch64-none-elf
    • C11: -Wall -Wextra -pedantic -Werror -std=c11 -O2 --target=aarch64-none-elf

* Apple Clang also tested with --target=aarch64-linux-gnu.

High-level functionality

Bit structs

Availability: All system registers.

All parsed system registers define a union of the form:

union sctlr_el1
{
    u64 _;
    struct
    {
        u64 m : 1;
        u64 a : 1;
        u64 c : 1;
        u64 sa : 1;
        ...
    };
};

The ._ member allows for raw access to the underlying register value while the anonymous struct member allows for manipulation of the register's constituent bit fields.

Safe values

Availability: All writeable system registers.

The safe value for a writeable system register has all currently or previously RES1 fields set to 1 and all currently or previously RES0 fields cleared to 0. These values are used by the safe_write_<reg>() convenience macros, or you can use them yourself when manually constructing a value to write into a register using unsafe_write_<reg>().

Example:

static const union sctlr_el3 SCTLR_EL3_SAFEVAL =
{
    .res1_5_4 = 3,
    .eos = 1,
    .res1_16 = 1,
    .res1_18 = 1,
    .eis = 1,
    .res1_23 = 1,
    .res1_29_28 = 3,
};

Some of these fields are currently RES1 bits, such as .res1_5_4 for bits [5:4] and .res1_16 for bit [16]. Setting these to 1 in the safe value ensures portability when running on future CPU implementations where those bits have been repurposed into new fields, as in these cases a value of 1 will give the old behaviour while a value of 0 will give the new behaviour, and we don't want to inadvertently enable the new behaviour by clearing them.

This is why the .eos and .eis fields are also set to 1; these fields were previosuly RES1 bits but were repurposed into new fields in later revisions of the architecture. The user can choose to clear these to 0 explicitly if they want the new behaviour, but the library defaults to setting them to 1 in the safe value and, by extension, the safe_write_<reg>() convenience macro.

Reads

Availability: All readable system registers.

Read the current value of a system register into a field-accessible structure:

/* C code */
#include "sysreg/mpidr_el1.h"
u64 foo( void )
{
    return read_mpidr_el1().aff0;
}

/* Compiler output */
mrs     x8, mpidr_el1
and     x0, x8, #0xff
ret

Writes

Unsafe write

Availability: All writeable system registers.

Write a field-accessible structure into a system register. This function is prefixed unsafe_ to emphasise the fact that it has no provision for helping to ensure that any currently or previously RES1 fields are set to 1; when using this function it is the responsibility of the programmer to ensure that any such bits are set appropriately so as to ensure both correct behaviour and future portability. For this reason, it is recommended that you instead use safe_write_<reg>() wherever possible, or use <REG>_SAFEVAL as a basis for the value being constructed.

/* C code */
#include "sysreg/sctlr_el1.h"
void foo( void )
{
    union sctlr_el1 val = { .m=1, .c=1, .i=1 };
    unsafe_write_sctlr_el1(val);
}

/* Compiler output */
mov     w8, #0x1005         // Danger! No RES1 bits set! See safe_write_<reg>()
msr     sctlr_el1, x8
ret

Use the union's ._ member to write a raw value:

/* C code */
#include "sysreg/sctlr_el1.h"
void foo( u64 raw )
{
    union sctlr_el1 val = { ._=raw };
    unsafe_write_sctlr_el1(val);
}

/* Compiler output */
msr     sctlr_el1, x0
ret

Safe write

Availability: All writeable system registers.

Use safe_write_<reg>() to set a variadic list of fields. Any fields not specified in the variadic list that are currently or previously RES1 will be set to 1, and all other fields will be cleared to 0.

For example:

/* C code */
#include "sysreg/sctlr_el1.h"
void foo( void )
{
    safe_write_sctlr_el1( .m=1, .c=1, .i=1 );
}

/* compiler output */
mov     w8, #0x1985
movk    w8, #0x30d0, lsl #16
msr     sctlr_el1, x8
ret

Note how while we only specified .m=1, .c=1, and .i=1, the value written to SCTLR_EL1 also has all currently or previously RES1 fields set to 1, such as .itd=1, .sed=1, .eos=1, etc.

Repeating the same, but this time explicitly clearing one of those currently or previously RES1 fields to 0 in the variadic list:

/* in C */
#include "sysreg/sctlr_el1.h"
void foo( void )
{
    safe_write_sctlr_el1( .m=1, .c=1, .i=1, .itd=0 );
}

/* compiler output */
mov     w8, #0x1905
movk    w8, #0x30d0, lsl #16
msr     sctlr_el1, x8
ret

Here we can see bit [7] corresponding to .itd in the first MOV has been cleared; the value moved into w8 is now 0x1905 vs 0x1985 in the earlier example.

Read-modify-write

Availability: All system registers that are both readable and writeable.

Use read_modify_write_<reg>() to read the current value of a system register, set a variadic list of fields in that value, then write the result back.

/* in C */
void foo( void )
{
    read_modify_write_sctlr_el1( .m=1, .c=1, .i=1 );
}

/* compiler output */
mrs     x8, sctlr_el1
mov     w9, #0x1005
orr     x8, x8, x9
msr     sctlr_el1, x8
ret

NOTE: Many AArch64 system registers have architecturally UNKNOWN values at reset, meaning you should not perform a read-modify-write sequence when first initializing them. Instead, use safe_write_<reg>() to guarantee that all currently or previously RES1 fields are set to 1 and all other unspecified fields are cleared to 0.

Building and testing the library

NOTE: The library has already been built for you using the June 2020 release of the AArch64 System Register XML (SysReg_xml_v86A-2020-06). You can simply add -I/path/to/arm64-sysreg-lib/include to your compiler flags to begin using the library in your own projects straight away. You can also run the run-tests.py script to build the compilation tests using your chosen compiler. More detailed instructions for running the compilation tests can be found at the end of this document.

Step 1) Obtain AArch64 System Register XML

Download and extract the AArch64 System Register XML from the Arm A-Profile CPU architecture exploration tools page.

Alternatively, use curl to download the Armv8.6-A XML published in June 2020:

curl -O https://developer.arm.com/-/media/developer/products/architecture/armv8-a-architecture/2020-06/SysReg_xml_v86A-2020-06.tar.gz
tar xf SysReg_xml_v86A-2020-06.tar.gz

Step 2) Build

Run the provided run-build.py script, pointing it at the AArch64 System Register XML downloaded and extracted earlier:

python3.8 run-build.py /path/to/SysReg_xml_v86A-2020-06

Step 3) Test

Run the provided run-tests.py script, pointing it at your chosen compiler:

python3.8 run-tests.py [--keep] COMPILER_PATH [COMPILER_FLAGS]

For example:

python3.8 run-tests.py /path/to/aarch64-none-elf-gcc 

It is assumed the compiler uses the same switches as gcc and clang, and the script always invokes the compiler with the following flags:

-Wall -Wextra -pedantic -Werror

You may pass additional flags to the run-tests.py script which will be passed in turn to the compiler, for example:

python3.8 run-tests.py /path/to/aarch64-none-elf-gcc -std=c99 -O3  

If no -std flag is provided, the script defaults to -std=c11.

If no -O flag is provided, the script defaults to -O2.

If the compiler path contains substring clang and no --target flag is provided, the script defaults to --target=aarch64-none-elf.

By default the script will cleanup the generated .o object files after finishing the test run; pass the --keep flag before the compiler path if you wish to keep these files.

Known issues and limitations

This library is still in development and is not yet able to parse all files included in the AArch64 System Register XML.

Of the 485 AArch64-* files included in the SysReg_xml_v86A-2020-06 release that actually describe system register encodings:

  • 126 are skipped as they correspond to instructions that use the system register encoding space (ic, dc, tlbi, at, cfp, cpp, and dvp)
  • 263 successfully build
  • 96 fail to parse

Of the 96 that fail to parse:

  • 43 fail as their fields vary, such a when a bit in another register is set to 1
  • 19 fail due to being an arrayed register (<n>)
  • 23 fail due to having arrayed fields (<m>, <n>, or <x>)
  • 11 fail due to having variable length fields

Support for these registers will be added in a future release.

Not counted above are external system registers such as GICD_* and GICR_*, support for which will also be added later.

About

Header-only C library for reading/writing 64-bit Arm registers, automatically generated by parsing the AArch64 System Register XML.

License:MIT License


Languages

Language:C 99.4%Language:Python 0.6%