[커널 20차] 24주차

2023.10.22 17:44

이민찬 조회 수:737

 

 

2023.10.21 (24주차) - 약 10명 참여

head.S : __cpu_setup 완료, __primary_switch의 __enable_mmu의 phys_to_ttbr 까지 진행.

개념 정리) __cpu_setup의 tlbi vmalle1 코드에서 local TLB를 invalidate하는데, TLB에 대해서 알고 넘어가면 좋겠다. 

WKEkvVf6lHHUCKM4k1utYVXjyaaR1RsWGrxrcp3J

1)http://www.iamroot.org/xe/index.php?mid=Programming&listStyle=viewer&document_srl=219053&page=1

2) https://www.bhral.com/post/arm-v8-linux-kernel-head-s-6

3) https://www.techtarget.com/whatis/definition/translation-look-aside-buffer-TLB

4) https://en.wikipedia.org/wiki/Translation_lookaside_buffer

 

Q: .req 지시자

mair .req x17

tcr .req x16

 

A: define 

#define mair x17    // mair .req x17

#define tcr x16      // tcr .req x16

 

정리)

< MAIR_EL1에 들어갈 값 설정 >

1. default 값 설정 (0x4_0044_ffff)

#define MAIR_EL1_SET \

(MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRnE, MT_DEVICE_nGnRnE) | \

MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRE, MT_DEVICE_nGnRE) | \

MAIR_ATTRIDX(MAIR_ATTR_NORMAL_NC, MT_NORMAL_NC) | \

MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL) | \

MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL_TAGGED))

 

1) MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRnE, MT_DEVICE_nGnRnE) 0x00 << (3 * 8)

2) MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRE, MT_DEVICE_nGnRE)     0x04 << (4 * 8)

3) MAIR_ATTRIDX(MAIR_ATTR_NORMAL_NC, MT_NORMAL_NC)       0x44 << (2 * 8)

4) MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL)       0xff << (0 * 8)

5) MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL_TAGGED))       0xff << (1 * 8)

 

(정확한지는 모르겠다.)

1) Attr0 1111_1111

- Normal memory, Outer Write-Back Non-transient 

- Normal memory, Inner Write-Back Non-transient 

2) Attr1 1111_1111

- Normal memory, Outer Write-Back Non-transient 

- Normal memory, Inner Write-Back Non-transient 

3) Attr2 0100_0100

- Normal memory, Outer Non-cacheable 

- Normar memory, Inner Non-cacheable

4) Attr3 0000_0000  

- Device-nGnRnE memory

5) Attr4 0000_0100

- Device-nGnRE memory

 

< TCR (Tranlsation Control Register) 에 들어갈 값 설정 >

1. default 값 설정

mov_q tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \

TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \

TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS | TCR_MTE_FLAGS

 

1) TCR_TxSZ(VA_BITS) : T0SZ와 T1SZ 값을 (64 - VA_BITS)로 설정

2) TCR_CACHE_FLAGS : Inner cacheability / Outer cacheability 특성을 WBWA로 설정 

(Write Back, Write Allocate)

3) TCR_SMP_FLAGS : Sharability 특성을 Inner sharable로 설정

4) TCR_TG_FLAGS : 페이지 크기에 따라 granule 설정

5) TCR_KASLR_FLAGS : KASLR 설정

6) TCR_ASID16 : ASID 크기 설정

7) TCR_TBI0 : TTBR0_EL1 영역에서 주소 계산 시 Top byte 무시 설정

8) TCR_A1 : TTBR0_EL1과  TTBR1_EL1 중 어느 것에서 ASID 비트를 사용할 것인지 설정 

9) TCR_KASAN_SW_FLAGS : TTBR1_EL1 영역에서 주소 계산 시 Top byte 무시 설정, top byte 사용 시 명령어와 데이터 접근에 사용할 것인지, 데이터 접근에만 사용할 것인지 설정

10) TCR_MTE_FLAGS : MTE 설정 (Memory Tagging Extension)

 

2. errata 처리

tcr_clear_errata_bits tcr, x9, x5

특정 CPU에서 errata 발생시 TCR bits를 클리어

 

3. T0SZ / T1SZ 값 재설정

#ifdef CONFIG_ARM64_VA_BITS_52

sub x9, xzr, x0

add x9, x9, #64

tcr_set_t1sz tcr, x9

#else

idmap_get_t0sz x9

#endif

tcr_set_t0sz tcr, x9

 

1) VA_BITS가 52인 경우 T0SZ / T1SZ

x0는 VA_BITS_MIN이 들어있다. 결국 (64 - VA_BITS_MIN) 값으로 재설정하는 것이다.

VA_BITS가 52인데, 지원하지 않는 경우 VA_BITS_MIN = 48이다. 지원하는 경우 52이다.

 

T0SZ는 T1SZ와 같다.

 

2) VA_BITS가 52가 아닌 경우 T0SZ / T1SZ

T1SZ는 default 값 설정과 같다. (64 - VA_BITS)이다.

 

T0SZ는 idmap_get_t0sz 매크로를 통해서 얻어온다. 커널의 끝 주소를 통해 idmap을 커버할 T0SZ 값을 구한다. VA_BITS로 표현할 수 있는 주소 범위보다 커널이 더 높은 위치에 있는 경우에 T0SZ 값을 변경함으로써 idmap을 커버할 수 있게 한다.

 

4. TCR_EL1의 IPS 비트 값 설정 (Intermediate Physical address Size)

/*

 * Set the IPS bits in TCR_EL1.

 */

tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6

/*

 * tcr_compute_pa_size - set TCR.(I)PS to the highest supported

 * ID_AA64MMFR0_EL1.PARange value

 *

 * tcr: register with the TCR_ELx value to be updated

 * pos: IPS or PS bitfield position

 * tmp{0,1}: temporary registers

 */

.macro tcr_compute_pa_size, tcr, pos, tmp0, tmp1

mrs \tmp0, ID_AA64MMFR0_EL1

// Narrow PARange to fit the PS field in TCR_ELx

ubfx \tmp0, \tmp0, #ID_AA64MMFR0_EL1_PARANGE_SHIFT, #3

mov \tmp1, #ID_AA64MMFR0_EL1_PARANGE_MAX

cmp \tmp0, \tmp1

csel \tmp0, \tmp1, \tmp0, hi

bfi \tcr, \tmp0, \pos, #3

.endm

 

1) ID_AA64MMFR0_EL1 레지스터로부터 PS 필드의 값을 읽어온다.

2) 읽어온 값과 ID_AA64MMFR0_EL1_PARANGE_MAX 값을 비교해 작은 값을 가져온다.

3) 가져온 작은 값을 TCR_EL1의 IPS로 설정한다. 

 

부팅 시 들어가는 ID_AA64MMFR0_EL1.PARANGE(PS) 값과 커널에서 config된 PA_BITS에 따른 PARANGE_MAX 값을 비교해서 작은 값을 선택하는 것이다.

 

아래 사진을 통해 ID_AA64MMFR0_EL1.PS 와 TCR_EL1.IPS는 대응하는 것을 확인할 수 있다.

cklJacpRYBQGYccsggykwje9J-PBYeoLXcfGvB8F

 

h4KpRnOaKMJk-HyHnWJcdYGdB0kxxXAlGTo3W8T3


 

5. Access Flags bit를 설정한다. (TCR.HA)

#ifdef CONFIG_ARM64_HW_AFDBM

/*

* Enable hardware update of the Access Flags bit.

* Hardware dirty bit management is enabled later,

* via capabilities.

*/

mrs x9, ID_AA64MMFR1_EL1

and x9, x9, #0xf

cbz x9, 1f

orr tcr, tcr, #TCR_HA // hardware Access flag update

1:

#endif

1) ID_AA64MMFR1_EL1의 값을 읽어와서 하위 4비트가 0인지 확인해, 아니라면 TCR.HA 비트를 설정해준다.

 

Access Flag의 의미는 다음과 같다.

 

Q: Access flag

A: 사용 중인 페이지를 알 수 있다. 

56sqNoviv5DtcRBk_8rTojncpKqLg66j9DdqXKBs

 

하드웨어적으로 Access Flag 비트를 업데이트 하는 것을 허용한다는 의미는 다음과 같다.

 

Q: hardware update of the Access Flag bit

A: The Page fault handler records that this page is now being used and manually sets the AF bit in the table entry. 를 보면 페이지 폴트 핸들러는 페이지가 사용중이라고 기록하고, 수동으로 AF bit를 기록한다고 나와있다. 수동의 의미는 O/S가 처리해준다는 것인데, 이 과정을 하드웨어적으로 지원한다는 의미인 것 같다. 


 

< 설정한 값을 시스템에 반영 >

1. 설정한 mair, tcr 값을 MAIR_EL1, TCR_EL1 레지스터에 반영한다. SCTLR에는 INIT_SCTLR_EL1_MMU_ON 값을 저장한다.

#endif /* CONFIG_ARM64_HW_AFDBM */

msr mair_el1, mair

msr tcr_el1, tcr

/*

* Prepare SCTLR

*/

mov_q x0, INIT_SCTLR_EL1_MMU_ON

ret

 

< __primary_switch 초반부 >

Q: reserved_pg_dir?

A: http://jake.dothome.co.kr/head-64-60/

보안 상향을 위해 copy_from_user() 등의 별도의 전용 API 사용을 제외하고 무단으로 커널 space에서 유저 공간에 접근 못하게 금지하는 SW 에뮬레이션 방식에서 필요한 zero 페이지 테이블이다. ARMv8.0까지 사용되며, ARMv8.1-PAN HW 기능을 사용하면서 이 테이블은 사용하지 않는다.

 

< __enable_mmu >

1. ID_AA64MMFR0_EL1.TGRAN 값을 통해 지원하는 granule 인지 확인한다.

mrs x3, ID_AA64MMFR0_EL1

ubfx x3, x3, #ID_AA64MMFR0_EL1_TGRAN_SHIFT, 4

cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN

b.lt    __no_granule_support

cmp     x3, #ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX

b.gt    __no_granule_support

cf)  

#define ID_AA64MMFR0_EL1_TGRAN4_SHIFT                   28

#define ID_AA64MMFR0_EL1_TGRAN16_SHIFT                 20

#define ID_AA64MMFR0_EL1_TGRAN64_SHIFT                 24 

 

ID_AA64MMFR0_EL1.TGRAN

https://developer.arm.com/documentation/ddi0601/2023-09/AArch64-Registers/ID-AA64MMFR0-EL1--AArch64-Memory-Model-Feature-Register-0

 

ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN 과 MAX 값을 통해 읽어온 granule 필드 값이 MIN 보다 작거나 MAX 보다 큰 경우 __no_granule_suport로 이동.

 

Q: 왜 ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN, MAX의 값이 0x0, 0x7, 0x15로 설정되었을까요?

D5Zj0ZcLCM4XLoCC2lPyqUqkxJmCdUrswEmClG5P

A: https://lore.kernel.org/linux-arm-kernel/20221102022057.553634951@linuxfoundation.org/t/ 

패치 노트를 보면, 이전에는 지원하는지 지원하지 않는지를 .ne로 비교했는데, 

aYDG8KQ9U9n1jc5eblF04QBhUhxcVLcHMsKMbgCj

위 사진과 같이 52bit를 지원하면서 범위를 설정하게 되었다. 

MIN보다 작은 경우에 __no_granule_support로 이동하는 코드는 16KB granule일 때 not supported를 확인하기 위함인 것 같고, MAX보다 큰 경우에 __no_granule_support로 이동하는 코드는 4K, 64KB granule일 때 not supported를 확인하기 위함인 것 같다. 

 

각 MIN,MAX 값이 위와 같이 설정된 이유는 정확히는 모르겠지만, 확장성을 고려하지 않았을까 싶다.

 

참고:

https://lore.kernel.org/lkml/718f4b0c-20d9-8588-1268-e5b26690899d@arm.com/T/


 

2. __no_granule_support

SYM_FUNC_START_LOCAL(__no_granule_support)

/* Indicate that this CPU can't boot and is stuck in the kernel */

update_early_cpu_boot_status \

CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2

1:

wfe

wfi

b 1b

SYM_FUNC_END(__no_granule_support)

early_cpu_boot_status 영역에 (CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN) 값을 저장한다. (로그로 활용하기 위함인 것 같다.)

 

(arch/arm64/mm/mmu.c)

/*

 * The booting CPU updates the failed status @__early_cpu_boot_status,

 * with MMU turned off.

 */

long __section(".mmuoff.data.write") __early_cpu_boot_status;

MMU가 꺼진 상태에서 부팅에 실패하면 __early_cpu_boot_status에 실패 상태를 저장한다.

 

< 미결 질문 >

Q: __no_granule_support에서 wfi만 무한 루프 돌면 재부팅이 되는 것으로 알고 있습니다. 왜 wfe와 wfi를 중복 사용했을까요?

 

cf) cold boot(메모리에 값이 없는 상태로 부팅) / warm boot? (메모리에 값이 쓰여진 채로 부팅)

 

참고:

http://www.iamroot.org/xe/index.php?mid=Programming&document_srl=219128

https://developer.arm.com/documentation/ka001283/latest/

 

3. TTBR 레지스터에 들어갈 값을 설정한다. 

/*

 * Arrange a physical address in a TTBR register, taking care of 52-bit

 * addresses.

 *

 * phys: physical address, preserved

 * ttbr: returns the TTBR value

 */

.macro phys_to_ttbr, ttbr, phys

#ifdef CONFIG_ARM64_PA_BITS_52

orr \ttbr, \phys, \phys, lsr #46

and \ttbr, \ttbr, #TTBR_BADDR_MASK_52

#else

mov \ttbr, \phys

#endif

.endm

 

1) PA_BITS가 52로 설정된 경우

주소 값에서 상위 6비트를 하위 6비트로 옮긴 후 TTBR_BADDR_MASK_52 값을 통해 masking 해준다. 이 연산을 완료하면, TTBR의 BADDR(Base address)에 해당하는 값만 남게 된다.

 

Q: TTBR_BADDR_MASK_52 값은 0x0000000000000000_1111111111111111_1111111111111111_1111111111111100 입니다. 왜 하위 2비트는 0일까요? 

A1: https://developer.arm.com/documentation/ddi0595/2021-12/AArch32-Registers/TTBR0--Translation-Table-Base-Register-0?lang=en

A2:  https://github.com/iamroot20/linux-stable/commit/529c4b05a3cb2f324aac347042ee6d641478e946

A3: https://developer.arm.com/documentation/ddi0595/2021-12/AArch64-Registers/TTBR0-EL1--Translation-Table-Base-Register-0--EL1-?lang=en#fieldset_0-0_0-1

 

2) PA_BITS가 52가 아닌 경우

idmap의 시작 주소를 ttbr 값으로 한다.

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 626
248 [커널 19차] 103 주차 new Min 2024.04.28 1
247 [커널 20차] 48주차 무한질주 2024.04.25 21
246 [커널 19차] 102 주차 Min 2024.04.20 36
245 [커널 19차] 101 주차 Min 2024.04.13 63
244 [커널 19차] 100 주차 Min 2024.04.13 16
243 [커널 19차] 99 주차 Min 2024.03.30 82
242 [커널 19차] 98 주차 Min 2024.03.23 55
241 [커널 19차] 97 주차 Min 2024.03.16 50
240 [커널 19차] 96 주차 Min 2024.03.14 32
239 [커널 19차] 95 주차 [2] Min 2024.03.03 111
238 [커널 20차] 32주차 brw 2023.12.16 386
237 [커널 20차] 29주차 brw 2023.11.27 161
236 [커널 20차] 27주차 brw 2023.11.21 86
235 [커널 20차] 26주차 brw 2023.11.21 48
234 [커널 20차] 28주차 이민찬 2023.11.19 64
233 [커널 20차] 25주차 이민찬 2023.10.30 120
» [커널 20차] 24주차 이민찬 2023.10.22 737
231 [커널 20차] 23주차 이민찬 2023.10.14 81
230 [커널 20차] 22주차 이민찬 2023.10.08 76
229 [커널 20차] 21주차 이민찬 2023.09.23 116
XE Login