[커널 17차] 60주차

2021.10.24 01:31

ㅇㅇㅇ 조회 수:97

init_cpu_features() 계속
- init_cpu_hwcaps_indirect_list() 함수로 boot cpu capability feature enable을 위한 준비 작업을 수행한다
- setup_boot_cpu_capabilities() 함수로 boot cpu capability feature를 enable 한다

 

__cpuinfo_store_cpu()
- read_cpuid() 함수를 쓰는 경우는 assembler가 시스템 레지스터 이름을 모르는 경우임
- 이 경우는 시스템 레지스터 이름과 MRS/MSR 명령어를 opcode로 만들어서 치환한다 (MRS_S/MSR_S 매크로 이용)
- 나머지는 assembler가 시스템 레지스터 이름을 인식할 수 있는 경우이고 이 경우엔 MRS/MSR 명령어를 직접 사용한다

 

sve_probe_vqs()
- vq 루프를 16부터 1로 줄여가면서 도는 것이 직관적임
- 그러나 새로 구한 vector length를 다시 vq로 만들어서 루프에 넣어줌으로써 필요없는 루프를 건너 뛰고 있다
- 예를 들어, 최대 vector length가 4 (= 4 x 128bit = 512bit)인 경우, vq가 처음에는 512에서 시작했으나 두 번째 루프에서는 4로 확 줄어든다 (skip intervening lengths)
- vector length는 다음과 같은 아키텍처 제약이 있음
1) 2의 power 길이 지원
2) 128의 배수 지원

- https://developer.arm.com/documentation/102476/0001/SVE-architecture-fundamentals
- https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/ZCR-EL1--SVE-Control-Register--EL1-

 

arm64_cpu_capabilities 구조체
- arm64 cpu의 capability를 나타내는 구조체
- description : 이름
- capability : capability index 값
- type : SCOPE_SYSTEM, SCOPE_LOCAL_CPU, SCOPE_BOOT_CPU 여부를 표시
- matches : 함수 포인터로 현재 cpu나 linux kernel 설정에서 해당 capability가 존재하는지 체크해서 리턴해주는 함수
- sys_reg : capability 관련된 cpu register opcode
- field_pos : cpu register 내에서 해당 feature = capability의 bit field 위치 (shift 값)
- sign : feature 값이 signed 인지 unsigned 인지 표시
- min_field_value : capability 값이 enable로 인식되는 최소 값
- cpu_enable : 함수 포인터로 해당 cpu 또는 kernel에서 현재 capability를 enable시켜주는 함수
- cpu_enable 함수 포인터는 capability 별로 다르나 matches 함수는 has_cpuid_feature() 함수를 사용하는 경우가 많다


전역 변수 arm64_feature[] 배열
- arm64_cpu_capabilities 구조체 배열
- arm64 cpu에 존재하는 feature들을 arm64_capabilities 구조체로 만들어서 배열로 정리한 것
- GIC, PAN, LSE atomic operation, no HW PRFM, UAO, VHE, 32bit EL0, 32bit EL1, KPTI, FPSIMD, SVE, RAS, CNP, SB, PAC, E0PD, IRQ_PRIO_MASK, BTI 등 다양한 capability가 존재함


전역 변수 arm64_errata[] 배열
- arm64_cpu_capabilities 구조체 배열
- 현재까지 존재하는 arm64 CPU 관련 HW ERRATUM 정보를 arm64_capabilities 구조체로 만들어서 배열로 정리한 것
- arm64_capabilities 구조체에 union으로 HW ERRATUM 정보를 저장할 수 있다


전역 변수 cpu_hwcaps_ptrs[] 배열
- arm64_cpu_capabilities 구조체 배열
- 현재 cpu 또는 kernel에 존재하는 cpu capability를 엔트리로 가지는 배열
- 함수 init_cpu_hwcaps_indirect_list()가 엔트리를 채워 준다


전역 변수 cpu_hwcaps 비트맵
- CPU에 capability가 존재하는지 나타내는 비트맵
- 각 비트 인덱스는 capability 값임


전역 변수 boot_capabilities 비트맵
- Booting CPU에 capability가 존재하는지 나타내는 비트맵
- 각 비트 인덱스는 capability 값임


init_cpu_hwcaps_indirect_list()
- 함수 init_cpu_hwcaps_indirect_list_from_array()를 이용하여 전역 변수 cpu_hwcaps_ptrs[] 배열에 현재 시스템에 존재하는 arm64_cpu_capabilities를 채워준다. 이 때 errata 정보도 함께 채워준다.
- arm64_feature[] / arm64_errata[] 배열을 사용한다


init_cpu_hwcaps_indirect_list_from_array()
- 인자로 arm64_capabilities 구조체 배열을 받는다
- 각 arm64_capabilities 구조체에 대해서 루프를 돌면서 matches() 함수를 수행하여 현재 cpu 또는 kernel에서 해당 capability가 존재하는 지 확인하고 존재하면 전역 변수 cpu_hwcaps_ptrs[] 배열에 해당 arm64_capabilities 구조체를 채운다
- 배열 index로는 arm64_capabilities 구조체의 capability를 사용한다


has_cpuid_feature()
- 인자로 arm64_capabilities 구조체와 scope를 받는다
- scope는 SCOPE_SYSTEM, SCOPE_LOCAL_CPU, SCOPE_BOOT_CPU가 있음
- SCOPE_SYSTEM : kernel에서 관리하는 capability. 함수 read_sanitised_ftr_reg()를 통해서 search하여 feature 관련 register 값을 읽어온다
- SCOPE_LOCAL_CPU/SCOPE_BOOT_CPU : cpu에 존재하는 feature로 함수 __read_sysreg_by_encoding()를 이용하여 feature와 관련된 register 값을 읽어온다
- 이후 함수 feature_matches()로 search한 feature 값이 minimum field value가 넘는지 확인하고 리턴한다

 

read_sanitised_ftr_reg()
- register ID를 인자로 받아서 함수 get_arm64_ftr_reg()로 전역 변수 배열 arm64_ftr_regs[]에서 세팅된 레지스터 값을 읽어서 리턴한다
- 실제 하드웨어 cpu의 레지스터 값이 아님

 

__read_sysreg_by_encoding()
- register ID를 인자로 받아서 실제 하드웨어 cpu의 레지스터 값을 읽어서 리턴한다


setup_boot_cpu_capabilities()
- update_cpu_capabilities() 함수로 non system (boot/local cpu) capability에 대한 bitmap cpu_hwcaps/boot_capabilities를 만들어 준다
- enable_cpu_capabilities()로 boot cpu capability들을 enable 시켜준다


update_cpu_capabilities()
- 인자로 capability type 또는 scope를 받는다 (system/local cpu/boot cpu)
- 각 capability에 대해서 루프를 돌면서 다음을 수행한다
1) capability가 NULL이거나 capability의 type이 scope 인자에 안맞거나 이미 비트맵 cpu_hwcaps에 set 되어있는 capability이거나 matches() 함수에 의해 match되지 않으면 continue
2) capability의 description이 있으면 커널 로그에 해당 capability가 detect되었음을 알려준다
3) 비트맵 cpu_hwcaps에 해당 capability를 set 해준다
4) 인자 scope가 boot cpu이고 capability의 type도 boot cpu이면 비트맵 boot_capabilities에도 해당 capability를 set 해준다


전역 변수 static_key 배열 cpu_hwcap_keys[]
- DEFINE_STATIC_KEY_ARRAY_FALSE() 매크로로 false로 초기화
- 배열 크기는 ARM64_NCAPS


enable_cpu_capabilities()
- 인자로 capability type 또는 scope를 받는다 (system/local cpu/boot cpu)
- 각 capability에 대해서 루프를 돌면서 다음을 수행한다
1) capability가 NULL이거나 capability의 type이 scope 인자에 안맞거나 비트맵 cpu_hwcaps에 clear 되어있으면 continue
2) static key 배열 cpu_hwcap_keys에 대해서 함수 static_branch_enable()로 해당 capability에 대해서 static key를 enable해준다
3) 인자로 들어온 scope이 boot cpu이고 현재 capability에 대해서 cpu_enable() 함수가 있으면 함수를 수행하여 capability를 enable한다
- 인자로 들어온 scope이 boot cpu가 아니면 stop_machine() 함수를 이용해서 cpu_enable_non_boot_scope_capabilities() 함수를 부르고 boot cpu scope가 아닌 capability들을 enable 시킨다


stop_machine()
- cpu_read_lock()을 건다
- stop_machine_cpuslocked()을 수행하여 요청된 함수를 수행한다
- cpus_read_unlock()로 lock을 풀고 수행 결과를 리턴한다


stop_machine_cpuslocked()
- stop_machine_initialized가 false인 경우만 진행
- IRQ를 disable하고 요청된 함수를 수행한다
- 이 때 hard_irq_disable() 함수로 IRQ disable이 즉시 수행되도록 강제한다 (lazy enabling/disabling 방지)
- hard_irq_disable()은 그냥 do { } while(0) 으로 정의되어 있다 —> arm64는 lazy enabling/disabling 안하는 듯
- IRQ를 enable하고 함수 결과를 리턴한다

- 원래는 on_each_cpu()를 이용해서 IPI로 cpufeature를 enable()하였으나 feature가 PSTATE에 있는 경우, IPI에서 리턴하면서 PSTATE가 기존값으로 원복되어 원하는 feature가 enable()되지 않는 경우가 발생
- 따라서 on_each_cpu() 대신 stop_machine()을 사용했다고 함
- https://lists.infradead.org/pipermail/linux-arm-kernel/2016-October/461807.html


cpu_enable_non_boot_scope_capabilities()
- 매크로 for_each_available_cap()로 비트맵 cpu_hwcaps에 set되어 있는 capability에 대해 루프를 돌면서 boot cpu scope가 아닌 capability를 enable한다


static key 내용
- http://jake.dothome.co.kr/static-keys/
- http://jake.dothome.co.kr/jump_label_init/


static_branch_enable() 매크로
- static_key_enable() 함수를 불러서 인자로 들어온 static key를 enable한다


static_key_enable()
- cpu_read_lock()을 건다
- 함수 static_key_enable_cpuslocked()로 인자 static key를 enable한다
- cpus_read_unlock()로 lock을 풀고 리턴한다


static_key_enable_cpuslocked()
- 인자로 static key를 받아서 enable한다

- STATIC_KEY_CHECK_USE() 매크로로 해당 static key가 초기화되었음을 확인한다
- 이미 enable되어 있는 경우 그냥 리턴한다
- jump_label_lock() 함수로 jump_label_mutex를 잡는다
- key->enabled = 0이면 아래 두 줄을 수행한다
- 일단 key->enabled를 -1로 잡고 함수 jump_label_update()로 jump label을 업데이트한다
- 이후 key->enabled를 1로 업데이트한다
- jump_label_unlock() 함수로 jump_label_mutex를 푼다


jump_label_update()
- 일단 모듈이 아닌 경우만 해석
- static key의 entry를 가져온다
- entry가 valid하면 함수 __jump_label_update()를 이용해서 static key와 관련된 entry들을 업데이트한다. 이 때 인자로 system state가 init인지 아닌지 알려준다 (SYSTEM_BOOTING 또는 SYSTEM_SCHEDULING이면 init이고 그 외에는 init이 아님)

 

__jump_label_update()
- static key, entry, stop, init을 인자로 받는다
- entry는 현재 static key와 관련된 jump label entry이고 stop은 __stop___jump_table 이다.
- init은 현재 system state가 초기화 상태인지 알려준다
- entry 주소가 jump table 내에 존재하고, 현재 entry가 지금 static key에 대응된다는 조건 하에서 entry에 대해 루프를 돌면서 아래를 수행한다
- 각 entry에 대해 함수 jump_label_can_update()로 entry를 업데이트할 수 있는지 체크하고 가능하면 함수 arch_jump_label_transform()로 entry를 업데이트한다
- 함수 arch_jump_label_transform()에 인자로 entry와 함께 함수 jump_label_type()로 entry의 type을 넘겨준다

 

jump_label_can_update()
- 인자로 jump label entry와 현재 시스템이 init인지를 받아서 entry의 update가 가능한지 리턴한다
- 현재 시스템이 init이 아니거나 현재 entry가 init text 영역에 있으면 false 리턴
- entry의 code 주소가 커널 영역에 있지 않으면 false 리턴
- 그 외의 경우에는 true 리턴

 

kernel_text_address()
- 주소값을 인자로 받아서 kernel text 영역에 있으면 1, 아니면 0을 리턴
- 함수 core_kernel_text()로 core kernel text 영역에 있으면 1을 리턴
- 기타 module text, trampoline 영역 등에 있어도 1을 리턴
- 나머지 경우는 0을 리턴한다

 

jump_label_type()
- 인자로 entry를 받는다
- entry의 static key로부터 enabled 및 likely 여부를 받아서 jump/nop 여부를 리턴한다
- enabled ^ likely = jump(1)/nop(0) 이 된다

 

jump_entry_is_branch()
- jump entry를 인자로 받아서 entry의 static key가 likely인지 unlikely인지를 리턴한다
- 1이 likely, 0이 unlikely

 

arch_jump_label_transform()
- 인자로 jump label entry와 instruction type (jump / nop)을 받아서 jump label을 업데이트한다
- instruction type이 jump이면 함수 aarch64_insn_gen_branch_imm()로 jump instruction을 생성한다
- 아니면 함수 aarch64_insn_gen_nop()로 nop instruction을 생성한다
- 함수 aarch64_insn_patch_text_nosync()로 생성된 instruction을 entry code 주소에 패치한다

 

aarch64_insn_gen_branch_imm()
- 인자로 branch 명령의 pc, target의 address, 그리고 branch type (no link/link/return/compare zero/compare nonzero)를 받는다
- branch_imm_common() 함수로 target address와 pc로부터 offset을 구하고 128 MB 보다 offset이 크면 에러 리턴한다
- branch type이 link이면 함수 aarch64_insn_get_bl_value() 함수로 bl 명령어 opcode를 구한다
- branch type이 no link이면 함수 aarch64_insn_get_b_value() 함수로 b 명령어의 opcode를 구한다
- 함수 aarch64_insn_encode_immediate()로 위에서 구한 명령어의 opcode에 offset을 삽입하여 명령어를 생성한다

 

branch_imm_common()
- 인자로 pc, addr, range를 받는다
- pc/addr가 word align (4 byte)가 안맞으면 에러 리턴
- addr - pc로 offset을 구하고 range 범위 밖이면 역시 에러 리턴
- 아니면 offset을 리턴한다


__AARCH64_INSN_FUNCS(abbr, mask, val) 매크로
- __AARCH64_INSN_FUNCS(b,     0xFC000000, 0x14000000) 방식으로 사용해서 명령어 관련한 아래 함수를 만든다

 

aarch64_insn_is_##abbr(u32 code)
- code가 abbr에 해당하는 명령어 opcode인지 판정하여 리턴한다

 

aarch64_insn_get_##abbr##_value()
- abbr에 해당하는 명령어의 opcode를 리턴한다

 

aarch64_insn_encode_immediate()
- 인자로 immediate type, opcode, immediate 값을 받아서 opcode에 적절히 immediate 값을 encoding하여 리턴한다

 

aarch64_insn_gen_nop()
- hint 명령어의 opcode를 리턴한다

 

aarch64_insn_patch_text_nosync()
- 인자로 주소 포인터와 명령어를 받는다
- 주소값이 word aligned이 아니면 에러 리턴
- 함수 aarch64_insn_write()로 주소에 명령어를 쓴다
- 함수 __flush_icache_range()로 명령어를 쓴 주소 영역의 i-cache를 flush()하고 리턴한다

 

aarch64_insn_write(addr, insn)
- raw_spin_lock_irqsave() 함수로 patch_lock을 잡는다
- patch_map() 함수로 fixmap으로 주소 addr의 임시 가상주소를 잡는다
- 함수 copy_to_kernel_nofault()로 임시 가상 주소에 명령어를 쓴다
- patch_unmap() 함수로 fixmap 주소를 해제한다
- raw_spin_unlock_irqrestore() 함수로 patch_lock을 풀고 리턴한다

 

patch_map()
- 커널 이미지 주소를 패치하기 위한 fixmap 주소를 받기 위해 사용한다
- 입력 주소가 커널 이미지인지 확인하고 fixmap 임시 주소를 리턴

 

copy_to_kernel_nofault()
- __put_kernel_nofault() 매크로로 write를 수행

https://elixir.bootlin.com/linux/latest/source/arch/arm64/include/asm/uaccess.h#L399

 

alternative 관련
- https://github.com/torvalds/linux/commit/ef5e724b25c9f90b7683bb2d45833ebac0989dcb
- arch/arm64/include/asm/alternative.h
- http://jake.dothome.co.kr/alternative/

 

smp_prepare_boot_cpu()
- apply_boot_alternatives() <— 여기 까지 진행

 

apply_boot_alternatives()
- 위에서 구한 cpu feature/capability 정보를 바탕으로 alternative로 지정된 코드를 변경시킨다

- CPU가 boot cpu가 아니면 경고
- 함수 __apply_alternatives()를 이용하여 위에서 만든 비트맵 boot_capabilities를 통해 boot cpu feature에 따라 alternative 코드를 패치한다

 

alternative instruction 구조체

 struct alt_instr {
     s32 orig_offset;    /* offset to original instruction */
     s32 alt_offset;     /* offset to replacement instruction */
     u16 cpufeature;     /* cpufeature bit set for replacement */
     u8  orig_len;       /* size of original instruction(s) */
     u8  alt_len;        /* size of new instruction(s), <= orig_len */
 };

 

alternative callback 함수 포인터

typedef void (*alternative_cb_t)(struct alt_instr *alt, __le32 *origptr, __le32 *updptr, int nr_inst);

 

전역변수 비트맵 applied_alternatives[]
- cpu feature에 대해 각 해당하는 alternative code 패치가 적용되었는지 나타내는 비트맵

 

__apply_alternatives()
- alternative code patch를 위한 함수로 alt_instr 구조체 포인터와 feature mask를 받는다.
- 이 외에 현재 patch하는 코드가 module 코드인지도 받아서 따로 처리하게 되어 있다.

 

- alternative entry들에 대해서 루프를 돌면서 패치가 필요한 코드를 변경시킨다
- 각 entry의 cpu feature가 feature mask에 set되어 있지 않으면 feature가 없는 것이므로 패스한다
- 각 entry의 cpu feature가 callback이 아니고 전역 변수 비트맵 cpu_hwcaps에 set되어 있지 않으면 패스한다
- 각 entry의 cpu feature가 callback인데 length가 0이 아니면 버그
- 각 entry의 cpu feature가 callback이 아니고 alternative와 original code의 길이가 동일하지 않으면 버그
- original code를 덮어 쓰기 위한 주소를 만든다. orignal code가 모듈 영역 코드면 그냥 original code 주소값을 그냥 사용하나 kernel image 영역 코드이면 linear mapping 주소를 받아온다. linear mapping 주소는 write 가능하기 때문
- 함수 patch_alternative()로 코드를 패치한다. 만약 alternative code가 callback 함수이면 해당 함수를 수행하여 코드를 패치한다
- orignal code가 모듈 영역이 아닌 경우 data cache를 clean & invalidate 한다

 

- 모든 entry에 대해 패치가 끝나면 모듈 코드 영역이 아닌 경우 아래를 수행한다
- dsb()/icache invalidate/isb()를 수행한다. 만약 cpu가 cache DIC 기능이 있으면 icache는 invalidate하지 않는다
- 비트맵 applied_alternatives에 feature_mask를 OR하여 적용된 패치를 반영한다
- 비트맵 applied_alternatives에 cpu_hwcaps를 AND하여 cpu hw에 없는 feature는 제외한다

 

patch_alternative()
- 루프를 돌면서 업데이트해야할 주소에 alternative code를 write한다
- 함수 get_alt_insn()로 alternative code를 만든다

 

get_alt_insn()
- alternative 명령어가 immediate branch, adrp인 경우에는 offset을 적절히 조정해준다
- alternative 명령어가 literal을 사용하는 경우에는 버그로 허용되지 않는다
- 나머지는 alternative 명령어를 변형하지 않고 그대로 사용한다
- offset 조정은 alternative 명령어에서 offset을 읽어오고, alternative 명령어의 PC를 더해 target address를 구한다
- 명령어가 immediate branch인 경우 target address에서 original 명령어의 PC를 빼서 새 offset을 만든다음 alternative 명령어의 offset에 추가한다
- 다만 이 때 target이 alternative code 영역 내에 있으면 offset 조정이 필요없으므로 생략한다
- adrp 명령어의 경우 PC값을 4KB-algin하는 부분만 제외하면 immediate branch의 경우와 동일하게 offset을 조정해준다. 다만 이 경우 target이 alternative code 영역 내에 있더라도 offset 조정을 생략하지 않는다

XE Login