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

 

참고

 

ARM64 시스템 주요 레지스터

<ARMv8.x>

ARM64 시스템 주요 레지스터

캐시

CTR_EL0(Cache Type Register – EL0)

캐시 타입을 알아오는 레지스터이다.

  • DIC
    • Instruction cache invalidation requirements for instruction to data coherence
    • 0=PoU를 위해 명령 캐시의 invalidation이 필요하다.
    • 1=PoU를 위해 명령 캐시의 invalidation이 필요하지 않다.
  • IDC
    • Data cache clean requirements for instruction to data coherence
    • 0=PoU를 위해 데이터 캐시의 clean이 필요하다. 단 다음 조건 제외
      • CLIDR_EL1.LoC == 0 or (CLIDR_EL1.LoUIS == 0 && CLIDR_EL1.LoUU == 0)
    • 1=PoU를 위해 데이터 캐시의 clean이 필요하지 않다.
  • CWG(Cache Writeback Granule)
    • 0~1=CWG 정보를 제공하지 않는다.
    • 2~9=Cache Writeback Granule로 2^n 워드를 초과하지 않는다.
    • 10 이상=reserved
  • ERG(Exclusives Reservation Granule)
    • 0=ERG 정보를 제공하지 않는다.
    • 1~9=Exclusives Reservation Granule로 2^n 워드를 초과하지 않는다.
    • 10 이상=reserved
  •  DminLine
    • 데이터 캐시 라인 사이즈로 2^n 워드
  • L1Ip
    • 레벨 1 명령 캐시 정책
      • 0=VPIPT (ARMv8.2 이상에서 가능)
      • 1=AIVIVT
      • 2=VIPT
      • 3=PIPT
  • IminLine
    • 명령 캐시 라인 사이즈로 2^n 워드

 

예) Cortex-A72, CTR_EL0=0x8444_c004

  • DIC=0
  • IDC=0
  • CWG=4(16 words)
  • ERG=4(16 words)
  • DminLine=4(16 words)
  • L1Ip=3(PIPT)
  • IminLine=4(16 words)

 

CLIDR_EL1(Cache Level ID Register – EL1)

구성된 레벨별 캐시 타입(명령 only, 데이터 only, 명령+데이터 분리, 명령+데이터 통합)을 구분하고 최상위 통합 캐시 레벨과 최상위 캐시 일관성 레벨을 알려주는 레지스터이다.

  • ICB(Inner Cache Boundary)
    • Inner 캐시 영역
    • 예) ICB=2
      • L2 캐시까지 Inner 캐시 영역이다.
  • LoUU(Level of Unification Uniprocessor)
    • Uni 프로세서 시스템에서 최상위 통합 캐시 레벨
    • Uni 프로세서 시스템에서 PoU 개념과 동일하다.
  • LoC(Level of Coherece)
    • 최상위 캐시 일관성 레벨
    • PoC 개념과 동일하다.
  • LoUIS(Level of Inner Shareable)
    • Inner 공유 영역내의 코어에서 최상위 통합 캐시 레벨
    • SMP 시스템에서 PoU 개념과 동일하다.
  • CType1 ~ CType7
    • 캐시 레벨 별 캐시 타입
      • 0=no cache
      • 1=명령 캐시만 존재
      • 2=데이터 캐시만 존재
      • 3=명령 캐시와 데이터 캐시 분리
      • 4=명령 캐시와 데이터 통합 캐시

 

예) Cortex-A72, CIDR_EL1=0xa20_0023

  • ICB=0
  • LoUU=2(L2 레벨)
  • LoC=2(L2 레벨)
  • LoUIS=2(L2 레벨)
  • CT7 ~ CT3=0(L7 ~ L3 캐시 없음)
  • CT2=4(L2 캐시가 통합 캐시)
  • CT1=3(L1 캐시가 분리 캐시)

 

프로세스

MIDR_EL2 (Main ID Register – EL2)

메인 ID를 담은 레지스터이다.

  • Implementer
    • 설계자 코드(ARM, Broadcom, Cavium, DEC, …)
  • Variant
    • variant 번호
  • Architecture
    • ARM 아키텍처 코드
  • PartNum
    • 파트 번호
  • Revision
    • 리비전 번호

 

MPIDR_EL2 (Multiprocessor Affinity Register – EL2)

Multiprocessor affinity 레벨을 담은 레지스터이다.

  • AFF3
    • Affinity level 3
  •  U
    • Uniprocess 시스템 여부를 나타낸다.
    • 0=SMP
    • 1=UP
  • MT
    • 로지컬 PE의 최소 affinity 레벨 .
    • 0=affinity 0이 최대 독립적
    • 1=affinity 0이 매우 밀착 (Multi-Thread)
  • AFF2
    • Affinity level 2
  • AFF1
    • Affinity level 1
  • AFF0
    • Affinity level 0

 


CPU Feature 식별

ID_AA64PFR0_EL1(AArch64 Processor Feature Register 0 – EL1)

AArch64 상태에 구현된 프로세서 feature를 알아오는 레지스터 0번이다.

  • DIT(Data Independent Timing)
    • 0=어떠한 명령도 constant 실행 시간을 보장하지 않는다.
    • 1=명령의 constant 실행 시간을 보장한다.
  • AMU(Activity Monitors Extension)
    • 0=Activity Monitor 미구현
    • 1=Activity Monitors Extension 버전 1 구현
  • MPAM
    • 0=MPAM Extension 미구현
    • 1=MPAM Extension 구현
  • SEL(Secure EL2)
    • 0=Secure EL2 미구현
    • 1=Secure EL2 미구현
  • SVE(Scalable Vector Extension)
    • 0=SVE 미구현
    • 1=SVE 구현
  • RAS
    • 0=RAS Extension 미구현
    • 1=RAS Extension 구현
    • 2=ARMv8.4-RAS 구현
  • GIC
    • 0=GIC를 위한 System 레지스터 인터페이스  미구현
    • 1=GIC를 위한 System 레지스터 인터페이스  구현
  • ASIMD(Advanced SIMD)
    • 0=Advanced SIMD 구현
    • 1=Advanced SIMD 구현 + half-precision 실수 연산 추가
    • 0xf=Advanced SIMD 미구현
  • FP(Floating Point)
    • 0=FP 구현
    • 1=FP 구현 + half-precision 실수 연산 추가
    • 0xf=FP 미구현
  • EL3(Exception Level 3)
    • 0=EL3 미구현
    • 1=AArch64 에서만 EL3 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL3 구현
  • EL2(Exception Level 2)
    • 0=EL2 미구현
    • 1=AArch64 에서만 EL2 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL2 구현
  • EL2(Exception Level 2)
    • 1=AArch64 에서만 EL1 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL1 구현
  • EL0(Exception Level 0)
    • 1=AArch64 에서만 EL0 구현
    • 2=AArch32 및 AArch64 양쪽에서 EL0 구현

 


메모리 모델 기능

ID_AA64MMFR0_EL1 (Memory Model Feature Register 0 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 0 이다.

  • TGran4
    • 4K 페이지 변환 지원 여부
    • 0b0000=지원, 0b1111=미지원
  • TGran64
    • 64K 페이지 변환 지원 여부
    • 0b0000=지원, 0b1111=미지원
  • TGran16
    • 16K 페이지 변환 지원 여부
    • 0b0000=미지원, 0b0001=지원
  • BigEndEL0
    • EL0에서 Mixed 엔디안 지원
    • 0b0000=미지원
    • 0b0001=지원 (SCTLR_EL1.E0E 비트로 엔디안 설정)
  • SNSMem
    • 시큐어와 non-시큐어 메모리 분리 지원 여부
    • 0b0000=미지원
    • 0b0001=지원
  • BigEnd
    • Mixed 엔디안 지원
    • 0b0000=미지원
    • 0b0001=지원 (SCTLR_ELx.E0E 비트로 엔디안 설정)
  • PARange
    • 물리 주소 지원 범위
    • 0b0000=32 bits, 4GB
    • 0b0001=36 bits, 64GB
    • 0b0010=40 bits, 1TB
    • 0b0011=42 bits, 4TB
    • 0b0100=44 bits, 16TB
    • 0b0101=48 bits, 2n56TB
    • 0b0110=52 bits, 4PB (ARMv8.2-LPA 필요)
  • ASIDBits
    • ASID 지원 비트 수
    • 0b0000=8bits
    • 0b0010=16bits

 

ID_AA64MMFR1_EL1 (Memory Model Feature Register 1 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 1 이다.

  • XNX
    • ARMv8.2 Execute Never Control 지원 여부
    • 0=not support
    • 1=support
  • SpecSEI
    • ARMv8.2 RAS 사용시 Speculative 읽기시 SError 발생 지원 여부
    • 0=not support, 1=support
  • PAN
    • ARMv8.1 PAN(Privileged Access Never) 지원 여부
    • 0=not support
    • 1=support (only for ARMv8.1)
    • 2=support 및 AT S1E1RP 및 AT S1E1WP, AT S1E1 명령 지원 (only for ARMv8.2)
  • LO
    • LORegions 지원 여부
    • 0=not support, 1=support
  • HPDS
    • ARMv8.1 변환 테이블에서 HPDS(Hierarchical Permission Disables Support) 기능 지원 여부
    • 0=not support
    • 1=support
    • 2=support 및 마지막 단계 테이블의 bits[62:59]의 하드웨어 할당 가능
  • VH
    • ARM8.1 Virtualization Host Entension 기능 지원 여부
    • 0=not support
    • 1=support (only for ARMv8.1)
  • VMIDBits
    • ARMv8.1 VMID에 사용하는 비트 수
    • 0=8 bits, 2=16 bits
  • HAFDBS
    • ARMv8.1 변환 테이블에서 HAFDBS(Hardware updates to Access Flag and Dirty Bit State) 기능 지원 여부
    • 0=not support
    • 1=access flag 지원
    • 2=access flag 및 dirty state 지원

 

ID_AA64MMFR2_EL1 (Memory Model Feature Register 1 – EL1)

AArch64 상태에서 지원되는 메모리 모델을 알아오는 레지스터 2 이다.

  • BBM
    • ARMv8.4 변환을 위한 블럭 사이즈 변경 시 BBM(Break Before Make) 기능 지원 레벨
    • 0=level 0 support
    • 1=level 1 support
    • 2=level 2 support
  • TTL
    • ARMv8.4 TTL 기능
    • 0=bits[47:44]를 0으로 변환
    • 1=bits[47:44] 변환 없이
  • FWB
    • ARMv8.4 HCR_EL2.FWB 기능 지원 여부
    • 0=not support, 1=support
  • IDS
    • ARMv8.4 ID Space에 접근 시 아래 레지스터 값에 따른 exception 발생
    • 0=ESR_ELx.EC==0
    • 1=ESR_ELx.EC==0x18
  • AT
    • ARMv8.4 비정렬 single-copy atomic 기능 지원 여부
    • 0=not support
    • 1=support (16 byts)
  • ST
    • ARMv8.4 small 변환 테이블 지원
    • 0=TCR_ELx.{T0SZ, T1SZ} 과 VTCR_EL2.T0SZ 필드의 최고 값은 39
    • 1=TCR_ELx.{T0SZ, T1SZ} 과 VTCR_EL2.T0SZ 필드의 최고 값은 48 (단 64K granules시 47)
  • NV
    • ARMv8.4 NV(Nested Virtualization) 지원 여부
    • 0=not support
    • 1=HCR_EL2.{NV, NV1, AT} 지원 (for ARMv8.3)
    • 2=HCR_EL2.{NV, NV1, NV2, AT} 및 VNCR_EL2 지원
  • CCIDX
    • ARMv8.3 CCSIDR_EL1 레지스터 포맷
    • 0=32 비트, 1=64 비트
  • VARange
    • ARMv8.2 큰 가상 주소 지원 여부
    • 0=48 비트, 1=64K granule 시 52 비트, 그 외 48 비트 (for ARMv8.2-LVA)
  • IESB
    • ARMv8.2 SCTLR_ELx.IESB 비트 지원 여부
    • 0=not support, 1=support
  • LSM
    • ARMv8.2 SCTLR_EL{1, 2} 레지스터에서 LSMAOE 및 nTLSMD 필드 지원 여부
    • 0=not support, 1=support
  • UAO
    • ARMv8.2 UAO(User Access Override) 기능 지원 여부
    • 0=not support, 1=support
  • CnP
    • ARMv8.2 CnP(Common Not Private) 변환 기능 지원 여부
    • 0=not support, 1=support

 

MMFR1_EL1 (Memory Model Feature Register 1 – EL1)

AArch32 상태에서 지원되는 메모리 모델을 알아오는 레지스터 1 이다.

  • 참고: | ARM

  • BPred
    • 0=no branch predict
    • 1=다음 상황에서 branch predict 플러시를 요청한다.
      • 주소 변환 스테이지의 활성화/비활성화시
      • 명령어 변경시
      • 새로운 변환 테이블 매핑시
      • TTBR0/1 및 TTBCR 레지스터 변경시
      • ContextID 및 ASID 변경시
    • 2=다음 상황에서 branch predict 플러시를 요청한다. (for ARMv8-A)
      • 주소 변환 스테이지의 활성화/비활성화시
      • 명령어 변경시
      • 새로운 변환 테이블 매핑시
      • 해당 ContextID 및 ASID 변경 없이 TTBR0/1 및 TTBCR 레지스터 변경시
    • 3=명령어 변경시에만 branch predict 플러시를 요청한다. (for ARMv8-A)
    • 4=어떠한 경우에도 branch predict 플러시를 요청하지 않는다. (for ARMv8-A)
  • L1TstCin
    • L1 데이터 캐시 테스트 & 클린
    • 0=none support (only for ARMv8-A)
    • 1=test & clean 지원
    • 2=test & clean, test, clean, invalidate 지원
  • L1Uni
    • L1 통합 캐시 지원
    • 0=none supported (only for ARMv8-A)
    • 1=캐시의 invalidate 지원
    • 2=캐시의 invalidate, clean 및 clean & invalidate 지원
  • L1Hvd
    • L1 하버드 캐시 지원
    • 0=none support (only for ARMv8-A)
    • 1=명령 캐시만 invalidate 지원
    • 2=명령 및 데이터 캐시의 invalidate 지원
    • 3=명령 및 데이터 캐시의 invalidate 및 clean & invalidate 지원
  • L1UniSW
    • L1 통합 캐시의 Set/Way 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean 캐시 지원
    • 2=clean 및 clean & invalidate 지원
    • 3=clean, clean & invalidate 및 invalidate 지원
  • L1HvdSW
    • L1 하버드 캐시의 Set/Way 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean 및 clean & invalidate 데이터 캐시 지원
    • 2=clean, clean & invalidate 및 invalidate 데이터 캐시 지원
    • 3=clean, clean & invalidate 및 invalidate 데이터 캐시 및 명령 캐시 invalidate 지원
  • L1UniVA
    • L1 통합 캐시의 가상 주소(VA)를 사용하여 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean, invalidate 및 clean & invalidate 캐시 지원
    • 2=clean, invalidate 및 clean & invalidate 캐시, 그리고 branch predictor invalidate 지원
  • L1HvdVA
    • L1 하버드 캐시의 가상 주소(VA)를 사용하여 조작 지원
    • 0=none supported (only for ARMv8-A)
    • 1=clean, invalidate 및 clean & invalidate 데이터 캐시, invalidate 명령 캐시 지원
    • 2=clean, invalidate 및 clean & invalidate 데이터 캐시, invalidate 명령 캐시, 그리고 branch predictor invalidate 지원

 


시스템 제어

SCTLR_EL1 (System Control Register – EL1)

EL1 모드를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • EnIA
    • ARMv8.3 APIAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnIB
    • ARMv8.3 APIBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • LSMAOE
    • ARMv8.2-LSMAOC(Load/Store Multiple Atomicity and Ordering Enable) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • nTLSMD
    • ARMv8.2-LSMAOC 기능의 디바이스 메모리 No Trap 활성화 여부
    • 0=disable, fault 발생
    • 1=enable
  • EnDA
    • ARMv8.3 APDAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • UCI
    • EL0에서 EL1 캐시 조작 명령어 수행시 트랩 여부
    • 0=trap
    • 1=no trap
  • EE
    • EL1 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • E0E
    • EL0 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • SPAN
    • ARMv8.1 PAN(Privileged Access Never) 기능 동작시 PSTATE.PAN 동작 여부
    • 0=익셉션 발생시 PSTATE.PAN=1 설정
    • 1=익셉션 발생시 PSTATE.PAN 변경 없다.
  • IESB
    • ARMv8.2-IESB 기능의 Implicit Error Synchronization 이벤트 활성화 여부
    • 0=disable
    • 1=enable
  • WXN
    • 쓰기 권한이 XN(Excute Never) 권한을 내포하는지 여부
    • 0=disable
    • 1=enable
  • SA
    • SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16바이트)
  • nTWE
    • EL0에서 WFE 명령 사용시 EL2로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • nTWI
    • EL0에서 WFI 명령 사용시 EL2로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • UCT
    • EL0에서 CTR_EL0 접근시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • DZE
    • EL0에서 DC ZVA 명령 사용시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • EnDB
    • ARMv8.3 APDBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • I
    • 명령 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • UMA
    • EL0에서 MSR/MRS 명령으로 PSTATE.DAIF 접근시 EL1으로 No 트랩 여부
    • 0=trap
    • 1=no trap
  • SED
    • AArch32 EL0에서 SETEND 명령 disable 여부
    • 0=enable
    • 1=disable, undefined instruction으로 취급
  • ITD
    • AArch32 EL0에서 IT 명령 disable 여부
    • 0=enable
    • 1=disable, undefined instruction으로 취급
  • nAA
    • ARMv8.4-LSE 기능의 Non-Aligned Access 활성화 여부
    • 0=disable, fault 발생
    • 1=enable, fault 발생하지 않는다.
  • CP15BEN
    • EL0에서 CP15 메모리 베리어 활성화 여부
    • 0=disable, undefined instruction으로 취급
    • 1=enable
  • SA0
    • EL0에서 SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16 바이트 미정렬시 exception 발생)
  • SA
    • EL1에서 SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16 바이트 미정렬시 exception 발생)
  • C
    • 데이터 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • A
    • 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable, fault 발생
  • M
    • MMU 활성화 여부
    • 0=disable
    • 1=enable

 

LORC_EL1, LORegion Register (EL1)

LORegion을 활성화하는 레지스터이다.

  • DS
    • ARMv8.1 DS(Descriptor Select)
    • 0~255
  • EN
    • LORegions 기능 활성화 여부
    • 0=disable, 1=enable

 


GIC

ICC_SRE_EL2 (Interrupt Controller System Register Enable Register – EL2)

인터럽트 컨트롤러 시스템 레지스터를 enable하는 레지스터이다.

  • DIB
    • Disable IRQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • DFB
    • Disable FIQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • SRE
    • GIC에 대한 시스템 레지스터(ICC_*) 활성화 여부
    • 0=disable, 메모리 맵드 방식으로 사용해야 하고, ICC_로 시작하는 시스템 레지스터들의 접근시 el1에 트랩된다.
    • 1=enable

 


하이퍼 바이저 관련 레지스터

시스템 설정

SCTLR_EL2 (System Control Register – EL2)

하이퍼 바이저 모드를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • EnIA
    • ARMv8.3 APIAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnIB
    • ARMv8.3 APIBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EnDA
    • ARMv8.3 APDAKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • EE
    • 엔디안 설정
    • 0=리틀 엔디안
    • 1=빅 엔디안
  • IESB
    • ARMv8.2-IESB 기능의 Implicit Error Synchronization 이벤트 활성화 여부
    • 0=disable
    • 1=enable
  • WXN
    • 쓰기 권한이 XN(Excute Never) 권한을 내포하는지 여부
    • 0=disable
    • 1=enable
  • EnDB
    • ARMv8.3 APDBKey_EL1 Key 사용 PA(Pointer Authentification) 기능 활성화 여부
    • 0=disable
    • 1=enable
  • I
    • 명령 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • nAA
    • ARMv8.4-LSE 기능의 Non-Aligned Access 활성화 여부
    • 0=disable, fault 발생
    • 1=enable, fault 발생하지 않는다.
  • SA
    • SP 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable (16바이트)
  • C
    • 데이터 캐시 활성화 여부
    • 0=disable
    • 1=enable
  • A
    • 정렬 체크 활성화 여부
    • 0=disable
    • 1=enable, fault 발생
  • M
    • MMU 활성화 여부
    • 0=disable
    • 1=enable

 

HCR_EL2 (System Control Register – EL2)

하이퍼 바이저를 설정하기 위한 시스템 콘트롤 레지스터이다.

  • TEA
    • 동기 외부 abort exceptin을 EL2로 라우트
    • 0=disable, 1=enable (route to el2)
  • E2H
    • Host 운영체제가 EL2에서 동작하도록 설정
    • 0=disable, 1=enable
  • ID
    • Stage 2에서의 명령 캐시 disable for el1&0
    • 0=no effect
    • 1=effect
  • CD
    • Stage 2에서의 데이터 캐시 disable for el1&0
    • 0=no effect, 1=effect
  • TGE
    • el0 에서 el2로 TGE(Trap General  Exception)
    • 0=no effect, 1=effect
  • AMO
    • 물리 SError 라우팅
  • IMO
    • 물리 IRQ 라우팅
  • FMO
    • 물리 FIQ 라우팅

 

VPIDR_EL2 (Virtualization Processor ID Register – EL2)

Multiprocessor ID를 담은 레지스터이다.

  • MIDR_EL1 참고

 

VMPIDR_EL2 (Virtualization Multiorocessor ID Register – EL2)

Virtualization Multiprocessor ID를 담은 레지스터이다.

  • MPIDR_EL1 참고

 

HSTR_EL2 (Hypervisor System Trap Register – EL2)

EL0&1에서 coprocess(MCR, MRC, MCRR, and MRRC) 접근 명령 사용시 발생되는 el2로의 트랩 제어 레지스터이다.

  • T<n>
    • CP15, C<n>에 접근하는 코프로세서 명령에 대해 el2로 트랩 발생
    • 0=no trap, 1=trap
    • T14와 T4는 RES0

 

CPTR_EL2 (Architectural Feature Trap Register – EL2)

CPACR, CPACR_EL1, 트레이스 펑션, SVE, Advanced SIMD 및 실수 펑션 사용시 el2로 트랩을 제어하는 레지스터이다.

  • TFP
    • SVE, A-SIMD, 실수 펑션 사용시 el2로 트랩 발생
    • 0=no trap, 1=trap
  •  TZ
    • SVE 레지스터를 액세스할 때 el2로 트랩 발생
    • 0=no trap, 1=trap

 

VBAR_EL2, Vector Base Address Register (EL2)

exception 벡터 베이스 주소를 지정하는 레지스터이다.

 


Stage 2 변환 테이블

VTTBR_EL2 (Virtualization Translation Table Base Register – EL2)

stage 2의 페이지 테이블을 가리키는 레지스터이다.

  • VMID[15:8]
    • ARMv8.1-VMID16 지원시 상위 8비트를 기록하여 사용한다.
  • VMID[15:8]
    • 변환 테이블을 위한 VMID
  • BADDR
    • 변환 테이블 물리 주소
  • CnP
    • ARMv8.2-TTCNP 기능이 있는 경우 CnP(Common not Private) 기능 지원 여부
    • 0=변환 테이블의 엔트리는 현재 PE에만 적용한다.
    • 1=변환 테이블의 엔트리는 Inner Share의 다른 PE와 공유한다.

 


하이퍼 바이저 카운터

아키텍처 타이머 및 카운터 레지스터의 사용법은 간단하나 종류가 여러 가지 있다.

 

CNTHCTL_EL2 (Counter-timer Hypervisor Control Register – EL2)

EL0&1 physical 카운터와 타이머에 접근하기 위한 콘트롤 레지스터이다.

  • EL1PTEN
    • HCR_EL2.TGE==0인 경우, EL0&1에서 EL1 물리 타이머 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable
  • EL1PCTEN
    • HCR_EL2.TGE==0인 경우, EL0&1에서 EL1 물리 카운터 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EL0PTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 물리 타이머 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable
  • EL0VTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 가상 타이머 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EVNTI
    • CNTPCT_EL0 카운터의 어떠한 비트(0~15)에서 이벤트가 발생될지 선택한다.
  • ENNTDIR
    • CNTPCT_EL0 카운터의 해당(EVNTI) 비트의 트리거 방향을 제어한다.
    • 0=high edge시 트리거
    • 1=low edge시 트리거
  • EVNTEN
    • CNTPCT_EL0 카운터로부터 이벤트 스트림 발생 여부 제어
    • 0=disable, 1=enable
  • EL0VCTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 가상 카운터 액세스시 EL2로 트랩발생
    • 0=enable, 1=disable
  • EL0PCTEN
    • HCR_EL2.TGE==1인 경우, EL0에서 EL0 물리 카운터 액세스시 EL2로 트랩 발생
    • 0=enable, 1=disable

 

CNTVOFF_EL2 (Counter-timer Hypervisor Control Register – EL2)

물리 카운터(CNTPCT_EL0)에 대한 Virtual Offset을 담고 있는 콘트롤 레지스터이다.

 


하이퍼 바이저 GIC

ICC_SRE_EL2 (Interrupt Controller System Register Enable Register – EL2)

인터럽트 컨트롤러 시스템 레지스터를 enable하는 레지스터이다.

  • Enable
    • 더 낮은 EL에서 ICC_SRE_EL1 접근을 가능하게 할지 여부
    • 0=disable, non-secure EL1에서 ICC_SRE_EL1 접근시 EL2로 트랩 발생
  •  DIB
    • Disable IRQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • DFB
    • Disable FIQ Bypass 기능 활성화 여부
    • 0=enable, 1=disable
  • SRE
    • GIC에 대한 시스템 레지스터(ICC_*) 활성화 여부
    • 0=disable, 메모리 맵드 방식으로 사용해야 하고, ICC_ 및 ICH_로 시작하는 시스템 레지스터들의 접근시 el2에 트랩된다.
    • 1=enable

 

NUMA -1- (ARM64 초기화)

<kernel v5.10>

NUMA -1- (ARM64 초기화)

arm64_numa_init()

arch/arm64/mm/numa.c

/**
 * arm64_numa_init - Initialize NUMA
 *
 * Try each configured NUMA initialization method until one succeeds.  The
 * last fallback is dummy single node config encomapssing whole memory.
 */
void __init arm64_numa_init(void)
{
        if (!numa_off) {
                if (!acpi_disabled && !numa_init(arm64_acpi_numa_init))
                        return;
                if (acpi_disabled && !numa_init(of_numa_init))
                        return;
        }

        numa_init(dummy_numa_init);
}

ARM64 시스템에서 ACPI 테이블 또는 디바이스 트리를 통해 NUMA 시스템 구성을 시도한다. 그러한 구성이 없으면 노드가 한 개인 dummy 누마 초기화를 수행한다.

 

numa_init()

arch/arm64/mm/numa.c

static int __init numa_init(int (*init_func)(void))
{
        int ret;

        nodes_clear(numa_nodes_parsed);
        nodes_clear(node_possible_map);
        nodes_clear(node_online_map);

        ret = numa_alloc_distance();
        if (ret < 0)
                return ret;

        ret = init_func();
        if (ret < 0)
                goto out_free_distance;

        if (nodes_empty(numa_nodes_parsed)) {
                pr_info("No NUMA configuration found\n");
                ret = -EINVAL;
                goto out_free_distance;
        }

        ret = numa_register_nodes();
        if (ret < 0)
                goto out_free_distance;

        setup_node_to_cpumask_map();

        return 0;
out_free_distance:
        numa_free_distance();
        return ret;
}

NUMA 시스템을 초기화한다.

  • 코드 라인 5~7에서 NUMA 상태를 관리하는 비트맵들을 모두 초기화한다.
  • 코드 라인 9~11에서 numa_disatance[] 배열을 필요한 만큼 할당하고 초기 값들을 지정한다.
  • 코드 라인 13~15에서 인자로 받은 함수 @init_func을 실행시켜 설정한다.
  • 코드 라인 17~21에서 노드 정보가 하나도 없는 경우 “No NUMA configuration found” 메시지를 출력하고 빠져나간다.
  • 코드 라인 23~25에서 노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.
  • 코드 라인 27에서 노드 -> cpu 맵을 설정한다.

 

아래와 같이 ACPI 또는 디바이스 트리를 통해 NUMA 설정을 찾을 수 없는 경우 “No NUMA configuration found” 메시지가 출력되는 것을 볼 수 있다. 그리고 fallback되어 dymmy NUMA 초기화 루틴을 통해 “Faking a node at …” 메시지가 출력되는 것도 볼 수 있다.

[    0.000000] NUMA: No NUMA configuration found
[    0.000000] NUMA: Faking a node at [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000] NUMA: NODE_DATA [mem 0x7ddf4840-0x7ddf5fff]
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000040000000-0x000000007fffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000007fffffff]

 

numa_alloc_distance()

arch/arm64/mm/numa.c

static int __init numa_alloc_distance(void)
{
        size_t size;
        u64 phys;
        int i, j;

        size = nr_node_ids * nr_node_ids * sizeof(numa_distance[0]);
        phys = memblock_find_in_range(0, PFN_PHYS(max_pfn),
                                      size, PAGE_SIZE);
        if (WARN_ON(!phys))
                return -ENOMEM;

        memblock_reserve(phys, size);

        numa_distance = __va(phys);
        numa_distance_cnt = nr_node_ids;

        /* fill with the default distances */
        for (i = 0; i < numa_distance_cnt; i++)
                for (j = 0; j < numa_distance_cnt; j++)
                        numa_distance[i * numa_distance_cnt + j] = i == j ?
                                LOCAL_DISTANCE : REMOTE_DISTANCE;

        pr_debug("Initialized distance table, cnt=%d\n", numa_distance_cnt);

        return 0;
}

노드 수 * 노드 수만큼 numa_distance[] 배열을 만들어 할당한다.

  • 코드 라인 7에서 할당할 사이즈로 노드 수 * 노드 수를 사용한다.
  • 코드 라인 8~11에서 memblock의 빈 공간에서 size 만큼의 공간을 페이지 단위로 알아온다.
  • 코드 라인 13에서 size 영역을 memblock에 reserve 한다.
  • 코드 라인 15~16에서 할당한 물리 주소를 가상 주소로 변환하여 numa_distance에 대입하고, 노드 수를 지정한다.
  • 코드 라인 19~22에서 numa_distance[]는 논리적으로 이중 배열을 표현하였다. 디폴트 numa_distance[] 값으로 같은 노드를 의미하는 경우에만 LOCAL_DISTANCE(10)를 지정하고 그 외의 경우는 REMOTE_DISTANCE(20) 값을 지정해둔다.
    • 예) nr_node_ids=2인 경우 numa_distance[]를 알아본다.
      • numa_distance[0] = 10
      • numa_distance[1] = 20
      • numa_distance[2] = 20
      • numa_distance[3] = 10

 

numa_register_nodes()

arch/arm64/mm/numa.c

static int __init numa_register_nodes(void)
{
        int nid;
        struct memblock_region *mblk;

        /* Check that valid nid is set to memblks */
        for_each_mem_region(mblk) {
                int mblk_nid = memblock_get_region_node(mblk);

                if (mblk_nid == NUMA_NO_NODE || mblk_nid >= MAX_NUMNODES) {
                        pr_warn("Warning: invalid memblk node %d [mem %#010Lx-%#010Lx]\n",
                                mblk_nid, mblk->base,
                                mblk->base + mblk->size - 1);
                        return -EINVAL;
                }
        }

        /* Finally register nodes. */
        for_each_node_mask(nid, numa_nodes_parsed) {
                unsigned long start_pfn, end_pfn;

                get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
                setup_node_data(nid, start_pfn, end_pfn);
                node_set_online(nid);
        }

        /* Setup online nodes to actual nodes*/
        node_possible_map = numa_nodes_parsed;

        return 0;
}

노드 데이터(struct pglist_data)를 할당하고 기본 노드 정보를 설정한다.

  • 코드 라인 7~16에서 memblock에 노드가 지정되지 않은 경우 경고 메시지를 출력하고 에러 -EINVAL 값을 반환한다.
  • 코드 라인 19~25에서 노드를 순회하며 노드 데이터( struct pglist_data)를 할당하고 노드의 기본 정보를 설정하고, online 노드 상태로 변경한다.
  • 코드 라인 28에서 online 노드들을 possible 맵에 지정한다.

 

setup_node_to_cpumask_map()

arm64/mm/numa.c

/*
 * Allocate node_to_cpumask_map based on number of available nodes
 * Requires node_possible_map to be valid.
 *
 * Note: cpumask_of_node() is not valid until after this is done.
 * (Use CONFIG_DEBUG_PER_CPU_MAPS to check this.)
 */
static void __init setup_node_to_cpumask_map(void)
{
        int node;

        /* setup nr_node_ids if not done yet */
        if (nr_node_ids == MAX_NUMNODES)
                setup_nr_node_ids();

        /* allocate and clear the mapping */
        for (node = 0; node < nr_node_ids; node++) {
                alloc_bootmem_cpumask_var(&node_to_cpumask_map[node]);
                cpumask_clear(node_to_cpumask_map[node]);
        }

        /* cpumask_of_node() will now work */
        pr_debug("Node to cpumask map for %u nodes\n", nr_node_ids);
}

노드 -> cpu 맵을 설정한다.

  • 코드 라인 6~7에서 nr_node_ids가 아직 설정되지 않은 경우 possible 노드 수로 지정한다.
  • 코드 라인 10~13에서 노드 수만큼 순회하며 node_to_cpumask_map[node] 비트맵을 할당하고 초기화한다.

 

dummy_numa_init()

arch/arm64/mm/numa.c

/**
 * dummy_numa_init - Fallback dummy NUMA init
 *
 * Used if there's no underlying NUMA architecture, NUMA initialization
 * fails, or NUMA is disabled on the command line.
 *
 * Must online at least one node (node 0) and add memory blocks that cover all
 * allowed memory. It is unlikely that this function fails.
 */
static int __init dummy_numa_init(void)
{
        phys_addr_t start = memblock_start_of_DRAM();
        phys_addr_t end = memblock_end_of_DRAM();
        int ret;

        if (numa_off)
                pr_info("NUMA disabled\n"); /* Forced off on command line. */
        pr_info("Faking a node at [mem %#018Lx-%#018Lx]\n", start, end - 1);

        ret = numa_add_memblk(0, start, end);
        if (ret) {
                pr_err("NUMA init failed\n");
                return ret;
        }

        numa_off = true;
        return 0;
}

NUMA 설정이 없는 경우 한 개의 노드로 구성된 dummy NUMA 구성으로 초기화한다.

  • 코드 라인 7~8에서 “numa=off” 커널 파라미터가 지정된 경우 “NUMA disabled” 메시지를 출력한다.
  • 코드 라인 9에서 “Faking a node at …” 메시지 정보를 통해 한 개의 메모리 정보를 출력한다.
  • 코드 라인 11~15에서 memory memblock 모두에 0번 노드를 지정한다.
  • 코드 라인 17에서 NUMA가 disable 되었음을 나타낸다.

 

numa_add_memblk()

arch/arm64/mm/numa.c

/**
 * numa_add_memblk() - Set node id to memblk
 * @nid: NUMA node ID of the new memblk
 * @start: Start address of the new memblk
 * @end:  End address of the new memblk
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
int __init numa_add_memblk(int nid, u64 start, u64 end)
{
        int ret;

        ret = memblock_set_node(start, (end - start), &memblock.memory, nid);
        if (ret < 0) {
                pr_err("memblock [0x%llx - 0x%llx] failed to add on node %d\n",
                        start, (end - 1), nid);
                return ret;
        }

        node_set(nid, numa_nodes_parsed);
        return ret;
}

memblock 영역에 노드 id를 지정하고  numa_nodes_parsed 비트맵에 현재 노드를 설정한다.

 

“numa” Early 커널 파라미터

numa_parse_early_param()

arch/arm64/mm/numa.c

static __init int numa_parse_early_param(char *opt)
{
        if (!opt)
                return -EINVAL;
        if (str_has_prefix(opt, "off"))
                numa_off = true;

        return 0;
}
early_param("numa", numa_parse_early_param);

“numa=off” 커널 파라미터가 지정된 경우 전역 변수 numa_off에 true를 대입한다.

 


디바이스 트리를 통한 NUMA 시스템 초기화

다음과 같이 4개의 NUMA 노드를 가진 시스템 구성을 참고한다.

  • 각  노드마다 4개의 클러스터로 구성하여 총 16개의 클러스터를 사용한다.
  • 각 클러스터마다 4개의 코어를 구성하여 총 64개의 코어를 사용한다.
  • 메모리 노드의 누마 id는 UEFI 펌웨어가 인식하여 전달되므로 아래는 노드 0번 정보만 기록되어 있다.
#include "hip07.dtsi"

/ {
        model = "Hisilicon Hip07 D05 Development Board";
        compatible = "hisilicon,hip07-d05";

        /* the mem node will be updated by UEFI. */
        memory@0 {
                device_type = "memory";
                reg = <0x0 0x00000000 0x0 0x40000000>;
                numa-node-id = <0>;
        };

        distance-map {
                compatible = "numa-distance-map-v1";
                distance-matrix = <0 0 10>,
                                  <0 1 15>,
                                  <0 2 20>,
                                  <0 3 25>,
                                  <1 0 15>,
                                  <1 1 10>,
                                  <1 2 25>,
                                  <1 3 30>,
                                  <2 0 20>,
                                  <2 1 25>,
                                  <2 2 10>,
                                  <2 3 15>,
                                  <3 0 25>,
                                  <3 1 30>,
                                  <3 2 15>,
                                  <3 3 10>;

 

arch/arm64/boot/dts/hisilicon/hip07.dtsi

                cpu-map {
                        cluster0 {
                                core0 {
                                        cpu = <&cpu0>;
                                };
                                core1 {
                                        cpu = <&cpu1>;
                                };
                                core2 {
                                        cpu = <&cpu2>;
                                };
                                core3 {
                                        cpu = <&cpu3>;
                                };
                        };

                        ...

                        cluster15 {
                                core0 {
                                        cpu = <&cpu60>;
                                };
                                core1 {
                                        cpu = <&cpu61>;
                                };
                                core2 {
                                        cpu = <&cpu62>;
                                };
                                core3 {
                                        cpu = <&cpu63>;
                                };
                        };
                };

                cpu0: cpu@10000 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72", "arm,armv8";
                        reg = <0x10000>;
                        enable-method = "psci";
                        next-level-cache = <&cluster0_l2>;
                        numa-node-id = <0>;
                };

                ...

                cpu63: cpu@70303 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72", "arm,armv8";
                        reg = <0x70303>;
                        enable-method = "psci";
                        next-level-cache = <&cluster15_l2>;
                        numa-node-id = <3>;
                };

                cluster0_l2: l2-cache0 {
                        compatible = "cache";
                };

                ...

                cluster15_l2: l2-cache15 {
                        compatible = "cache";
                };
        };

 

다음 그림은 NUMA 시스템의 distance를 보여준다.

 

of_numa_init()

drivers/of/of_numa.c

int __init of_numa_init(void)
{
        int r;

        of_numa_parse_cpu_nodes();
        r = of_numa_parse_memory_nodes();
        if (r)
                return r;
        return of_numa_parse_distance_map();
}

디바이스 트리를 통해 cpu 노드와 memory 노드에서 누마 id를 읽어 설정하고, distance 맵을 파싱해온다.

 

of_numa_parse_cpu_nodes()

drivers/of/of_numa.c

/*
 * Even though we connect cpus to numa domains later in SMP
 * init, we need to know the node ids now for all cpus.
*/
static void __init of_numa_parse_cpu_nodes(void)
{
        u32 nid;
        int r;
        struct device_node *np;

        for_each_of_cpu_node(np) {
                r = of_property_read_u32(np, "numa-node-id", &nid);
                if (r)
                        continue;

                pr_debug("CPU on %u\n", nid);
                if (nid >= MAX_NUMNODES)
                        pr_warn("Node id %u exceeds maximum value\n", nid);
                else
                        node_set(nid, numa_nodes_parsed);
        }
}

cpu 노드 수만큼 순회하며 노드를 파싱한다.

  • “numa-node-id” 속성이 1 이상인 경우 numa_nodes_parsed 비트맵에 노드를 설정한다.

 

of_numa_parse_memory_nodes()

drivers/of/of_numa.c

static int __init of_numa_parse_memory_nodes(void)
{
        struct device_node *np = NULL;
        struct resource rsrc;
        u32 nid;
        int i, r;

        for_each_node_by_type(np, "memory") {
                r = of_property_read_u32(np, "numa-node-id", &nid);
                if (r == -EINVAL)
                        /*
                         * property doesn't exist if -EINVAL, continue
                         * looking for more memory nodes with
                         * "numa-node-id" property
                         */
                        continue;

                if (nid >= MAX_NUMNODES) {
                        pr_warn("Node id %u exceeds maximum value\n", nid);
                        r = -EINVAL;
                }

                for (i = 0; !r && !of_address_to_resource(np, i, &rsrc); i++)
                        r = numa_add_memblk(nid, rsrc.start, rsrc.end + 1);

                if (!i || r) {
                        of_node_put(np);
                        pr_err("bad property in memory node\n");
                        return r ? : -EINVAL;
                }
        }

        return 0;
}

memory 노드 수만큼 순회하며 노드를 파싱하여 memblock에 노드를 기록한다.

  • “numa-node-id” 속성이 있는 메모리 노드인 경우 그 영역의 memblock에 노드 정보를 기록한다.

 

of_numa_parse_distance_map()

drivers/of/of_numa.c

static int __init of_numa_parse_distance_map(void)
{
        int ret = 0;
        struct device_node *np;

        np = of_find_compatible_node(NULL, NULL,
                                     "numa-distance-map-v1");
        if (np)
                ret = of_numa_parse_distance_map_v1(np);

        of_node_put(np);
        return ret;
}

compatible = “numa-distance-map-v1” 속성 값을 가진 노드에서 distance 맵을 파싱하여 온다.

 

of_numa_parse_distance_map_v1()

drivers/of/of_numa.c

static int __init of_numa_parse_distance_map_v1(struct device_node *map)
{
        const __be32 *matrix;
        int entry_count;
        int i;

        pr_info("parsing numa-distance-map-v1\n");

        matrix = of_get_property(map, "distance-matrix", NULL);
        if (!matrix) {
                pr_err("No distance-matrix property in distance-map\n");
                return -EINVAL;
        }

        entry_count = of_property_count_u32_elems(map, "distance-matrix");
        if (entry_count <= 0) {
                pr_err("Invalid distance-matrix\n");
                return -EINVAL;
        }

        for (i = 0; i + 2 < entry_count; i += 3) {
                u32 nodea, nodeb, distance;

                nodea = of_read_number(matrix, 1);
                matrix++;
                nodeb = of_read_number(matrix, 1);
                matrix++;
                distance = of_read_number(matrix, 1);
                matrix++;

                if ((nodea == nodeb && distance != LOCAL_DISTANCE) ||
                    (nodea != nodeb && distance <= LOCAL_DISTANCE)) {
                        pr_err("Invalid distance[node%d -> node%d] = %d\n",
                               nodea, nodeb, distance);
                        return -EINVAL;
                }

                numa_set_distance(nodea, nodeb, distance);

                /* Set default distance of node B->A same as A->B */
                if (nodeb > nodea)
                        numa_set_distance(nodeb, nodea, distance);
        }

        return 0;
}

distance 맵을 파싱하여 온다.

  • “distance-matrix” 속성 값을 읽어 numa_distance[] 배열에 값을 지정한다.

 

numa_set_distance()

arch/arm64/mm/numa.c

/**
 * numa_set_distance() - Set inter node NUMA distance from node to node.
 * @from: the 'from' node to set distance
 * @to: the 'to'  node to set distance
 * @distance: NUMA distance
 *
 * Set the distance from node @from to @to to @distance.
 * If distance table doesn't exist, a warning is printed.
 *
 * If @from or @to is higher than the highest known node or lower than zero
 * or @distance doesn't make sense, the call is ignored.
 *
 */
void __init numa_set_distance(int from, int to, int distance)
{
        if (!numa_distance) {
                pr_warn_once("Warning: distance table not allocated yet\n");
                return;
        }

        if (from >= numa_distance_cnt || to >= numa_distance_cnt ||
                        from < 0 || to < 0) {
                pr_warn_once("Warning: node ids are out of bound, from=%d to=%d distance=%d\n",
                            from, to, distance);
                return;
        }

        if ((u8)distance != distance ||
            (from == to && distance != LOCAL_DISTANCE)) {
                pr_warn_once("Warning: invalid distance parameter, from=%d to=%d distance=%d\n",
                             from, to, distance);
                return;
        }

        numa_distance[from * numa_distance_cnt + to] = distance;
}

numa distace를 설정한다.

  • numa_distance[from * numa_distance_cnt + to] 배열에 distance 값을 지정한다.

 

참고

 

 

bootmem_init() – ARM64

<kernel v5.15>

부트 메모리 초기화

bootmem_init()

arch/arm64/mm/init.c

void __init bootmem_init(void)
{
        unsigned long min, max;

        min = PFN_UP(memblock_start_of_DRAM());
        max = PFN_DOWN(memblock_end_of_DRAM());

        early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);

        max_pfn = max_low_pfn = max;
        min_low_pfn = min;

        arm64_numa_init();

        /*
         * must be done after arm64_numa_init() which calls numa_init() to
         * initialize node_online_map that gets used in hugetlb_cma_reserve()
         * while allocating required CMA size across online nodes.
         */
#if defined(CONFIG_HUGETLB_PAGE) && defined(CONFIG_CMA)
        arm64_hugetlb_cma_reserve();
#endif

        dma_pernuma_cma_reserve();

        kvm_hyp_reserve();

        /*
         * sparse_init() tries to allocate memory from memblock, so must be
         * done after the fixed reservations
         */
        sparse_init();
        zone_sizes_init(min, max);

        /*
         * Reserve the CMA area after arm64_dma_phys_limit was initialised.
         */
        dma_contiguous_reserve(arm64_dma_phys_limit);

        /*
         * request_standard_resources() depends on crashkernel's memory being
         * reserved, so do it here.
         */
        reserve_crashkernel();

        memblock_dump_all();
}

부트 메모리 초기화 루틴에서는 각 노드의 각 존별 초기화를 수행한다.

  • 코드 라인 5~8에서 “memtest=<count>” 커널 파라메터가 주어진 경우 메모리에 대한 패턴 테스트를 <count> 수 만큼 수행한다.
  • 코드 라인 10에서 arm64에는 highmem이 없다. 따라서 highmem 경계를 나타내는 max_low_pfn은 max 값과 동일하다.
  • 코드 라인 11에서 min_low_pfn에 위에서 산출한 min 값을 대입한다.
  • 코드 라인 13에서 NUMA 시스템인 경우 초기화를 수행한다. ACPI 또는 디바이스 트리를 통해 초기화를 수행한다. ACPI 또는 디바이스 트리를 통해 NUMA 초기화를 수행하지 못하는 경우이거나,  “numa=off” 커널 파라메터가 주어진 경우 NUMA disable 상태로 초기화를 수행한다.
  • 코드 라인 21에서 ‘hugetlb_cma=’ 명령을 사용하여 cma 영역에 hugetlb를 지원하도록 한다.
  • 코드 라인 24에서 “cma_pernuma=size” 명령을 사용하여 NUMA 별 cma 영역을 사용할 수 있게 한다.
  • 코드 라인 26에서 kvm 하이퍼 바이저 모듈을 위한 영역을 reserve 한다.
  • 코드 라인 32에서 Sparse memory 모델을 사용하는 시스템을 위해 관리 영역을 할당받고 매핑 초기화한다.
  • 코드 라인 33에서 존별로 메모리 영역을 지정하고 초기화한다.
  • 코드 라인 38에서 dma 사용을 위한 cma 영역을 reserve 한다.
  • 코드 라인 44에서 크래시 상황에서 크래시 커널을 기동하여 크래시 로그를 출력할 수 있도록 크래시커널용 영역을 reserve 한다.
  • 코드 라인 46에서 “memblock=debug” 커널 파라메터가 주어진 경우 memblock 상태를 덤프한다.

 

다음은 4G RAM을 가진 rock960 보드의 memblock 상태를 덤프하여 보여준다.

$ cat /sys/kernel/debug/memblock/memory
   0: 0x0000000000200000..0x00000000f7ffffff
$ cat /sys/kernel/debug/memblock/reserved
   0: 0x0000000002080000..0x00000000033b5fff
   1: 0x00000000ef400000..0x00000000f5dfffff
   2: 0x00000000f5eef000..0x00000000f5f01fff
   3: 0x00000000f6000000..0x00000000f7bfffff
   4: 0x00000000f7df4000..0x00000000f7df4fff
   5: 0x00000000f7df5e00..0x00000000f7df5fff
   6: 0x00000000f7e74000..0x00000000f7f61fff
   7: 0x00000000f7f62600..0x00000000f7f6265f
   8: 0x00000000f7f62680..0x00000000f7f626df
   9: 0x00000000f7f62700..0x00000000f7f6282f
  10: 0x00000000f7f62840..0x00000000f7f62857
  11: 0x00000000f7f62880..0x00000000f7f62887
  12: 0x00000000f7f648c0..0x00000000f7f6492b
  13: 0x00000000f7f64940..0x00000000f7f649ab
  14: 0x00000000f7f649c0..0x00000000f7f64a2b
  15: 0x00000000f7f64a40..0x00000000f7f64a47
  16: 0x00000000f7f64a64..0x00000000f7f64aea
  17: 0x00000000f7f64aec..0x00000000f7f64b1a
  18: 0x00000000f7f64b1c..0x00000000f7f64b4a
  19: 0x00000000f7f64b4c..0x00000000f7f64b7a
  20: 0x00000000f7f64b7c..0x00000000f7f64baa
  21: 0x00000000f7f64bac..0x00000000f7fcdff7
  22: 0x00000000f7fce000..0x00000000f7ffffff

 


 

참고

 

setup_arch() – ARM64

<kernel v5.10>

다음 그림은 ARM64 아키텍처가 setup_arch() 함수내에서 준비하는 과정 중 메모리 할당자 및 매핑 시스템에 대한 전개 과정을 보여준다.

  • 부트 업 순간 memblock_add() 및 memblock_reserve() 등의 API를 사용할 수 있다. 그러나 메모리에 대한 매핑이 완료되지 않은 상태에서는 memblock_alloc() 함수를 사용할 수 없다.
  • paging_init()이 완료된 이후 부터는 early 메모리 할당자인 memblock을 사용하여 메모리 영역 중 빈 영역을 할당할 수 있다.
  • setup_arch() 및 mm_init()이 완료된 이후 부터는 정규 메모리 할당자 들이 사용될 수 있다.

 

setup_arch()

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

void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
        init_mm.start_code = (unsigned long) _text;
        init_mm.end_code   = (unsigned long) _etext;
        init_mm.end_data   = (unsigned long) _edata;
        init_mm.brk        = (unsigned long) _end;

        *cmdline_p = boot_command_line;

        /*
         * If know now we are going to need KPTI then use non-global
         * mappings from the start, avoiding the cost of rewriting
         * everything later.
         */
        arm64_use_ng_mappings = kaslr_requires_kpti();

        early_fixmap_init();
        early_ioremap_init();

        setup_machine_fdt(__fdt_pointer);

        /*
         * Initialise the static keys early as they may be enabled by the
         * cpufeature code and early parameters.
         */
        jump_label_init();
        parse_early_param();

        /*
         * Unmask asynchronous aborts and fiq after bringing up possible
         * earlycon. (Report possible System Errors once we can report this
         * occurred).
         */
        local_daif_restore(DAIF_PROCCTX_NOIRQ);

        /*
         * TTBR0 is only used for the identity mapping at this stage. Make it
         * point to zero page to avoid speculatively fetching new entries.
         */
        cpu_uninstall_idmap();

        xen_early_init();
        efi_init();

        if (!efi_enabled(EFI_BOOT) && ((u64)_text % MIN_KIMG_ALIGN) != 0)
             pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");

        arm64_memblock_init();

        paging_init();

ARM64 아키텍처에 초기화를 진행한다. 주로 커널 및 메모리 매핑과 reserve 영역들을 등록한다.

  • 코드 라인 3~6에서 커널 코드 영역과, 데이터 영역(컴파일 타임에 초기화된 데이터 및 초기화되지 않은 데이터 영역)의 가상 주소를 지정한다.
  • 코드 라인 8에서 boot_command_line을 출력 인자 cmdline_p에 대입한다.
  • 코드 라인 15에서 arm64 아키텍처가 보안을 위해 커널 매핑 위치를 랜더마이즈를 함에 따라 non-global 매핑을 사용하는지 여부를 알아온다.
  • 코드 라인 17에서 fixmap을 사용할 수 있도록 페이지 테이블에 매핑해둔다.
  • 코드 라인 18에서 fixmap 가상 주소 영역을 사용하여 7개 디바이스까지 각 디바이스 당 최대 256K 영역을 지원하는 임시(early) 매핑을 할 수 있도록 준비한다.
  • 코드 라인 20에서 디바이스 트리를 스캔하여 해당 머신에 대한 메모리 정보 및 커멘드 라인 파라메터 정보를 알아온다.
  • 코드 라인 26에서 static 브랜치 사용을 준비한다.
  • 코드 라인 27에서 커멘드 라인 파라메터들 중 early 파라메터에 대한 파싱 및 이에 대응하는 함수를 동작 시킨다.
  • 코드 라인 34에서 현재 cpu의 DAIF 플래그를 복구한다.
    • irq는 현재 시점에서 허용하지 않지만, asynchronous aborts와 fiq는 earlycon 이후로 동작될 수도 있다.
  • 코드 라인 40에서 유저용 페이지 테이블을 가리키는 ttbr0는 부트 업 현재 시점까지 1:1 매핑 영역 페이지 테이블에 연결되어 사용되었다. 여기에서는 현재 cpu가 이러한 1:1 identity 매핑 테이블을 사용하지 않게 한다.
  • 코드 라인 42에서 ARM64의 경우 xen 하이퍼바이저를 위해 따로 준비한 셋업 코드가 없다.
  • 코드 라인 43에서 최근 ARM64 서버에서 EFI 펌웨어를 지원한다. 이의 사용을 위해 준비한다. 서버를 제외한 임베디드에서는 대부분 지원하지 않는다.
  • 코드 라인 48에서 memblock 영역에 reserve할 영역을 등록해둔다.
  • 코드 라인 50에서 커널 영역과 메모리 영역을 모두 정규 매핑한다.

 

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

        acpi_table_upgrade();

        /* Parse the ACPI tables for possible boot-time configuration */
        acpi_boot_table_init();

        if (acpi_disabled)
                unflatten_device_tree();

        bootmem_init();

        kasan_init();

        request_standard_resources();

        early_ioremap_reset();

        if (acpi_disabled)
                psci_dt_init();
        else
                psci_acpi_init();

        init_bootcpu_ops();
        smp_init_cpus();
        smp_build_mpidr_hash();

        /* Init percpu seeds for random tags after cpus are set up. */
        kasan_init_tags();

#ifdef CONFIG_ARM64_SW_TTBR0_PAN
        /*
         * Make sure init_thread_info.ttbr0 always generates translation
         * faults in case uaccess_enable() is inadvertently called by the init
         * thread.
         */
        init_task.thread_info.ttbr0 = __pa_symbol(empty_zero_page);
#endif

        if (boot_args[1] || boot_args[2] || boot_args[3]) {
                pr_err("WARNING: x1-x3 nonzero in violation of boot protocol:\n"
                        "\tx1: %016llx\n\tx2: %016llx\n\tx3: %016llx\n"
                        "This indicates a broken bootloader or old kernel\n",
                        boot_args[1], boot_args[2], boot_args[3]);
        }
}
  • 코드 라인 1~4에서 서버 등에서 받은 ACPI 테이블 데이터를 조회하여 관련 메모리 영역 등을 memblock에 reserve 한다.
  • 코드 라인 6~7에서 ACPI를 사용하지 않는 시스템의 경우 DTB(FDT)형태로 로드된 디바이스 트리를 객체 형태로 바꾼다.
    • 객체 형태로 바꾸기 전에는 fdt_*() API를 사용하고, 바꾼 후에는 of_*() API도 사용할 수 있다.
  • 코드 라인 9에서 zone 영역을 설정하고, 초기화한다.
  • 코드 라인 11에서 커널 메모리 디버그용 KASAN(Kernel Address SANitizer)을 준비한다.
  • 코드 라인 13에서 memblock에 등록된 reserve 영역들과 메모리 영역을 리소스에 등록해둔다.
  • 코드 라인 15에서 정규 페이징이 동작하는 경우 fixmap을 사용한 early_ioremap()을 사용할 필요가 없으므로 after_paging_init 전역 변수에 1을 대입해둔다.
    • ARM64 아키텍처의 경우에는 정규 페이징이 동작하더라도 early_ioremap()의 사용을 허락한다.
  • 코드 라인 17~20에서 디바이스 트리 또는 ACPI 정보를 통해 절전 관련 ops를 초기화한다.
  • 코드 라인 22에서 디바이스 트리 또는 ACPI 정보를 통해 “enable-method”가 지정된 경우 cpu online/offline을 관리할 ops를 초기화한다.
  • 코드 라인 23에서 디바이스 트리 또는 ACPI 정보를 통해 cpu 노드 정보를 읽어온 후 cpu의 셋업 코드를 수행한다.
  • 코드 라인 24에서 mpidr 레지스터로부터 mpidr 해시를 생성해둔다.
    • cpu의 suspend 진입 시 어셈블리 코드에서 빠르게 이 해시를 사용하여 cpu 로지컬 id를 분류한 후 cpu 레지스터 등의 context 정보를 저장할 때 사용한다.
  • 코드 라인 27에서 KASAN에서 사용할 per-cpu prng_state를 초기화한다.
  • 코드 라인 29~36에서 보안 목적으로 커널이 ttbr0를 통해 유저 영역의 메모리에 직접 접근하지 못하게한다. 이 때 sw 적인 방법을 사용하는데 ttbr0 멤버에 zero 페이지를 연결하여 유저 액세스를 금지 상태로 변경한다.
  • 코드 라인 38~43에서 처음 커널의 head.S에 진입 시 boot_args[]에 저장해둔 x0~x3 레지스터 값들 중 x1 ~ x3 레지스터 값에 0이 아닌 값이 있는 경우 에러 메시지를 출력한다.