CPU Capabilities – ARM64

 

CPU Capabilities

 

CPU Capabilities

다음은 ARM64 시스템에서 사용되는 cpu capabilities(caps) 항목들이다.

arch/arm64/include/asm/cpucaps.h

#define ARM64_WORKAROUND_CLEAN_CACHE            0
#define ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE    1
#define ARM64_WORKAROUND_845719                 2
#define ARM64_HAS_SYSREG_GIC_CPUIF              3
#define ARM64_HAS_PAN                           4
#define ARM64_HAS_LSE_ATOMICS                   5
#define ARM64_WORKAROUND_CAVIUM_23154           6
#define ARM64_WORKAROUND_834220                 7
#define ARM64_HAS_NO_HW_PREFETCH                8
#define ARM64_HAS_UAO                           9
#define ARM64_ALT_PAN_NOT_UAO                   10
#define ARM64_HAS_VIRT_HOST_EXTN                11
#define ARM64_WORKAROUND_CAVIUM_27456           12
#define ARM64_HAS_32BIT_EL0                     13
#define ARM64_HARDEN_EL2_VECTORS                14
#define ARM64_HAS_CNP                           15
#define ARM64_HAS_NO_FPSIMD                     16
#define ARM64_WORKAROUND_REPEAT_TLBI            17
#define ARM64_WORKAROUND_QCOM_FALKOR_E1003      18
#define ARM64_WORKAROUND_858921                 19
#define ARM64_WORKAROUND_CAVIUM_30115           20
#define ARM64_HAS_DCPOP                         21
#define ARM64_SVE                               22
#define ARM64_UNMAP_KERNEL_AT_EL0               23
#define ARM64_HARDEN_BRANCH_PREDICTOR           24
#define ARM64_HAS_RAS_EXTN                      25
#define ARM64_WORKAROUND_843419                 26
#define ARM64_HAS_CACHE_IDC                     27
#define ARM64_HAS_CACHE_DIC                     28
#define ARM64_HW_DBM                            29
#define ARM64_SSBD                              30
#define ARM64_MISMATCHED_CACHE_TYPE             31
#define ARM64_HAS_STAGE2_FWB                    32
#define ARM64_HAS_CRC32                         33
#define ARM64_SSBS                              34
#define ARM64_WORKAROUND_1188873                35
#define ARM64_HAS_SB                            36
#define ARM64_WORKAROUND_1165522                37
#define ARM64_HAS_ADDRESS_AUTH_ARCH             38
#define ARM64_HAS_ADDRESS_AUTH_IMP_DEF          39
#define ARM64_HAS_GENERIC_AUTH_ARCH             40
#define ARM64_HAS_GENERIC_AUTH_IMP_DEF          41

#define ARM64_NCAPS                             42

 

다음과 같이 cpu_hwcaps 라는 전역 비트맵을 선언하여 관리한다.

DECLARE_BITMAP(cpu_hwcaps, ARM64_NCAPS);

 

cpus_have_const_cap(id) 인라인 함수를 사용하여 시스템 cpu caps의 동작 유무를 알아온다.

 

CPU Capabilities 등록

 

다음 그림은 4가지 목적의 CPU Capabilities를 보여준다.

 

1) for ARM64 CPU Features

ARM64 시스템의 cpu feature들은 컴파일 타임에 아래와 같이 arm64_features[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

static const struct arm64_cpu_capabilities arm64_features[] = {
        {
                .desc = "GIC system register CPU interface",
                .capability = ARM64_HAS_SYSREG_GIC_CPUIF,
                .type = ARM64_CPUCAP_SYSTEM_FEATURE,
                .matches = has_useable_gicv3_cpuif,
                .sys_reg = SYS_ID_AA64PFR0_EL1,
                .field_pos = ID_AA64PFR0_GIC_SHIFT,
                .sign = FTR_UNSIGNED,
                .min_field_value = 1,
        },

(...생략...)

 

2) for ARM64 CPU ERRATA

ARM64 시스템의 CPU ERRATA들은 컴파일 타임에 아래와 같이 arm64_errata[] 배열에 등록된다.

arch/arm64/kernel/cpu_errata.c

const struct arm64_cpu_capabilities arm64_errata[] = {
#ifdef CONFIG_ARM64_WORKAROUND_CLEAN_CACHE
        {
                .desc = "ARM errata 826319, 827319, 824069, 819472",
                .capability = ARM64_WORKAROUND_CLEAN_CACHE,
                ERRATA_MIDR_RANGE_LIST(workaround_clean_cache),
                .cpu_enable = cpu_enable_cache_maint_trap,
        },
#endif

(...생략...)

 

3) for Authentication

ARM64 시스템의 ARMv8.3 아키텍처에서 소개된 포인터 인증 Features들은 컴파일 타임에 아래와 같이 ptr_auth_hwcap_addr_matches[] 배열 및 ptr_auth_hwcap_gen_matches[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

#ifdef CONFIG_ARM64_PTR_AUTH
static const struct arm64_cpu_capabilities ptr_auth_hwcap_addr_matches[] = {
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_APA_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_APA_ARCHITECTED)
        },
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_API_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_API_IMP_DEF)
        },
        {},
};

static const struct arm64_cpu_capabilities ptr_auth_hwcap_gen_matches[] = {
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_GPA_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_GPA_ARCHITECTED)
        },
        {
                HWCAP_CPUID_MATCH(SYS_ID_AA64ISAR1_EL1, ID_AA64ISAR1_GPI_SHIFT,
                                  FTR_UNSIGNED, ID_AA64ISAR1_GPI_IMP_DEF)
        },
        {},
};
#endif

 

4) for ARM64 elf hwcaps

ARM64 시스템의 elf hwcaps들은 컴파일 타임에 아래와 같이 arm64_elf_hwcaps[] 배열에 등록된다.

arch/arm64/kernel/cpufeature.c

static const struct arm64_cpu_capabilities arm64_elf_hwcaps[] = {
        HWCAP_CAP(SYS_ID_AA64ISAR0_EL1, ID_AA64ISAR0_AES_SHIFT, FTR_UNSIGNED, 2, CAP_HWCAP, HWCAP_PMM
ULL),

(...생략...)

 

arm64_cpu_capabilities 구조체

arch/arm64/include/asm/cpufeature.h

struct arm64_cpu_capabilities {
        const char *desc;
        u16 capability;
        u16 type;
        bool (*matches)(const struct arm64_cpu_capabilities *caps, int scope);
        /*
         * Take the appropriate actions to enable this capability for this CPU.
         * For each successfully booted CPU, this method is called for each
         * globally detected capability.
         */
        void (*cpu_enable)(const struct arm64_cpu_capabilities *cap);
        union {
                struct {        /* To be used for erratum handling only */
                        struct midr_range midr_range;
                        const struct arm64_midr_revidr {
                                u32 midr_rv;            /* revision/variant */
                                u32 revidr_mask;
                        } * const fixed_revs;
                };

                const struct midr_range *midr_range_list;
                struct {        /* Feature register checking */
                        u32 sys_reg;
                        u8 field_pos;
                        u8 min_field_value;
                        u8 hwcap_type;
                        bool sign;
                        unsigned long hwcap;
                };
        };

        /*
         * An optional list of "matches/cpu_enable" pair for the same
         * "capability" of the same "type" as described by the parent.
         * Only matches(), cpu_enable() and fields relevant to these
         * methods are significant in the list. The cpu_enable is
         * invoked only if the corresponding entry "matches()".
         * However, if a cpu_enable() method is associated
         * with multiple matches(), care should be taken that either
         * the match criteria are mutually exclusive, or that the
         * method is robust against being called multiple times.
         */
        const struct arm64_cpu_capabilities *match_list;
};
  • *desc
    • 디스크립션
  • capability
    • capability 번호
  • type
    • 다음 3 비트를 사용하여 해당 feature가 다음 각각의 cpu scope에서 갱신되어야 하는 것을 알려준다.
      • SCOPE_LOCAL_CPU – bits0
      • SCOPE_SYSTEM – bits1
      • SCOPE_BOOT_CPU – bits2
    • 추가 2 비트로 late 상태에서의 동작을 알려준다.
      • ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU – bits4
      • ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU – bits5
  • (*matches)
    • cpu capability를 확인하는 후크 함수가 지정된다.
  • (*cpu_enable)
    • capability를 활성화하는 후크 함수가 지정된다.

유니온 타입 1) for erratum

  • midr_range
    • cpu 모델과, revision 최소 ~ 최대 범위를 표현한다.
  • fixed_revs->midr_rv
  • fixed_revs->revidr_mask
  • 다음 매크로 함수를 통해 등록한다.
    • ERRATA_MIDR_REV()
    • ERRATA_MIDR_RANGE()
    • ERRATA_MIDR_REV_RANGE()
    • MIDR_ALL_VERSIONS()
    • MIDR_RANGE()

유니온 타입 2) for erratum 리스트

  • *midr_range_list
    • 한 개 이상의 erratum 배열을 가리킨다.
    • 다음 매크로 함수를 통해 등록한다.
      • ERRATA_MIDR_RANGE_LIST()

유니온 타입 3) for feature 레지스터

  • sys_reg
    • 시스템 레지스터 id
    • 예) sys_reg(3, 0, 0, 6, 0)
  • field_pos
    • 필드 시작 비트
  • min_field_value
    • 필드 최소 값
  • hwcap_type
    • hwcap 타입
      • CAP_HWCAP(1) – for 64bit EL0
      • CAP_COMPAT_HWCAP(2) – for 32bit EL0 호환
      • CAP_COMPAT_HWCAP2(3) – for 32bit EL0 호환2
  • sign
    • 음수 비트 포함 여부
    • FTR_UNSIGNED(0), FTR_SIGNED(1)
  • hwcap
    • hwcap 플래그 (2^n)
    • fp=1, asimd=2, evtstrm=4, pmull=8, …

Pointer Authentication

  • *match_list
    • HWCAP_MULTI_CAP() 매크로를 통해 등록되는 매치들이다.

 

CPU Capabilities 접근

cpu caps는 커널과 유저에서 다음과 같이 사용된다.

  • 커널
    • 모든 CPU Capabilities의 사용 유무를 cpu_hwcaps를 트래킹하여 사용한다.
  • 유저
    • CPU Capabilities들 중 ELF HWCAPs 항목들은 유저에서 별도의 API를 통해 사용 가능 여부를 알 수 있다.

 


Capabilites Attributes

 

다음 그림은 주요 Capabilites Attributes를 보여준다.

 

 

Scope of Detection

시스템은 CPU ID Feature 레지스터의 필드 값을 체크하거나 CPU 모델을 체크하는 등 몇 가지 체크를 통하여 Capability 유무를 감지한다. 이러한 감지 동작은 CPU capabilities에 등록된 (*matches) 후크 함수를통해 수행한다. 다음 3 가지 scope 타입이 어떻게 수행되는지 알아본다.

  • SCOPE_LOCAL_CPU
    • 모든 cpu를 체크하여 최소 하나 이상 매치되는 경우이다.
    • 이의 의미는 부팅 시 인식한 모든 cpu를 체크한 후 상태를 결정하게 된다. (최대값 적용)
  • SCOPE_SYSTEM
    • 모든 cpu를 검사하고 모든 cpu가 동일한 상태일 때 감지된다.
    • 이것은 시스템이 feature의 상태를 완료((Finalise the state)하고 한 번만 검사를 실행 함을 의미한다.
    • Feature가 cpu id feature 레지스터 중 하나의 필드에 의존하는 경우 cpu feature 레지스터의 삭제 된 값을 사용하여 결정한다. (최소값 적용)
  • SCOPE_BOOT_CPU
    • 기본 부트 cpu에서만 기능을 감지한다.
    • 이 scope은 SMP cpu가 시작되기 전에 조기(early)에 커널에 의해 상태 완료(Finalise the state) 및 기능의 수행을 위한다.

 

다음 그림은 기본 cpu scope과 conflict 상황에서 예외처리되는 옵션을 보여준다.

  • conflict 상황 예외 옵션
    • ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU
      • late cpu로 부터 새로운 feature가 감지될 때 그냥 허용한다.
    • ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU
      • 시스템 feature는 있지만 late cpu에 feature가 감지되지 않을 때 그냥 허용한다.

 

다음 그림은 각 feature에 실제 적용될 때 사용한 복합 cpu scope 들이다.

 


ARM64 ELF hwcaps

HMP(Heterogeneous Multi Processor) 시스템에서 어느 한 cpu의 특정 feature가 없는 경우를 생각해보자. 예를 들어 Floating Pointer 기능이 어느 한 쪽에 없는 경우 FP 기능을 사용하는 유저 Application이 FP가 없는 프로세스에서 수행이 중단될 수 있다. 이러한 경우를 막기 위해서는 시스템 차원에서 해당 기능을 차단해야 한다. 이러한 기능들이 hotplug cpu 시스템 기반에서 동작할 때에는 각 cpu가 late하게 online/offline 상태가 변경될 때마다 갱신되는 cpu caps를 시스템 차원에서 판단하여 feature로 제공될 수 있는지 여부를 체크하여야 한다. 이렇게 체크된 결과는 유저 스페이스 레벨(EL0)에 elf_hwcaps를 액세스하는 API를 통해 CPU Feature의 사용 여부를 알 수 있게 한다.

 

총 32비트로 구성된 HWCAP 플래그이다.

/*
 * HWCAP flags - for elf_hwcap (in kernel) and AT_HWCAP
 */
#define HWCAP_FP                (1 << 0)
#define HWCAP_ASIMD             (1 << 1)
#define HWCAP_EVTSTRM           (1 << 2)
#define HWCAP_AES               (1 << 3)
#define HWCAP_PMULL             (1 << 4)
#define HWCAP_SHA1              (1 << 5)
#define HWCAP_SHA2              (1 << 6)
#define HWCAP_CRC32             (1 << 7)
#define HWCAP_ATOMICS           (1 << 8)
#define HWCAP_FPHP              (1 << 9)
#define HWCAP_ASIMDHP           (1 << 10)
#define HWCAP_CPUID             (1 << 11)
#define HWCAP_ASIMDRDM          (1 << 12)
#define HWCAP_JSCVT             (1 << 13)
#define HWCAP_FCMA              (1 << 14)
#define HWCAP_LRCPC             (1 << 15)
#define HWCAP_DCPOP             (1 << 16)
#define HWCAP_SHA3              (1 << 17)
#define HWCAP_SM3               (1 << 18)
#define HWCAP_SM4               (1 << 19)
#define HWCAP_ASIMDDP           (1 << 20)
#define HWCAP_SHA512            (1 << 21)
#define HWCAP_SVE               (1 << 22)
#define HWCAP_ASIMDFHM          (1 << 23)
#define HWCAP_DIT               (1 << 24)
#define HWCAP_USCAT             (1 << 25)
#define HWCAP_ILRCPC            (1 << 26)
#define HWCAP_FLAGM             (1 << 27)
#define HWCAP_SSBS              (1 << 28)
#define HWCAP_SB                (1 << 29)
#define HWCAP_PACA              (1 << 30)
#define HWCAP_PACG              (1UL << 31)

 

다음과 같은 방법으로 유저 레벨에서 AT_HWCAP 보조 벡터를 사용하여 특정 feature의 사용 가능 여부를 알아낼 수 있다.

bool floating_point_is_present(void)
{
        unsigned long hwcaps = getauxval(AT_HWCAP);
        if (hwcaps & HWCAP_FP)
                return true;

        return false;
}

 

다음 코드는 유저 레벨에서 AT_HWCAP 보조 벡터를 사용하여 HWCAP_CPUID feature의 사용 가능 여부를 알아낸 후 mrs 명령을 통해 각 레지스터를 읽어오는 모습을 보여준다.

#include <asm/hwcap.h>
#include <stdio.h>
#include <sys/auxv.h>

#define get_cpu_ftr(id) ({					\
		unsigned long __val;				\
		asm("mrs %0, "#id : "=r" (__val));		\
		printf("%-20s: 0x%016lx\n", #id, __val);	\
	})

int main(void)
{

	if (!(getauxval(AT_HWCAP) & HWCAP_CPUID)) {
		fputs("CPUID registers unavailable\n", stderr);
		return 1;
	}

	get_cpu_ftr(ID_AA64ISAR0_EL1);
	get_cpu_ftr(ID_AA64ISAR1_EL1);
	get_cpu_ftr(ID_AA64MMFR0_EL1);
	get_cpu_ftr(ID_AA64MMFR1_EL1);
	get_cpu_ftr(ID_AA64PFR0_EL1);
	get_cpu_ftr(ID_AA64PFR1_EL1);
	get_cpu_ftr(ID_AA64DFR0_EL1);
	get_cpu_ftr(ID_AA64DFR1_EL1);

	get_cpu_ftr(MIDR_EL1);
	get_cpu_ftr(MPIDR_EL1);
	get_cpu_ftr(REVIDR_EL1);

#if 0
	/* Unexposed register access causes SIGILL */
	get_cpu_ftr(ID_MMFR0_EL1);
#endif

	return 0;
}

 

다음과 같은 방법으로 cpu별로 features를 파악할 수 있다.

$ cat /proc/cpuinfo
processor       : 0
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0xd03
CPU revision    : 4

 


CPU Features 초기화 및 갱신

다음 그림은 부트업 타임에 cpu feature들이 초기화되고 각 cpu가 on/off 되면서 cpu feature들이 갱신되는 과정을 보여준다.

 

다음 그림은 init_cpu_features() 및 setup_cpu_features() 함수들의 호출 관계를 보여준다.

Boot CPU에서 cpu features 초기화

init_cpu_features()

arch/arm64/kernel/cpufeature.c

void __init init_cpu_features(struct cpuinfo_arm64 *info)
{
        /* Before we start using the tables, make sure it is sorted */
        sort_ftr_regs();

        init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr);
        init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid);
        init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq);
        init_cpu_ftr_reg(SYS_ID_AA64DFR0_EL1, info->reg_id_aa64dfr0);
        init_cpu_ftr_reg(SYS_ID_AA64DFR1_EL1, info->reg_id_aa64dfr1);
        init_cpu_ftr_reg(SYS_ID_AA64ISAR0_EL1, info->reg_id_aa64isar0);
        init_cpu_ftr_reg(SYS_ID_AA64ISAR1_EL1, info->reg_id_aa64isar1);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR0_EL1, info->reg_id_aa64mmfr0);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR1_EL1, info->reg_id_aa64mmfr1);
        init_cpu_ftr_reg(SYS_ID_AA64MMFR2_EL1, info->reg_id_aa64mmfr2);
        init_cpu_ftr_reg(SYS_ID_AA64PFR0_EL1, info->reg_id_aa64pfr0);
        init_cpu_ftr_reg(SYS_ID_AA64PFR1_EL1, info->reg_id_aa64pfr1);
        init_cpu_ftr_reg(SYS_ID_AA64ZFR0_EL1, info->reg_id_aa64zfr0);

        if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {
                init_cpu_ftr_reg(SYS_ID_DFR0_EL1, info->reg_id_dfr0);
                init_cpu_ftr_reg(SYS_ID_ISAR0_EL1, info->reg_id_isar0);
                init_cpu_ftr_reg(SYS_ID_ISAR1_EL1, info->reg_id_isar1);
                init_cpu_ftr_reg(SYS_ID_ISAR2_EL1, info->reg_id_isar2);
                init_cpu_ftr_reg(SYS_ID_ISAR3_EL1, info->reg_id_isar3);
                init_cpu_ftr_reg(SYS_ID_ISAR4_EL1, info->reg_id_isar4);
                init_cpu_ftr_reg(SYS_ID_ISAR5_EL1, info->reg_id_isar5);
                init_cpu_ftr_reg(SYS_ID_MMFR0_EL1, info->reg_id_mmfr0);
                init_cpu_ftr_reg(SYS_ID_MMFR1_EL1, info->reg_id_mmfr1);
                init_cpu_ftr_reg(SYS_ID_MMFR2_EL1, info->reg_id_mmfr2);
                init_cpu_ftr_reg(SYS_ID_MMFR3_EL1, info->reg_id_mmfr3);
                init_cpu_ftr_reg(SYS_ID_PFR0_EL1, info->reg_id_pfr0);
                init_cpu_ftr_reg(SYS_ID_PFR1_EL1, info->reg_id_pfr1);
                init_cpu_ftr_reg(SYS_MVFR0_EL1, info->reg_mvfr0);
                init_cpu_ftr_reg(SYS_MVFR1_EL1, info->reg_mvfr1);
                init_cpu_ftr_reg(SYS_MVFR2_EL1, info->reg_mvfr2);
        }

        if (id_aa64pfr0_sve(info->reg_id_aa64pfr0)) {
                init_cpu_ftr_reg(SYS_ZCR_EL1, info->reg_zcr);
                sve_init_vq_map();
        }

        /*
         * Initialize the indirect array of CPU hwcaps capabilities pointers
         * before we handle the boot CPU below.
         */
        init_cpu_hwcaps_indirect_list();

        /*
         * Detect and enable early CPU capabilities based on the boot CPU,
         * after we have initialised the CPU feature infrastructure.
         */
        setup_boot_cpu_capabilities();
}

부트업 CPU의 시스템 레지스터에서 읽은 정보를 사용하여 cpu feature 레지스터를 초기화하고, boot scope에 해당하는 cpu caps를 활성화시킨다.

 

init_cpu_ftr_reg()

arch/arm64/kernel/cpufeature.c

/*
 * Initialise the CPU feature register from Boot CPU values.
 * Also initiliases the strict_mask for the register.
 * Any bits that are not covered by an arm64_ftr_bits entry are considered
 * RES0 for the system-wide value, and must strictly match.
 */
static void __init init_cpu_ftr_reg(u32 sys_reg, u64 new)
{
        u64 val = 0;
        u64 strict_mask = ~0x0ULL;
        u64 user_mask = 0;
        u64 valid_mask = 0;

        const struct arm64_ftr_bits *ftrp;
        struct arm64_ftr_reg *reg = get_arm64_ftr_reg(sys_reg);

        BUG_ON(!reg);

        for (ftrp  = reg->ftr_bits; ftrp->width; ftrp++) {
                u64 ftr_mask = arm64_ftr_mask(ftrp);
                s64 ftr_new = arm64_ftr_value(ftrp, new);

                val = arm64_ftr_set_value(ftrp, val, ftr_new);

                valid_mask |= ftr_mask;
                if (!ftrp->strict)
                        strict_mask &= ~ftr_mask;
                if (ftrp->visible)
                        user_mask |= ftr_mask;
                else
                        reg->user_val = arm64_ftr_set_value(ftrp,
                                                            reg->user_val,
                                                            ftrp->safe_val);
        }

        val &= valid_mask;

        reg->sys_val = val;
        reg->strict_mask = strict_mask;
        reg->user_mask = user_mask;
}

boot cpu에서 읽은 시스템 레지스터 값으로 요청한 cpu feature 레지스터를 초기화한다.

  • 코드 라인 9에서 인자로 요청한 시스템 레지스터 @sys_reg에 대해 레지스터 관리 값을 읽어온다.
  • 코드 라인 13~17에서 해당 레지스터의 필드들을 순회하며 필드에 적용되는 비트들만을 1로 하는 마스크 값과 타입에 따라 적용할 값을 알아와서 feature 레지스터 값으로 기록한다.
    • 각 타입에 따라 최소 값, 최대값, safe 값으로만 갱신된다.
  • 코드 라인 18에서 필드들이 사용하는 비트들이 1인 마스크 값들을 모두 valid_mask로 모은다.
  • 코드 라인 19~20에서 해당 필드가 strict 체크를 원하지 않는 경우 strict_mask에 해당 필드를 제외시킨다.
  • 코드 라인 21~26에서 해당 필드가 유저 접근을 허용하는 경우 유저 마스크에 필드 마스크들을 포함시킨다. 유저 접근을 허용하지 않는 경우 유저 값으로 safe 값을 더한다.
  • 코드 라인 29~31에서 레지스터에 대한 sys_val 값으로 레지스터에 저장한 값에 valid 마스크를 적용한 값을 저장한다.
  • 코드 라인 32~33에서 strict 마스크와 유저 마스크를 대입한다.

 

다음 그림은 Cache Type Register를 읽어 해당 Feature 레지스터를 초기화하는 모습을 보여준다.

 

init_cpu_hwcaps_indirect_list()

arch/arm64/kernel/cpufeature.c

static void __init init_cpu_hwcaps_indirect_list(void)
{
        init_cpu_hwcaps_indirect_list_from_array(arm64_features);
        init_cpu_hwcaps_indirect_list_from_array(arm64_errata);
}

cpu hwcaps인 두 arm64_features들과 arm64_errata들에 대응하는 간접 포인터를 설정한다.

 

init_cpu_hwcaps_indirect_list_from_array()

arch/arm64/kernel/cpufeature.c

static void __init
init_cpu_hwcaps_indirect_list_from_array(const struct arm64_cpu_capabilities *caps)
{
        for (; caps->matches; caps++) {
                if (WARN(caps->capability >= ARM64_NCAPS,
                        "Invalid capability %d\n", caps->capability))
                        continue;
                if (WARN(cpu_hwcaps_ptrs[caps->capability],
                        "Duplicate entry for capability %d\n",
                        caps->capability))
                        continue;
                cpu_hwcaps_ptrs[caps->capability] = caps;
        }
}

cpu hwcaps인 @caps들에 대응하는 간접 포인터를 설정한다.

 

setup_boot_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

static void __init setup_boot_cpu_capabilities(void)
{
        /* Detect capabilities with either SCOPE_BOOT_CPU or SCOPE_LOCAL_CPU */
        update_cpu_capabilities(SCOPE_BOOT_CPU | SCOPE_LOCAL_CPU);
        /* Enable the SCOPE_BOOT_CPU capabilities alone right away */
        enable_cpu_capabilities(SCOPE_BOOT_CPU);
}

boot cpu 차원의 cap 들을 동작하도록 준비한다.

  • boot 및 local scope의 cpu caps를 갱신하고, boot scope의 caps들의 기능을 enable 한다.

 


각 CPU에서 cpu features 셋업

setup_cpu_features()

arch/arm64/kernel/cpufeature.c

void __init setup_cpu_features(void)
{
        u32 cwg;

        setup_system_capabilities();
        mark_const_caps_ready();
        setup_elf_hwcaps(arm64_elf_hwcaps);

        if (system_supports_32bit_el0())
                setup_elf_hwcaps(compat_elf_hwcaps);

        if (system_uses_ttbr0_pan())
                pr_info("emulated: Privileged Access Never (PAN) using TTBR0_EL1 switching\n");

        sve_setup();
        minsigstksz_setup();

        /* Advertise that we have computed the system capabilities */
        set_sys_caps_initialised();

        /*
         * Check for sane CTR_EL0.CWG value.
         */
        cwg = cache_type_cwg();
        if (!cwg)
                pr_warn("No Cache Writeback Granule information, assuming %d\n",
                        ARCH_DMA_MINALIGN);
}

부트업 cpu를 제외한 secondary cpu 초기화 루틴에서 호출되어 cpu feature를 enable 한다.

  • 코드 라인 5에서 system 차원의 cap 들을 동작하도록 준비한다.
  • 코드 라인 6에서 const caps가 동작할 수 있도록 ready 상태로 설정한다.
  • 코드 라인 7에서 arm64_elf_hwcaps들을 사용하여 elf_hwcap을 설정한다.
  • 코드 라인 9~10에서 32bit 호환을 위해 compat_elf_hwcaps들을 사용하여 compat_elf_hwcap2를 설정한다.
  • 코드 라인 12~13에서 ttbr0_pan 기능을 사용하는 시스템인 경우 이 기능을 사용한다고 정보 출력을 한다.
  • 코드 라인 15에서 SVE(Scalable Vector Extension support) 기능을 지원하는 경우 sve를 설정한다.
  • 코드 라인 16에서 시그널 전달을 위한 최소 스택 사이즈를 설정한다.
  • 코드 라인 19에서 system caps가 초기화되었음을 나타내도록 설정한다.
  • 코드 라인 24~27에서 캐시 타입 레지스터에서 Cache Writeback Granule 값을 읽어 0인 경우 경고 메시지를 출력한다.

 

setup_system_capabilities()

arch/arm64/kernel/cpufeature.c

static void __init setup_system_capabilities(void)
{
        /*
         * We have finalised the system-wide safe feature
         * registers, finalise the capabilities that depend
         * on it. Also enable all the available capabilities,
         * that are not enabled already.
         */
        update_cpu_capabilities(SCOPE_SYSTEM);
        enable_cpu_capabilities(SCOPE_ALL & ~SCOPE_BOOT_CPU);
}

system 차원의 cap 들을 동작하도록 준비한다.

  • system scope의 cpu caps를 갱신하고, non-boot scope의 caps들의 기능을 enable 한다.

 

mark_const_caps_ready()

arch/arm64/kernel/cpufeature.c

static void __init mark_const_caps_ready(void)
{
        static_branch_enable(&arm64_const_caps_ready);
}

const caps가 동작할 수 있도록 ready 상태로 설정한다.

 

setup_elf_hwcaps()

arch/arm64/kernel/cpufeature.c

static void __init setup_elf_hwcaps(const struct arm64_cpu_capabilities *hwcaps)
{
        /* We support emulation of accesses to CPU ID feature registers */
        elf_hwcap |= HWCAP_CPUID;
        for (; hwcaps->matches; hwcaps++)
                if (hwcaps->matches(hwcaps, cpucap_default_scope(hwcaps)))
                        cap_set_elf_hwcap(hwcaps);
}

인자로 요청한 @hwcaps들로 elf hwcap를 설정한다.

 

cap_set_elf_hwcap()

arch/arm64/kernel/cpufeature.c

static void __init cap_set_elf_hwcap(const struct arm64_cpu_capabilities *cap)
{
        switch (cap->hwcap_type) {
        case CAP_HWCAP:
                elf_hwcap |= cap->hwcap;
                break;
#ifdef CONFIG_COMPAT
        case CAP_COMPAT_HWCAP:
                compat_elf_hwcap |= (u32)cap->hwcap;
                break;
        case CAP_COMPAT_HWCAP2:
                compat_elf_hwcap2 |= (u32)cap->hwcap;
                break;
#endif
        default:
                WARN_ON(1);
                break;
        }
}

인자로 요청한 @cap으로 elf hwcap를 설정한다.

  • 타입에 따라 elf_hwcap, compat_elf_hwcap 또는 compat_elf_hwcap2 전역 변수를 설정한다.

 

각 CPU에서 cpu features 갱신

cpu가 online될 때  secondary_start_kernel() -> cpuinfo_store_cpu() 함수 경로를 통해 해당 cpu의 feature들을 갱신한다.

  • 부트 cpu와 새로 online되는 cpu와 기능이 서로 다른 경우에는 경고 메시지를 출력한다.

 

update_cpu_features()

arch/arm64/kernel/cpufeature.c -1/2-

/*
 * Update system wide CPU feature registers with the values from a
 * non-boot CPU. Also performs SANITY checks to make sure that there
 * aren't any insane variations from that of the boot CPU.
 */
void update_cpu_features(int cpu,
                         struct cpuinfo_arm64 *info,
                         struct cpuinfo_arm64 *boot)
{
        int taint = 0;

        /*
         * The kernel can handle differing I-cache policies, but otherwise
         * caches should look identical. Userspace JITs will make use of
         * *minLine.
         */
        taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu,
                                      info->reg_ctr, boot->reg_ctr);

        /*
         * Userspace may perform DC ZVA instructions. Mismatched block sizes
         * could result in too much or too little memory being zeroed if a
         * process is preempted and migrated between CPUs.
         */
        taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu,
                                      info->reg_dczid, boot->reg_dczid);

        /* If different, timekeeping will be broken (especially with KVM) */
        taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu,
                                      info->reg_cntfrq, boot->reg_cntfrq);

        /*
         * The kernel uses self-hosted debug features and expects CPUs to
         * support identical debug features. We presently need CTX_CMPs, WRPs,
         * and BRPs to be identical.
         * ID_AA64DFR1 is currently RES0.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu,
                                      info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu,
                                      info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1);
        /*
         * Even in big.LITTLE, processors should be identical instruction-set
         * wise.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu,
                                      info->reg_id_aa64isar0, boot->reg_id_aa64isar0);
        taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu,
                                      info->reg_id_aa64isar1, boot->reg_id_aa64isar1);

        /*
         * Differing PARange support is fine as long as all peripherals and
         * memory are mapped within the minimum PARange of all CPUs.
         * Linux should not care about secure memory.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu,
                                      info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu,
                                      info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1);
        taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu,
                                      info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2);

 

arch/arm64/kernel/cpufeature.c -2/2-

        /*
         * EL3 is not our concern.
         */
        taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu,
                                      info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0);
        taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu,
                                      info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1);

        taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu,
                                      info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0);

        /*
         * If we have AArch32, we care about 32-bit features for compat.
         * If the system doesn't support AArch32, don't update them.
         */
        if (id_aa64pfr0_32bit_el0(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1)) &&
                id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) {

                taint |= check_update_ftr_reg(SYS_ID_DFR0_EL1, cpu,
                                        info->reg_id_dfr0, boot->reg_id_dfr0);
                taint |= check_update_ftr_reg(SYS_ID_ISAR0_EL1, cpu,
                                        info->reg_id_isar0, boot->reg_id_isar0);
                taint |= check_update_ftr_reg(SYS_ID_ISAR1_EL1, cpu,
                                        info->reg_id_isar1, boot->reg_id_isar1);
                taint |= check_update_ftr_reg(SYS_ID_ISAR2_EL1, cpu,
                                        info->reg_id_isar2, boot->reg_id_isar2);
                taint |= check_update_ftr_reg(SYS_ID_ISAR3_EL1, cpu,
                                        info->reg_id_isar3, boot->reg_id_isar3);
                taint |= check_update_ftr_reg(SYS_ID_ISAR4_EL1, cpu,
                                        info->reg_id_isar4, boot->reg_id_isar4);
                taint |= check_update_ftr_reg(SYS_ID_ISAR5_EL1, cpu,
                                        info->reg_id_isar5, boot->reg_id_isar5);

                /*
                 * Regardless of the value of the AuxReg field, the AIFSR, ADFSR, and
                 * ACTLR formats could differ across CPUs and therefore would have to
                 * be trapped for virtualization anyway.
                 */
                taint |= check_update_ftr_reg(SYS_ID_MMFR0_EL1, cpu,
                                        info->reg_id_mmfr0, boot->reg_id_mmfr0);
                taint |= check_update_ftr_reg(SYS_ID_MMFR1_EL1, cpu,
                                        info->reg_id_mmfr1, boot->reg_id_mmfr1);
                taint |= check_update_ftr_reg(SYS_ID_MMFR2_EL1, cpu,
                                        info->reg_id_mmfr2, boot->reg_id_mmfr2);
                taint |= check_update_ftr_reg(SYS_ID_MMFR3_EL1, cpu,
                                        info->reg_id_mmfr3, boot->reg_id_mmfr3);
                taint |= check_update_ftr_reg(SYS_ID_PFR0_EL1, cpu,
                                        info->reg_id_pfr0, boot->reg_id_pfr0);
                taint |= check_update_ftr_reg(SYS_ID_PFR1_EL1, cpu,
                                        info->reg_id_pfr1, boot->reg_id_pfr1);
                taint |= check_update_ftr_reg(SYS_MVFR0_EL1, cpu,
                                        info->reg_mvfr0, boot->reg_mvfr0);
                taint |= check_update_ftr_reg(SYS_MVFR1_EL1, cpu,
                                        info->reg_mvfr1, boot->reg_mvfr1);
                taint |= check_update_ftr_reg(SYS_MVFR2_EL1, cpu,
                                        info->reg_mvfr2, boot->reg_mvfr2);
        }

        if (id_aa64pfr0_sve(info->reg_id_aa64pfr0)) {
                taint |= check_update_ftr_reg(SYS_ZCR_EL1, cpu,
                                        info->reg_zcr, boot->reg_zcr);

                /* Probe vector lengths, unless we already gave up on SVE */
                if (id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1)) &&
                    !sys_caps_initialised)
                        sve_update_vq_map();
        }

        /*
         * Mismatched CPU features are a recipe for disaster. Don't even
         * pretend to support them.
         */
        if (taint) {
                pr_warn_once("Unsupported CPU feature variation detected.\n");
                add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
        }
}

요청한 @cpu에 대한 cpu 정보 @info로 각 시스템 레지스터들을 갱신한다. 만일 새 cpu의 feature가 boot cpu에 대한 feature 정보와 다른 경우 경고 메시지를 출력한다.

 

id_aa64pfr0_32bit_el0()

arch/arm64/include/asm/cpufeature.h

static inline bool id_aa64pfr0_32bit_el0(u64 pfr0)
{
        u32 val = cpuid_feature_extract_unsigned_field(pfr0, ID_AA64PFR0_EL0_SHIFT);

        return val == ID_AA64PFR0_EL0_32BIT_64BIT;
}

ID_AA64PFR0_EL1 레지스터의 EL0 필드를 읽어 32비트 및 64비트 유저 레벨을 지원하는지 여부를 반환한다.

  • EL0 필드값이 1인 경우 AArch64 상태만 지원하고, 2인 경우 AArch64 및 AArch32 상태를 지원한다.

 

id_aa64pfr0_sve()

arch/arm64/include/asm/cpufeature.h

static inline bool id_aa64pfr0_sve(u64 pfr0)
{
        u32 val = cpuid_feature_extract_unsigned_field(pfr0, ID_AA64PFR0_SVE_SHIFT);

        return val > 0;
}

ID_AA64PFR0_EL1 레지스터의 SVE 필드를 읽어 SVE(Scalable Vector Extension) 기능이 지원되는지 여부를 반환한다.

 

check_update_ftr_reg()

arch/arm64/kernel/cpufeature.c

static int check_update_ftr_reg(u32 sys_id, int cpu, u64 val, u64 boot)
{
        struct arm64_ftr_reg *regp = get_arm64_ftr_reg(sys_id);

        BUG_ON(!regp);
        update_cpu_ftr_reg(regp, val);
        if ((boot & regp->strict_mask) == (val & regp->strict_mask))
                return 0;
        pr_warn("SANITY CHECK: Unexpected variation in %s. Boot CPU: %#016llx, CPU%d: %#016llx\n",
                        regp->name, boot, cpu, val);
        return 1;
}

시스템 레지스터 id에 해당하는 레지스터의 각 필드 값에 필드 타입별로 새 cpu의 @val 값을 반영하여 갱신하고 갱신 유무를 반환한다. 만일 boot cpu의 값과 새 cpu의 값이 다른 경우 1을 반환하고 경고 메시지를 출력한다.

 

update_cpu_ftr_reg()

arch/arm64/kernel/cpufeature.c

static void update_cpu_ftr_reg(struct arm64_ftr_reg *reg, u64 new)
{
        const struct arm64_ftr_bits *ftrp;

        for (ftrp = reg->ftr_bits; ftrp->width; ftrp++) {
                s64 ftr_cur = arm64_ftr_value(ftrp, reg->sys_val);
                s64 ftr_new = arm64_ftr_value(ftrp, new);

                if (ftr_cur == ftr_new)
                        continue;
                /* Find a safe value */
                ftr_new = arm64_ftr_safe_value(ftrp, ftr_new, ftr_cur);
                reg->sys_val = arm64_ftr_set_value(ftrp, reg->sys_val, ftr_new);
        }

}

64비트 feature 레지스터 @reg의 각 필드 값에 필드 타입별로 @new 값을 반영하여 갱신한다.

  • 코드 라인 5~10에서 feature 레지스터의 각 필드를 순회하며 기존 필드 값과 새로 갱신할 필드 값이 동일한 경우 skip 한다.
  • 코드 라인 12~13에서 갱신할 필드 값과 기존 값 중 사용할 값을 알아온 후 해당 필드를 갱신한다.

 


CPU Capabilities 갱신

update_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

static void update_cpu_capabilities(u16 scope_mask)
{
        int i;
        const struct arm64_cpu_capabilities *caps;

        scope_mask &= ARM64_CPUCAP_SCOPE_MASK;
        for (i = 0; i < ARM64_NCAPS; i++) {
                caps = cpu_hwcaps_ptrs[i];
                if (!caps || !(caps->type & scope_mask) ||
                    cpus_have_cap(caps->capability) ||
                    !caps->matches(caps, cpucap_default_scope(caps)))
                        continue;

                if (caps->desc)
                        pr_info("detected: %s\n", caps->desc);
                cpus_set_cap(caps->capability);
        }
}

cpu capabilities를 갱신한다.

  • 코드 라인 7~8에서 cpu caps 수 만큼 순회한다.
  • 코드 라인 9~12에서 다음 항목들은 skip 한다.
    • cpu cap이 없다.
    • scope mask에 포함되지 않은 타입이다.
    • capability가 이미 지정되었다.
    • 디폴트 scope에 매치되지 않는다.
  • 코드 라인 14~16에서 cpu cap의 디스크립션을 출력하고 capabilies 번호에 대한 cpu_hwcaps 비트를 설정한다.

 

cpus_set_cap()

arch/arm64/include/asm/cpufeature.h

static inline void cpus_set_cap(unsigned int num)
{
        if (num >= ARM64_NCAPS) {
                pr_warn("Attempt to set an illegal CPU capability (%d >= %d)\n",
                        num, ARM64_NCAPS);
        } else {
                __set_bit(num, cpu_hwcaps);
        }
}

요청 @num에 대한 cpu_hwcaps 비트를 설정한다.

 

enable_cpu_capabilities()

arch/arm64/kernel/cpufeature.c

/*
 * Run through the enabled capabilities and enable() it on all active
 * CPUs
 */
static void __init enable_cpu_capabilities(u16 scope_mask)
{
        int i;
        const struct arm64_cpu_capabilities *caps;
        bool boot_scope;

        scope_mask &= ARM64_CPUCAP_SCOPE_MASK;
        boot_scope = !!(scope_mask & SCOPE_BOOT_CPU);

        for (i = 0; i < ARM64_NCAPS; i++) {
                unsigned int num;

                caps = cpu_hwcaps_ptrs[i];
                if (!caps || !(caps->type & scope_mask))
                        continue;
                num = caps->capability;
                if (!cpus_have_cap(num))
                        continue;

                /* Ensure cpus_have_const_cap(num) works */
                static_branch_enable(&cpu_hwcap_keys[num]);

                if (boot_scope && caps->cpu_enable)
                        /*
                         * Capabilities with SCOPE_BOOT_CPU scope are finalised
                         * before any secondary CPU boots. Thus, each secondary
                         * will enable the capability as appropriate via
                         * check_local_cpu_capabilities(). The only exception is
                         * the boot CPU, for which the capability must be
                         * enabled here. This approach avoids costly
                         * stop_machine() calls for this case.
                         */
                        caps->cpu_enable(caps);
        }

        /*
         * For all non-boot scope capabilities, use stop_machine()
         * as it schedules the work allowing us to modify PSTATE,
         * instead of on_each_cpu() which uses an IPI, giving us a
         * PSTATE that disappears when we return.
         */
        if (!boot_scope)
                stop_machine(cpu_enable_non_boot_scope_capabilities,
                             NULL, cpu_online_mask);
}

@scope_mask에 포함된 caps들의  기능을 enable 한다.

  • 코드 라인 10~18에서 cpu caps를 순회하며 @scope_mask에 포함되지 않거나 cpu가 지원하지 않는 cap인 경우 skip 한다.
  • 코드 라인 21에서 cpu가 지원하는 cap에 대한 static branch를 enable 한다.
  • 코드 라인 23~33에서 인자로 요청한 @scope_mask가 boot_scope에 포함된 경우 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.
  • 코드 라인 42~44에서 인자로 요청한 @scope_mask가 boot_scope에 포함되지 않은 경우 online cpu들을 잠깐 stop 시킨 후 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.

 

cpu_enable_non_boot_scope_capabilities()

arch/arm64/kernel/cpufeature.c

/*
 * Enable all the available capabilities on this CPU. The capabilities
 * with BOOT_CPU scope are handled separately and hence skipped here.
 */
static int cpu_enable_non_boot_scope_capabilities(void *__unused)
{
        int i;
        u16 non_boot_scope = SCOPE_ALL & ~SCOPE_BOOT_CPU;

        for_each_available_cap(i) {
                const struct arm64_cpu_capabilities *cap = cpu_hwcaps_ptrs[i];

                if (WARN_ON(!cap))
                        continue;

                if (!(cap->type & non_boot_scope))
                        continue;

                if (cap->cpu_enable)
                        cap->cpu_enable(cap);
        }
        return 0;
}

non-boot scope에 해당하는 caps들의  기능을 enable 한다.

  • 코드 라인 6~10에서 모든 유효한 caps를 순회한다.
  • 코드 라인 12~13에서 non-boot scope에 해당하지 않는 caps들을 skip 한다.
  • 코드 라인 15~16에서 해당 cap을 enable하기 위해 등록된 (*cpu_enable) 후크 함수를 호출한다.

 


CPU Feature 관리

CPU Feature 레지스터 트래킹용 자료 구조

arm64_ftr_reg 구조체

arch/arm64/include/asm/cpufeature.h

/*
 * @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;
        const struct arm64_ftr_bits     *ftr_bits;
};
  • *name
    • 레지스터 명
  • strict_mask
    • 모든 cpu의 레지스터 feature 값을 strict_mask 범위에 포함되는지 체크하기 위해 사용된다.
    • 레지스터 값을 갱신 시 strict_mask 범위를 벗어나는 경우 경고 메시지가 출력된다.
    • strict 허용되지 않은 비트를 제외하고 모두 1로 설정된다.
  • user_mask
    • 유저 레벨에서 접근 가능한 필드들에 대한 비트들만 1로 설정한 마스크 비트들이다.
  • sys_val
    • 시스템 접근 시 사용할 레지스터 값이 담긴다.
  • user_val
    • 유저 접근이 불가능한  필드 요청에 대해 반환할 레지스터 값을 관리한다.
  • *ftr_bits
    • 필드에 대한 속성들을 갖는 arm64_ftr_bits 구조체 배열을 가리킨다.

 

arm64_ftr_bits 구조체

arch/arm64/include/asm/cpufeature.h

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 */
};
  •  sign
    • 음수 표현 여부
    • 1=음수 포함, 0=양수만 포함
    • 음수를 표현하는 경우 비트 필드의 msb가 음수 비트가 된다.
  • visible
    • 유저에서 이 비트 필드에 대한 접근 가능 여부를 나타낸다.
    • 1=유저 접근 허용, 0=유저 접근 비허용
  • strict
    • 갱신할 cpu의 feature 레지스터 값이 strict_mask 범위에 포함되어 엄격히 체크해야 하는지 여부를 나타낸다.
    • 1=strict_mask 범위를 사용하고, 범위를 벗어나는 갱신 시 경고 메시지도 출력하고 한다, 0=strict_mask 범위로 체크하지 않는다.
  • type
    • 비트 필드들은 다음 3 가지 타입으로 식별된다.
      •  FTR_EXACT(0)
        • 새로운 값으로 갱신하지 않고, safe 값으로만 갱신되게 한다.
      • FTR_LOWER_SAFE(1)
        • 항상 작은 값으로만 갱신되게 한다.
        • 모든 cpu들 값에서 가장 작은 값을 사용하게 할 때 사용한다.
      • FTR_HIGHER_SAFE(2)
        • 항상 큰 값으로만 갱신되게 한다.
        • 모든 cpu들 값에서 가장 큰 값을 사용하게 할 때 사용한다.
  • shift
    • 레지스터 값에서 이 비트 필드에 도달할 때 시프트 되어야 할 비트 수
  • width
    • 레지스터내에서 사용하는 비트 수
  • safe_val
    • FTR_EXACT 타입을 사용하는 경우 항상 이 값을 사용하여 갱신한다.

 

arm64_ftr_regs[] 배열

컴파일 타임에 arm64용 feature 레지스터들이 배열로 구성된다.

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

        /* Op1 = 0, CRn = 0, CRm = 1 */
        ARM64_FTR_REG(SYS_ID_PFR0_EL1, ftr_id_pfr0),
        ARM64_FTR_REG(SYS_ID_PFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_DFR0_EL1, ftr_id_dfr0),
        ARM64_FTR_REG(SYS_ID_MMFR0_EL1, ftr_id_mmfr0),
        ARM64_FTR_REG(SYS_ID_MMFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_MMFR2_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_MMFR3_EL1, ftr_generic_32bits),

        /* Op1 = 0, CRn = 0, CRm = 2 */
        ARM64_FTR_REG(SYS_ID_ISAR0_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR2_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR3_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR4_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_ID_ISAR5_EL1, ftr_id_isar5),
        ARM64_FTR_REG(SYS_ID_MMFR4_EL1, ftr_id_mmfr4),

        /* Op1 = 0, CRn = 0, CRm = 3 */
        ARM64_FTR_REG(SYS_MVFR0_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_MVFR1_EL1, ftr_generic_32bits),
        ARM64_FTR_REG(SYS_MVFR2_EL1, ftr_mvfr2),

        /* Op1 = 0, CRn = 0, CRm = 4 */
        ARM64_FTR_REG(SYS_ID_AA64PFR0_EL1, ftr_id_aa64pfr0),
        ARM64_FTR_REG(SYS_ID_AA64PFR1_EL1, ftr_id_aa64pfr1),
        ARM64_FTR_REG(SYS_ID_AA64ZFR0_EL1, ftr_raz),

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

        /* Op1 = 0, CRn = 0, CRm = 6 */
        ARM64_FTR_REG(SYS_ID_AA64ISAR0_EL1, ftr_id_aa64isar0),
        ARM64_FTR_REG(SYS_ID_AA64ISAR1_EL1, ftr_id_aa64isar1),

        /* Op1 = 0, CRn = 0, CRm = 7 */
        ARM64_FTR_REG(SYS_ID_AA64MMFR0_EL1, ftr_id_aa64mmfr0),
        ARM64_FTR_REG(SYS_ID_AA64MMFR1_EL1, ftr_id_aa64mmfr1),
        ARM64_FTR_REG(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2),

        /* Op1 = 0, CRn = 1, CRm = 2 */
        ARM64_FTR_REG(SYS_ZCR_EL1, ftr_zcr),

        /* Op1 = 3, CRn = 0, CRm = 0 */
        { SYS_CTR_EL0, &arm64_ftr_reg_ctrel0 },
        ARM64_FTR_REG(SYS_DCZID_EL0, ftr_dczid),

        /* Op1 = 3, CRn = 14, CRm = 0 */
        ARM64_FTR_REG(SYS_CNTFRQ_EL0, ftr_single32),
};

 

ARM64 Feature 레지스터들 중 SYS_CNTFRQ_EL0 하나만을 알아본다.

#define SYS_CTR_EL0                     sys_reg(3, 3, 0, 0, 1)
  • CTR_EL0 레지스터 id는 0x001b_0020

 

sys_reg() 매크로

arch/arm64/include/asm/sysreg.h

/*
 * ARMv8 ARM reserves the following encoding for system registers:
 * (Ref: ARMv8 ARM, Section: "System instruction class encoding overview",
 *  C5.2, version:ARM DDI 0487A.f)
 *      [20-19] : Op0
 *      [18-16] : Op1
 *      [15-12] : CRn
 *      [11-8]  : CRm
 *      [7-5]   : Op2
 */
#define Op0_shift       19
#define Op0_mask        0x3
#define Op1_shift       16
#define Op1_mask        0x7
#define CRn_shift       12
#define CRn_mask        0xf
#define CRm_shift       8
#define CRm_mask        0xf
#define Op2_shift       5
#define Op2_mask        0x7

#define sys_reg(op0, op1, crn, crm, op2) \
        (((op0) << Op0_shift) | ((op1) << Op1_shift) | \
         ((crn) << CRn_shift) | ((crm) << CRm_shift) | \
         ((op2) << Op2_shift))

 

다음 그림은 sys_reg() 매크로 함수의 5개 인자로 구성하는 레지스터 id를 알아본다.

 

arch/arm64/kernel/cpufeature.c

struct arm64_ftr_reg arm64_ftr_reg_ctrel0 = {
        .name           = "SYS_CTR_EL0",
        .ftr_bits       = ftr_ctr
};

컴파일 타임에 Cache Type 레지스터 명과 각 필드에 대한 정보만 담겨있음을 알 수 있다. 나머지 멤버 값들은 부트업 과정에서 설정된다.

 

static const struct arm64_ftr_bits ftr_ctr[] = {
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_EXACT, 31, 1, 1), /* RES1 */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_DIC_SHIFT, 1, 1),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_IDC_SHIFT, 1, 1),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_HIGHER_SAFE, CTR_CWG_SHIFT, 4, 0),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_HIGHER_SAFE, CTR_ERG_SHIFT, 4, 0),
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_DMINLINE_SHIFT, 4, 1),
        /*
         * Linux can handle differing I-cache policies. Userspace JITs will
         * make use of *minLine.
         * If we have differing I-cache policies, report it as the weakest - VIPT.
         */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_EXACT, 14, 2, ICACHE_POLICY_VIPT),       /* LL
1Ip */
        ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, CTR_IMINLINE_SHIFT, 4, 0),
        ARM64_FTR_END,
};

Cache Type 레지스터의 각 필드들에 대한 정보들을 구성한다.

  • 참고: ARM64 시스템 주요 레지스터 | 문c
  • RES1 필드 – bits[31]
    • 모든 cpu에 대해 필드 값은 항상 safe 값인 1이 사용된다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • DIC 필드 – bits[29]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • IDC 필드 – bits[28]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • CWG 필드 – bits[27:24]
    • 모든 cpu에 대해 필드 값은 항상 큰 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • ERG 필드 – bits[23:20]
    • 모든 cpu에 대해 필드 값은 항상 큰 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • DminLine 필드 – bits[19:16]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.
  • L1Ip 필드 – bits[15:14]
    • 모든 cpu에 대해 필드 값은 항상 safe 값인 ICACHE_POLICY_VIPT(2)만 사용된다.
    • 유저 액세스를 허용하고 strict 체크하지 않는다.
  • IminLine 필드 – bits[3:0]
    • 모든 cpu에 대해 필드 값은 항상 작은 값만 허용한다.
    • 유저 액세스를 허용하고 strict 체크한다.

 

CPU Feature 레지스터 트래킹용 API

get_arm64_ftr_reg()

arch/arm64/kernel/cpufeature.c

/*
 * get_arm64_ftr_reg - Lookup a feature register entry using its
 * sys_reg() encoding. With the array arm64_ftr_regs sorted in the
 * ascending order of sys_id , we use binary search to find a matching
 * entry.
 *
 * returns - Upon success,  matching ftr_reg entry for id.
 *         - NULL on failure. It is upto the caller to decide
 *           the impact of a failure.
 */
static struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id)
{
        const struct __ftr_reg_entry *ret;

        ret = bsearch((const void *)(unsigned long)sys_id,
                        arm64_ftr_regs,
                        ARRAY_SIZE(arm64_ftr_regs),
                        sizeof(arm64_ftr_regs[0]),
                        search_cmp_ftr_reg);
        if (ret)
                return ret->reg;
        return NULL;
}

등록된 arm64용 feature 레지스터에서 @sys_id에 해당하는 레지스터를 검색한 후 반환한다.

 

search_cmp_ftr_reg()

arch/arm64/kernel/cpufeature.c

static int search_cmp_ftr_reg(const void *id, const void *regp)
{
        return (int)(unsigned long)id - (int)((const struct __ftr_reg_entry *)regp)->sys_id;
}

@id와 레지스터 @regp의 sys_id를 비교한다. 결과는 다음과 같다.

  • 음수: @id < @regp->sys_id
  • 0: @id == @regp->sys_id
  • 양수: @id > @regp->sys_id

 

CPU feature 레지스터의 필드 접근

arm64_ftr_value()

arch/arm64/include/asm/cpufeature.h

static inline s64 arm64_ftr_value(const struct arm64_ftr_bits *ftrp, u64 val)
{
        return (s64)cpuid_feature_extract_field_width(val, ftrp->shift, ftrp->width, ftrp->sign);
}

64비트 feature 레지스터 값인 @val에서 arm64 feature 비트 @ftrp에 해당하는 비트 필드값으로 반환한다.

 

cpuid_feature_extract_field_width()

arch/arm64/include/asm/cpufeature.h

static inline int __attribute_const__
cpuid_feature_extract_field_width(u64 features, int field, int width, bool sign)
{
        return (sign) ?
                cpuid_feature_extract_signed_field_width(features, field, width) :
                cpuid_feature_extract_unsigned_field_width(features, field, width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호 @sign 여부에 따른 정수 필드값으로 반환한다.

 

cpuid_feature_extract_signed_field_width()

arch/arm64/include/asm/cpufeature.h

static inline int __attribute_const__
cpuid_feature_extract_signed_field_width(u64 features, int field, int width)
{
        return (s64)(features << (64 - width - field)) >> (64 - width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호있는 정수 필드값으로 반환한다.

  • 예) features=0x1234_5678_9abc_def0, field=48, width=4
    • bits[51:48]=0x4 -> 결과값=4
  • 예) features=0x1234_5678_9abc_def0, field=12, width=4
    • bits[15:12]=0xd -> 결과값=0xffff_ffff_ffff_fffd=-3

 

cpuid_feature_extract_unsigned_field_width()

arch/arm64/include/asm/cpufeature.h

static inline unsigned int __attribute_const__
cpuid_feature_extract_unsigned_field_width(u64 features, int field, int width)
{
        return (u64)(features << (64 - width - field)) >> (64 - width);
}

64비트 feature 레지스터 값 @features의 bits[@field+@width-1:@field] 값을 부호없는 정수 필드값으로 반환한다.

  • 예) features=0x1234_5678_9abc_def0, field=12, width=4
    • bits[15:12]=0xd -> 결과값=0xd

 

cpuid_feature_extract_unsigned_field()

arch/arm64/include/asm/cpufeature.h

static inline unsigned int __attribute_const__
cpuid_feature_extract_unsigned_field(u64 features, int field)
{
        return cpuid_feature_extract_unsigned_field_width(features, field, 4);
}

64비트 feature 레지스터 값 @features의 bits[@field+3:@field] 값을 부호없는 정수 필드값으로 반환한다.

 

arm64_ftr_set_value()

arch/arm64/kernel/cpufeature.c

static u64 arm64_ftr_set_value(const struct arm64_ftr_bits *ftrp, s64 reg,
                               s64 ftr_val)
{
        u64 mask = arm64_ftr_mask(ftrp);

        reg &= ~mask;
        reg |= (ftr_val << ftrp->shift) & mask;
        return reg;
}

64비트 feature 레지스터 값 @reg에 ftr_val을 대입할 때 필드 @ftrp에 해당하는 비트들에만 들어가게 제한한다.

 

arm64_ftr_mask()

arch/arm64/include/asm/cpufeature.h

static inline u64 arm64_ftr_mask(const struct arm64_ftr_bits *ftrp)
{
        return (u64)GENMASK(ftrp->shift + ftrp->width - 1, ftrp->shift);
}

Feature 레지스터의 요청한 필드 @ftrp에서 사용할 마스크 값을 반환한다.

  • 예) shift=21, width=19
    • 0x0000_00ff_ffe0_0000

 

참고

 

2 thoughts to “CPU Capabilities – ARM64”

  1. 1. CPU를 cortex-A55로 확인하는 것은 커널의 어디에서 하는지요? 컴파일시에 지정해주는 것인가요?

    2. GIC 컨트롤러도 ARM에서도 여러 버전(?)이 있던데요. (GIC600 , GIC600AE 등)

    이런 것들을 커널이 어떻게 detect 하나요?

    감사합니다.

    1. 안녕하세요?

      1번에 대해 ARM64커널로 한정해 말씀드리면, 잘아시겠지만 ARM cpu의 버전은 다음과 같이 두 개의 분류 체제를 사용합니다.

      1) ARM Cortex 버전
      – A32, A34, A35, A53, A57, A72, A73, A75, A76, A77, A78, A710, X1, X2

      2) ARM 아키텍처 버전
      – v8, v8.1, v8.2, v8.3, v8.4, v8.5, v8.6, v8.7, v9

      그런데 커널에서는 대부분 위의 ARM cortex 버전이나 아키텍처 버전을 확인하여 코드가 진행되지 않습니다.
      다만 수십 가지의 cpu capability를 확인하여 기능을 지원하는 코드가 사용되는 스타일입니다.
      이러한 cpu capability는 우리가 알고 있는 기능(feature)을 말하는 것이고,
      arch/arm64/include/asm/cpucaps.h에서 정의하고 있습니다.

      그리고 각각의 기능들은 ARM64 아키텍처가 제공하는 많은 feature 레지스터를 통해 확인할 수 있습니다.
      참고: https://www.kernel.org/doc/html/latest/arm64/cpu-feature-registers.html

      —-

      2번에 대해 GIC 역시 두 가지 분류 체제를 사용합니다.

      1) GIC IP 버전
      – GIC-390, 400, 500, 600, 600AE, 700

      2) GIC 아키텍처 버전
      – GIC v1, v2, v3, v4, v4.1

      커널 내부에서는 먼저 디바이스 트리를 통해 어떤 GIC 드라이버를 사용할지 결정합니다.

        compatible = “arm,gic-400” < - v1
        compatible = “arm,gic-v2m-frame” < - v2
        compatible = “arm,gic-v3” < - v3
        compatible = “arm,gic-v3-its” < - v3, v4, v4.1

      예)

      gic: interrupt-controller@65210000 {
      compatible = "arm,gic-400";

      gic-v3-its 드라이버의 경우 GIC 일부 레지스터 정보를 읽어 v4, v4.1 버전 확인을 합니다.

      드라이버는 다음과 같이 버전별로 4개가 제공되고 있습니다. v2의 경우 v1 드라이버 코드를 같이 사용하고, v4의 경우도 v3 코드를 같이 사용합니다.

        – drivers/irqchip/irq-gic.c (v1)
        – drivers/irqchip/irq-gic-v2m.c (v2)
        – drivers/irqchip/irq-gic-v3.c (v3)
        – drivers/irqchip/irq-gic-v4.c (v4, v4.1)

      감사합니다.

댓글 남기기