4

I want to read the values of some Cortex-A53 registers, such as

  • D_AA64ISAR0_EL1 (AArch64)
  • ID_ISAR5 (Aarch32)
  • ID_ISAR5_EL1 (Aarch64)

Unfortunately, I lack a little embedded/assembly experience. The documentation reveals

To access the ID_AA64ISAR0_EL1: MRS , ID_AA64ISAR0_EL1 ; Read ID_AA64ISAR0_EL1 into Xt ID_AA64ISAR0_EL1[31:0] can be accessed through the internal memory-mapped interface and the external debug interface, offset 0xD30.

I decided to utilize devmem2 on my target (since busybox does not include the devmem applet). Is the following procecure correct to read the register?

devmem2 0xD30

The part which I am unsure about is using the "offset" as a direct physical address. If it is the actual address, why call if "offset" and not "address". If it's an offset, what is the base address? I am 99% certain this is not the correct procedure, but how do I know the base address to add the offset to? I have searched the Armv8 technical reference manual and A53 MPCore documents to no avail. The explain the register contents in detail but seem to assume you read them from ASM using the label ID_AA64ISAR0_EL1.

Update:

I found this:

Configuration Base Address Register, EL1 The CBAR_EL1 characteristics are: Purpose Holds the physical base address of the memory-mapped GIC CPU interface registers.

But it simply duplicates my problem, how to read this other register?

Update 2: The first update seems relevant only for GIC and not for configuration registers I am trying to read (I misunderstood the information I think).

For the specific problem at hand (checking crypto extension availability) one may simply cat /proc/cpuinfo and look for aes/sha etc.

Update 3:

I am now investigating http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0176c/ar01s04s01.html, as well as the base address being SoC specific and thus may be found in the reference manual of the SoC.

Update 4:

Thanks to the great answer I seem to be able to read data via my kernel module:

[ 4943.461948] ID_AA64ISA_EL1 : 0x11120
[ 4943.465775] ID_ISAR5_EL1     : 0x11121

P.S.: This was very insightful, thank you again!

Update 5: Source code as per request:

/******************************************************************************
 *
 *   Copyright (C) 2011  Intel Corporation. All rights reserved.
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; version 2 of the License.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *****************************************************************************/

#include <linux/module.h>
#include <linux/types.h>

/*****************************************************************************/

// read system register value ID_AA64ISAR0_EL1 (s3_0_c0_c6_0).
static inline uint64_t system_read_ID_AA64ISAR0_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, ID_AA64ISAR0_EL1" : "=r" (val));
    return val;
}

// read system register value ID_ISAR5_EL1 (s3_0_c0_c2_5).
static inline uint64_t system_read_ID_ISAR5_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, s3_0_c0_c2_5" : "=r" (val));
    return val;
}

/*****************************************************************************/

int init_module(void)
{
    printk("ramdump Hello World!\n");
    printk("ID_AA64ISAR0_EL1 : 0x%llX\n", system_read_ID_AA64ISAR0_EL1());
    printk("ID_ISAR5_EL1     : 0x%llX\n", system_read_ID_ISAR5_EL1());
    return 0;
}

void cleanup_module(void)
{
    printk("ramdump Goodbye Cruel World!\n");
}

MODULE_LICENSE("GPL");
smoothware
  • 756
  • 5
  • 15

1 Answers1

2

Disclaimer: I am not an Aarch64 expert, but I am currently learning about the architecture and have read a bit.

You cannot read ID_AA64ISAR0_EL1, ID_ISAR5_EL1 nor ID_ISAR5 from a user-mode application running at EL0: the _EL1 suffix means than running at least at EL1 is required in order to be allowed to read those two registers.

You may find helpful to read the pseudo-code in the arm documentation here and here. In the case of ID_ISAR5 for example, the pseudo-code is very explicit:

if PSTATE.EL == EL0 then
    UNDEFINED;
elsif PSTATE.EL == EL1 then
    if EL2Enabled() && !ELUsingAArch32(EL2) && HSTR_EL2.T0 == '1' then
        AArch64.AArch32SystemAccessTrap(EL2, 0x03);
    elsif EL2Enabled() && ELUsingAArch32(EL2) && HSTR.T0 == '1' then
        AArch32.TakeHypTrapException(0x03);
    elsif EL2Enabled() && !ELUsingAArch32(EL2) && HCR_EL2.TID3 == '1' then
        AArch64.AArch32SystemAccessTrap(EL2, 0x03);
    elsif EL2Enabled() && ELUsingAArch32(EL2) && HCR.TID3 == '1' then
        AArch32.TakeHypTrapException(0x03);
    else
        return ID_ISAR5;
elsif PSTATE.EL == EL2 then
    return ID_ISAR5;
elsif PSTATE.EL == EL3 then
    return ID_ISAR5;

One easy way to read those register would be to write a tiny loadable kernel module you could call from your user-mode application: Since the Linux kernel is running at EL1, it is perfectly able to read those three registers.

See for example this article for a nice introduction to Linux loadable kernel modules.

And this is likely that an application running at EL0 cannot access memory-mapped registers accessible only from EL1, since this would obviously break the protection scheme.

The C code snippets required to read those registers in Aarch64 state would be (tested with gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu) :

#include <stdint.h>

// read system register value ID_AA64ISAR0_EL1 (s3_0_c0_c6_0).
static inline uint64_t system_read_ID_AA64ISAR0_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, s3_0_c0_c6_0" : "=r" (val));
    return val;
}

// read system register value ID_ISAR5_EL1 (s3_0_c0_c2_5).
static inline uint64_t system_read_ID_ISAR5_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, s3_0_c0_c2_5" : "=r" (val));
    return val;
}

Update #1: The GCC toolchain does not understand all arm system register names, but can nevertheless properly encode system registers access instructions if specified which exact values of the coproc, opc1, CRn, CRm, and opc2 fields are associated to this register.

In the case of ID_AA64ISAR0_EL1, the values specified in the Arm® Architecture Registers Armv8, for Armv8-A architecture profile document are:

coproc=0b11, opc1=0b000, CRn=0b0000, CRm=0b0110, opc2=0b000

The system register alias would then be s[coproc]_[opc1]_c[CRn]_c[CRm]_[opc2], that is s3_0_c0_c6_0 in the case of ID_AA64ISAR0_EL1.

Frant
  • 3,769
  • 1
  • 11
  • 20
  • devmem uses /dev/mem device to map physical addresses, which is possible with root privileges (which I have). I already have a "hello world" minimal kernel module in place (which currently does nothing). But I would need to figure out how to use inline assembly (for MRS instruction), or use an existing wrapper, which I have no knowledge of. – smoothware Feb 05 '20 at 13:41
  • 1
    @smoothware: I see, I augmented my answer accordingly. – Frant Feb 05 '20 at 13:46
  • @smoothware: Would you agree that arm designed the Aarch64 architecture so that `ID_AA64ISAR0_EL1` and `ID_ISAR5_EL1` could not be read by user applications running at `EL0`, even with root privileges, and therefore allowing root user to use `/dev/mem` to access them would defy their protection scheme ? a Linux application running at UID 0 is still running at Aarch64 `EL0` (I guess). – Frant Feb 05 '20 at 14:07
  • 1
    @smoothware: The [Exception model](https://developer.arm.com/architectures/learn-the-architecture/exception-model/single-page) documentation is a very interesting read IMHO. – Frant Feb 05 '20 at 14:13
  • thank you, I will try this shortly. Could you explain why you specify "s3_0_c0_c6_0" as register instead of "ID_AA64ISAR0_EL1" as my documentation specifies? And how did you figure out to use that label? Thanks in advance! – smoothware Feb 05 '20 at 15:09
  • @smoothware I can answer my own question, both types of labels work. – smoothware Feb 05 '20 at 16:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207307/discussion-between-frant-and-smoothware). – Frant Feb 05 '20 at 20:36