从 GNU/Linux shell 使用 devmem 读取 Armv8-A 寄存器

Reading Armv8-A registers with devmem from GNU/Linux shell

我想读取一些Cortex-A53寄存器的值,比如

遗憾的是,我缺乏一点embedded/assembly经验。文档显示

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.

我决定在我的目标上使用 devmem2(因为 busybox 不包含 devmem 小程序)。以下程序读取寄存器是否正确?

devmem2 0xD30

我不确定的部分是使用“偏移量”作为直接物理地址。如果它是实际地址,为什么调用“偏移量”而不是“地址”。如果是偏移量,基地址是什么?我 99% 确定这不是正确的程序,但我如何知道要添加偏移量的基地址? Armv8技术参考手册和A53 MPCore文档都找遍了,都没有找到。详细解释了寄存器内容,但似乎假设您使用标签 ID_AA64ISAR0_EL1.

从 ASM 读取它们

更新:

我发现了这个:

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

但它只是重复了我的问题,如何读取另一个寄存器?

更新二: 第一次更新似乎只与 GIC 有关,与我试图阅读的配置寄存器无关(我误解了我认为的信息)。

对于手头的具体问题(检查加密扩展可用性),可以简单地 cat /proc/cpuinfo 并查找 aes/sha 等

更新 3:

我现在正在调查 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0176c/ar01s04s01.html,以及特定于 SoC 的基地址,因此可以在 SoC 的参考手册中找到。

更新四:

多亏了很好的回答,我似乎可以通过我的内核模块读取数据了:

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

P.S.: 很有见地,再次感谢!

更新 5: 根据要求提供源代码:

/******************************************************************************
 *
 *   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");

免责声明:我不是 Aarch64 专家,但我目前正在学习架构并阅读了一些内容。

您无法从 EL0 处的用户模式应用程序 运行 中读取 ID_AA64ISAR0_EL1ID_ISAR5_EL1ID_ISAR5_EL1 后缀意味着至少需要 运行 EL1 才能读取这两个寄存器。

阅读 arm 文档中的伪代码可能会有所帮助 here and here。 以ID_ISAR5为例,伪代码非常明确:

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;

读取这些寄存器的一种简单方法是编写一个可以从用户模式应用程序调用的微型可加载内核模块:因为 Linux 内核在 EL1 运行 ], 完全可以读取这三个寄存器.

有关 Linux 可加载内核模块的详细介绍,请参阅 this 文章。

这很可能是 EL0 的应用程序 运行 无法访问只能从 EL1 访问的内存映射寄存器,因为这显然会破坏保护方案。

在 Aarch64 状态下读取这些寄存器所需的 C 代码片段将是(使用 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;
}

更新#1: GCC 工具链不理解所有的 arm 系统寄存器名称,但是如果指定了 coproc、opc1、CRn、CRm 和 opc2 字段的哪些确切值与该寄存器相关联,它仍然可以正确编码系统寄存器访问指令。

ID_AA64ISAR0_EL1的情况下,Arm® Architecture Registers Armv8, for Armv8-A architecture profile document are中指定的值:

coproc=0b11opc1=0b000CRn=0b0000CRm=0b0110opc2=0b000

系统寄存器别名将是 s[coproc]_[opc1]_c[CRn]_c[CRm]_[opc2],在 ID_AA64ISAR0_EL1 的情况下是 s3_0_c0_c6_0

我发现这个工具可能满足您的需求: system-register-tools

只是为arm64提供了读写系统寄存器的功能,类似x86中的MSR-tools