Skip to the content.

aarch64 cpufeature

简述

arm64_ftr_bits -> arm64_ftr_reg -> arm64_ftr_regs

arch/arm64/kernel/cpufeature.c 中定义的

static const struct __ftr_reg_entry {
	u32			sys_id;
	struct arm64_ftr_reg 	*reg;
} arm64_ftr_regs[] = {

	// ...
	/* Op1 = 0, CRn = 0, CRm = 4 */
	ARM64_FTR_REG_OVERRIDE(SYS_ID_AA64PFR0_EL1, ftr_id_aa64pfr0,
			       &id_aa64pfr0_override),

	// ...
	/* Op1 = 0, CRn = 0, CRm = 5 */
	ARM64_FTR_REG(SYS_ID_AA64DFR0_EL1, ftr_id_aa64dfr0),

arm64_ftr_bits

struct arm64_ftr_bits {
	bool		sign;	/* Value is signed ? */
	bool		visible;
	bool		strict;	/* CPU Sanity check: strict matching required ? */
	enum ftr_type	type;
	u8		shift;
	u8		width;
	s64		safe_val; /* safe value for FTR_EXACT features */
};

type 一共四种选择:

enum ftr_type {
      FTR_EXACT,
      FTR_LOWER_SAFE,
      FTR_HIGHER_SAFE,
      FTR_HIGHER_OR_ZERO_SAFE,
};

大多数时候,都是 FTR_LOWER_SAFE 定义的,这个很容易理解,因为取 CPU 能力的交集

如果 arm64_ftr_bits 没有定义对应的 bit,那么 sanitised val 会被配置为 0

static const struct arm64_ftr_bits ftr_id_aa64mmfr1[] = {
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_ECBHB_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_TIDCP1_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_AFP_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_HCX_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_ETS_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_TWED_SHIFT, 4, 0),
	// ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_XNX_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_HIGHER_SAFE, ID_AA64MMFR1_EL1_SpecSEI_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_PAN_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_LO_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_HPDS_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_VH_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_VMIDBits_SHIFT, 4, 0),
	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR1_EL1_HAFDBS_SHIFT, 4, 0),
	ARM64_FTR_END,
};

例如,如果注释掉 ID_AA64MMFR1_EL1_XNX_SHIFT ,那么可以确认,

		u64 hw = read_sysreg_s(reg);
		u64 san = read_sanitised_ftr_reg(reg);

hw 中的 xnx 为 1 ,但是 san 中会是 0

这非常合理,sanitised 就是用来描述操作系统对于数值的支持。

例子

commit 011e5f5bf529 (“arm64/cpufeature: Add remaining feature bits in ID_AA64PFR0 register”)

commit 011e5f5bf529f8ec2988ef7667d1a52f83273c36
Author: Anshuman Khandual <anshuman.khandual@arm.com>
Date:   Tue May 19 15:10:47 2020 +0530

    arm64/cpufeature: Add remaining feature bits in ID_AA64PFR0 register

    Enable MPAM and SEL2 features bits in ID_AA64PFR0 register as per ARM DDI
    0487F.a specification.

    Cc: Catalin Marinas <catalin.marinas@arm.com>
    Cc: Will Deacon <will@kernel.org>
    Cc: Mark Rutland <mark.rutland@arm.com>
    Cc: Suzuki K Poulose <suzuki.poulose@arm.com>
    Cc: linux-arm-kernel@lists.infradead.org
    Cc: linux-kernel@vger.kernel.org

    Suggested-by: Will Deacon <will@kernel.org>
    Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
    Link: https://lore.kernel.org/r/1589881254-10082-11-git-send-email-anshuman.khandual@arm.com
    [will: Make SEL2 a NONSTRICT feature per Suzuki]
    Signed-off-by: Will Deacon <will@kernel.org>

diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index ea075cc08c8f..638f6108860f 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -645,6 +645,8 @@
 #define ID_AA64PFR0_CSV2_SHIFT		56
 #define ID_AA64PFR0_DIT_SHIFT		48
 #define ID_AA64PFR0_AMU_SHIFT		44
+#define ID_AA64PFR0_MPAM_SHIFT		40
+#define ID_AA64PFR0_SEL2_SHIFT		36
 #define ID_AA64PFR0_SVE_SHIFT		32
 #define ID_AA64PFR0_RAS_SHIFT		28
 #define ID_AA64PFR0_GIC_SHIFT		24
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 41f6e9b26d18..68744871a65d 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -222,6 +222,8 @@ static const struct arm64_ftr_bits ftr_id_aa64pfr0[] = {
 	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_CSV2_SHIFT, 4, 0),
 	ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_DIT_SHIFT, 4, 0),
 	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_AMU_SHIFT, 4, 0),
+	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_MPAM_SHIFT, 4, 0),
+	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_SEL2_SHIFT, 4, 0),
 	ARM64_FTR_BITS(FTR_VISIBLE_IF_IS_ENABLED(CONFIG_ARM64_SVE),
 				   FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_SVE_SHIFT, 4, 0),
 	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_RAS_SHIFT, 4, 0),

FTR_LOWER_SAFE

前面提到了 arm64_ftr_bits::type 的功能,更多从异构的角度,也就是不同的 CPU 能力不同, 不过这个功能也适用于热迁移功能的。

最经典的例子就是,kvm_arm64_ftr_safe_value ,他禁止不同的 CPU 使用 ID_AA64DFR0_EL1_PMUVer ID_AA64DFR0_EL1_DebugVer ,但是热迁移,通过修改为 FTR_LOWER_SAFE , 允许从版本低的热迁移过来。

static s64 kvm_arm64_ftr_safe_value(u32 id, const struct arm64_ftr_bits *ftrp,
				    s64 new, s64 cur)
{
	struct arm64_ftr_bits kvm_ftr = *ftrp;

	/* Some features have different safe value type in KVM than host features */
	switch (id) {
	case SYS_ID_AA64DFR0_EL1:
		switch (kvm_ftr.shift) {
		case ID_AA64DFR0_EL1_PMUVer_SHIFT:
			kvm_ftr.type = FTR_LOWER_SAFE;
			break;
		case ID_AA64DFR0_EL1_DebugVer_SHIFT:
			kvm_ftr.type = FTR_LOWER_SAFE;
			break;
		}
		break;
	case SYS_ID_DFR0_EL1:
		if (kvm_ftr.shift == ID_DFR0_EL1_PerfMon_SHIFT)
			kvm_ftr.type = FTR_LOWER_SAFE;
		break;
	}

	return arm64_ftr_safe_value(&kvm_ftr, new, cur);
}

具体的检查逻辑在 arm64_check_features() 中:

在 arm64_check_features() 里,对每个 可写 字段会再调 kvm_arm64_ftr_safe_value() 检查源端值是不是 target 能安全接受的子集: arm64_check_features() 会在第一个失败的字段处直接返回 -E2BIG(内部), 然后 set_id_reg() 把它转成 -EINVAL(ret=-22)。

arm64_ftr_reg

arm64_ftr_reg 就是核心,

/*
 * @arm64_ftr_reg - Feature register
 * @strict_mask		Bits which should match across all CPUs for sanity.
 * @sys_val		Safe value across the CPUs (system view)
 */
struct arm64_ftr_reg {
	const char			*name;
	u64				strict_mask;
	u64				user_mask;
	u64				sys_val;
	u64				user_val;
	struct arm64_ftr_override	*override;
	const struct arm64_ftr_bits	*ftr_bits;
};

user_val 的使用

具体分析参考: ./feature-user.md

sys_val 的使用

就是通过 read_sanitised_ftr_reg 接口获取的:

u64 read_sanitised_ftr_reg(u32 id)
{
	struct arm64_ftr_reg *regp = get_arm64_ftr_reg(id);

	if (!regp)
		return 0;
	return regp->sys_val;
}

misc

例如,当 trace 一下 read_sanitised_ftr_reg ,可以发现其调用频率极高:

@[
        read_sanitised_ftr_reg+0
        filemap_map_pages+484
        do_read_fault+232
        do_fault+256
        handle_pte_fault+152
        __handle_mm_fault+444
        handle_mm_fault+172
        do_page_fault+376
        do_translation_fault+84
        do_mem_abort+72
        el0_ia+108
        el0t_64_sync_handler+204
        el0t_64_sync+420
]: 14900

arm64_cpu_capabilities

static const struct arm64_cpu_capabilities arm64_features = {
	// ...
	{
		.desc = "GICv3 CPU interface",
		.capability = ARM64_HAS_GICV3_CPUIF,
		.type = ARM64_CPUCAP_STRICT_BOOT_CPU_FEATURE,
		.matches = has_useable_gicv3_cpuif,
		ARM64_CPUID_FIELDS(ID_AA64PFR0_EL1, GIC, IMP)
	},
}

static const struct arm64_cpu_capabilities arm64_elf_hwcaps[];

arm64_ftr_regs 是基础,而 arm64_cpu_capabilities 是对于寄存器 结果的解析,相当于是更加结构化的结果。

这些能力会更新到 system_cpucaps 中去,通过调用 .matches 注册的函数 也就是 has_useable_gicv3_cpuif ,基本上都是走向到 read_scoped_sysreg 中

例如 kvm 大量的调用 cpus_have_cap 访问的是 bitmap system_cpucaps

kvm

#define ID_DESC_DEFAULT_CALLBACKS		\
	.access	= access_id_reg,		\
	.get_user = get_id_reg,			\
	.set_user = set_id_reg,			\
	.visibility = id_visibility,		\
	.reset = kvm_read_sanitised_id_reg
@[
        read_sanitised_id_aa64pfr0_el1+0
        set_id_reg+96
        set_id_aa64pfr0_el1+80
        kvm_sys_reg_set_user+148
        kvm_arm_sys_reg_set_reg+196
        kvm_arm_set_reg+692
        kvm_arch_vcpu_ioctl+1540
        kvm_vcpu_ioctl+1404
        __do_sys_ioctl+380
        __arm64_sys_ioctl+40
        invoke_syscall+80
        el0_svc_common.constprop.0+72
        do_el0_svc+36
        el0_svc+60
        el0t_64_sync_handler+288
        el0t_64_sync+420
]: 16

sys_reg_desc::val

./sys_regs.md 中已经谈到过,取决于 sys reg 的类型

struct sys_reg_desc {
	/* Sysreg string for debug */
	// ...
	/* Value (usually reset value), or write mask for idregs */
	u64 val;

kimi : 那些 sys_reg_descs 定义了但是 arm64_ftr_regs 没定义的

有,而且数量还不少。但需要区分两类寄存器:

  1. 非 ID 寄存器:sys_reg_descs[] 里大量的系统寄存器(如 SCTLR_EL1、TTBR0_EL1、定时器、GIC、debug 等)本来就不会出现在 arm64_ftr_regs[] 里,因为后者只关心 CPU feature ID 寄存器。
  2. ID 寄存器:即使只在 ID 寄存器范围内看,也有几个是 sys_reg_descs[] 定义了但 arm64_ftr_regs[] 里没有的。

ID 寄存器里的差异

我用脚本对比了两个表里的 ID 寄存器,结果如下:

在 sys_reg_descs[] 里,但不在 arm64_ftr_regs[] 里的 ID 寄存器:

SYS_ID_AA64AFR0_EL1
SYS_ID_AA64AFR1_EL1
SYS_ID_AFR0_EL1
SYS_LORID_EL1

在 arm64_ftr_regs[] 里,但不在 sys_reg_descs[] 里的:

SYS_DCZID_EL0
SYS_GMID_EL1

为什么这几个 ID 寄存器可以“破例”?

KVM 里的注释说的是:

/*
 * ID regs: all ID_SANITISED() entries here must have corresponding
 * entries in arm64_ftr_regs[].
 */

注意它限定了是 ID_SANITISED() 这种条目,而不是所有 ID 条目。那四个“破例”的寄存器在 KVM 里是这样处理的:

ID_HIDDEN(ID_AFR0_EL1),              /* CRm=1 */
...
ID_HIDDEN(ID_AA64AFR0_EL1),
ID_HIDDEN(ID_AA64AFR1_EL1),          /* CRm=5 */
...
{ SYS_DESC(SYS_LORID_EL1), trap_loregion },

因为它们不需要从 arm64_ftr_regs[] 里取“消毒后”的值,所以不需要在 arm64_ftr_regs[] 里有对应条目。

这一点在 cpufeature.c 的 MRS 模拟代码里也能得到印证:

regp = get_arm64_ftr_reg_nowarn(id);
if (regp)
    *valp = arm64_ftr_reg_user_value(regp);
else
    /*
     * The untracked registers are either IMPLEMENTATION DEFINED
     * (e.g, ID_AFR0_EL1) or reserved RAZ.
     */
    *valp = 0;

也就是说,内核自己对未跟踪的 ID 寄存器也是按 IMPLEMENTATION DEFINED 或 RAZ 处理的。

反过来呢?

SYS_DCZID_EL0 和 SYS_GMID_EL1 在 arm64_ftr_regs[] 里有,但不在 sys_reg_descs[] 里:

总结

宏名(Linux 写法) 寄存器名 作用
SYS_ID_AA64AFR0_EL1 ID_AA64AFR0_EL1 AArch64 辅助特性寄存器 0。内容完全由具体实现定义(IMPLEMENTATION DEFINED),用于暴露芯片自定义的 AArch64 辅助特性。
SYS_ID_AA64AFR1_EL1 ID_AA64AFR1_EL1 AArch64 辅助特性寄存器 1。与 AFR0 类似,也是实现自定义的预留寄存器。
SYS_ID_AFR0_EL1 ID_AFR0_EL1 AArch32 辅助特性寄存器 0。用于暴露 AArch32 状态下的实现自定义特性。
SYS_LORID_EL1 LORID_EL1 有限顺序域 ID 寄存器。报告 Limited Ordering Regions(LOR)支持情况,比如支持多少个 LOR 区域。LOR 用于在某些弱序内存模型下对特定地址范围施加更严格的顺序约束。
SYS_DCZID_EL0 DCZID_EL0 Data Cache Zero ID 寄存器。从 EL0 可读,描述 DC ZVA 指令一次清零的缓存块大小,以及该指令是否允许使用。
SYS_GMID_EL1 GMID_EL1 MTE Granule Maximum ID 寄存器。报告 Memory Tagging Extension(MTE)支持的 tag granule 信息,比如 GCR_EL1 中 PMID/PMEC 字段的最大值。

共同特点:

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。