- 2011년 1월 kernel v2.6.39-rc1에서 pv_table을 fixup 하는 방법을 구현하였다.
- 2013년 7월 kernel v3.13-rc1에서 ARM 32bits 시스템에서 4G를 초과하는 물리메모리를 사용하는 시스템(LPAE)에서도 동작되도록 구현을 추가하였다.
- .init.pv_table 섹션 영역에 주소 변환 함수(__virt_to_phys(), __phys_to_virt())를 사용하는 모든 커널 코드를 초기 커널 부팅 시 런타임에 모두 패치한다.
- 가상 주소와 물리 주소가 1:1 direct mapping 되는 구간의 특성을 이용하기 때문에 kernel 영역의 lowmem 영역의 주소 변환에서만 사용가능하다.
- 32bit ARM 프로세스에서 64비트 물리 주소를 사용하는 경우는 LPAE를 사용하여 구현한다.
- 이 때 가상주소는 32bit, 물리 주소는 64비트를 사용하여 변환한다.
- 런타임 시 가상주소보다 큰 주소를 가질 수 있는 물리 주소의 첫 시작 주소에서 가상 주소중 커널의 시작 주소를 뺀 값을 pv_offset 이라고한다.
- LPAE 시스템에서 물리주소가 4G를 초과하는 경우가 있기 때문에 pv_offset도 64비트를 사용한다.
- rpi2: 0xFFFF_FFFF_8000_0000
- 물리 RAM 주소가 커널 가상 주소보다 작은 경우에는 pv_offset의 상위 32비트가 0xFFFF_FFFF(-1 음수 값)이다.
- rpi2:
- 실제 라즈베리안 설정에서는 CONFIG_PHYS_OFFSET=0으로 고정되어 있고 CONFIG_ARM_PATCH_VIRT_PHYS 또한 설정되어 있지 않아 fixup_pv_table 루틴은 동작하지 않지만 CONFIG_ARM_PATCH_VIRT_PHYS 옵션을 사용하는 경우를 가정하여 알아본다.
- bcm2709_defconfig로 빌드하면 CONFIG_ARM_PATCH_VIRT_PHYS 옵션을 사용한다.
다양한 주소 변환 함수
- pv_table과 관련된 함수는 __virt_to_phys() 및 __phys_to_virt() 함수이다.
- __virt_to_phys()
- 가상 주소를 물리 주소로 바꾸는 inline 함수
- __phys_to_virt()
- 물리 주소를 가상 주소로 바꾸는 inline 함수
- __virt_to_phys()
- pv_offset를 사용하여 주소 변환을 하는 경우
- __virt_to_phys()
- 물리 주소 = 가상 주소 + pv_offset
- __phys_to_virt()
- 가상 주소 = 물리 주소 – pv_offset
- __virt_to_phys()
fixup_pv_table 순서도
fixup_pv_table에 사용한 레지스터들
- r4에서 r7까지의 pv_table 영역에서 엔트리를 r7에 가져온다.
- r7은 가상주소 값이므로 r3(pv_offset)를 적용하여 물리주소로 변환한 후에 커널 코드에 접근해서 해당 주소의 명령을 ip 레지스터로 받아온다.
- ip를 조작한 후 다시 그 자리에 기록한다.
__fixup_pv_table:
런타임에서 물리 주소를 가상주소로 변환하는 코드를 패치하는데 XIP 커널에서는 사용할 수 없고 최소한도내의 성능 저하로 동작한다.
변환하는 방법은 pv_offset(PHYS_OFFSET – PAGE_OFFSET) 값을 더하고 빼는 것에 기반한다.
- physical = virtual + (PHYS_OFFSET – PAGE_OFFSET)
- __virt_to_phys()를 위해 add 명령을 사용한다.
- virtual = physical – (PHYS_OFFSET – PAGE_OFFSET)
- __phys_to_virt()를 위해 sub 명령을 사용한다.
pv_offset을 계산할 때 사용하는 PHYS_OFFSET이 컴파일 시 고정되어 만들어지는 초기값이라 실제 런타임시에는 여러 가지 시스템의 각 메모리의 시작 주소가 다른 시스템에서 동작 하게 된다. 이 때 MMU가 켜지기 전에 실제 물리주소를 런타임에서 알아내어 pv_offset를 계산해낸다.
이러한 pv_offset 값을 이용해서 모든 __virt_to_phys() 함수와 __phys_to_virt() 함수에서 더하고 빼는 offset 값을 패치한다.
arch/arm/kernel/head.S
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT /* __fixup_pv_table - patch the stub instructions with the delta between * PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and * can be expressed by an immediate shifter operand. The stub instruction * has a form of '(add|sub) rd, rn, #imm'. */ __HEAD __fixup_pv_table: adr r0, 1f ldmia r0, {r3-r7} mvn ip, #0 subs r3, r0, r3 @ PHYS_OFFSET - PAGE_OFFSET add r4, r4, r3 @ adjust table start address add r5, r5, r3 @ adjust table end address add r6, r6, r3 @ adjust __pv_phys_pfn_offset address add r7, r7, r3 @ adjust __pv_offset address mov r0, r8, lsr #PAGE_SHIFT @ convert to PFN str r0, [r6] @ save computed PHYS_OFFSET to __pv_phys_pfn_offset strcc ip, [r7, #HIGH_OFFSET] @ save to __pv_offset high bits mov r6, r3, lsr #24 @ constant for add/sub instructions teq r3, r6, lsl #24 @ must be 16MiB aligned THUMB( it ne @ cross section branch ) bne __error str r3, [r7, #LOW_OFFSET] @ save to __pv_offset low bits b __fixup_a_pv_table ENDPROC(__fixup_pv_table)
- CONFIG_ARM_PATCH_PHYS_VIRT
- 런타임시 물리 주소를 가상주소로 변환하는 코드를 패치하는 옵션이다.
- 이 옵션은 XIP_KERNEL에서는 사용할 수 없고 SPARSEMEM 옵션을 사용하는 시스템에서도 사용할 수 없다.
- 당연히 MMU를 사용하지 않는 시스템에서도 이용할 수 없다.
- __HEAD
- .head.text 섹션을 사용한다.
- ldmia r0, {r3-r7}
- r3: offset 교정을 위한 현재 위치에 대한 가상 주소
- r4: pv_table의 시작 물리 주소
- r5: pv_table의 끝 물리 주소
- r6: __pv_phys_pfn_offset 데이터를 가리키는 주소
- r7: __pv_offset 데이터를 가리키는 주소
- r8: 물리 RAM 시작 주소로 이 함수 호출 전에 계산되어 있는 상태. (phys_offset)
- mvn ip, #0
- ip -> 0xffff_ffff
- mvn 명령은 오퍼랜드 값을 비트단위로 not을 수행한다.
- subs r3, r0, r3
- r3: offset (물리주소 – 가상주소)
- r4 ~ r7까지 offset 주소를 적용해 물리주소로 변경한다.
- mov r0, r8, lsr #PAGE_SHIFT
- 물리주소를 12비트 우측으로 쉬프트하면 pfn(페이지 번호)을 얻을 수 있다.
- rpi2:
- r8 = phys_offset(0x0000_0000)
- r0 = __pv_phys_pfn_offset(0x0)
- str r0, [r6]
- r6(__pv_phys_pfn_offset)가 가리키는 주소에 교정된 물리메모리의 pfn값이 들어간다.
- rpi2: r0 = 0x0
- strcc ip, [r7, #HIGH_OFFSET]
- 물리 RAM 주소가 커널 시작 작은 경우 r7+#HIGH_OFFSET이 가리키는 주소에 즉 __pv_offset[63:32]에 ip(-1)값을 저장한다.
- 64bit 물리 주소를 위해 사용되는 것으로 -1 값은 나중에 carry가 발생하고 그 carry를 adc 명령으로 더할 때 0이되는 것을 이용하기 위함이다.
- 만일 이 문장이 수행되지 않았다면 __pv_offset[63:32]에는 처음 빌드된대로 0 값이 들어있다.
- rpi2: 물리 RAM 주소가 가상 커널 주소보다 작아서 0xffff_ffff가 저장됨.
- mov r6, r3, lsr #24
- r6 = 24bit align(16M) 체크를 위해 r3(offset)를 우측으로 24비트 쉬프트한다.
- teq r3, r6, lsl #24
- r6를 다시 좌측으로 24비트 쉬프트하여 원래 값과 비교한다.
- bne __error
- 16M align이 안되어 있으면 에러를 출력한다.
- pv_offset 용도로 사용할 이 값은 실제로 변환할 주소의 MSB 8bit만을 사용하여 add, sub 등을 수행 시 오퍼랜드 부분의 immediate 필드의 8비트만을 변경하므로 반드시 24 비트 align 되어 있어야 한다.
- str r3, [r7, #LOW_OFFSET]
- r3(offset)을 r7+#LOW_OFFSET이 가리키는 주소 즉 __pv_offset[31:0]에 저장한다.
- 몇 가지 예)
- 커널가상주소=0x8000_0000, 물리RAM주소=0x8000_0000
- pv_offset = 0x0000_0000_0000_0000
- pv_phys_pfn_0ffset = 0x0008_0000
- 커널가상주소=0x8000_0000, 물리RAM주소=0x0000_0000
- pv_offset = 0xffff_ffff_8000_0000
- pv_phys_pfn_0ffset = 0x0000_0000
- 커널가상주소=0xC000_0000, 물리RAM주소=0x2000_0000
- pv_offset = 0xffff_ffff_6000_0000
- pv_phys_pfn_0ffset = 0x0002_0000
- 커널가상주소=0x8000_0000, 물리RAM주소=0x8000_0000
- b __fixup_a_pv_table
- __pv_table을 fixup 하는 루틴으로 이동
.align 1: .long . .long __pv_table_begin .long __pv_table_end 2: .long __pv_phys_pfn_offset .long __pv_offset
- 주의할 것은 __pv_phys_pfn_offset나 __pv_offset 항목은 해당 값이 저장되는 장소가 아니고 데이터가 저장되는 곳의 주소가 담겨있다.
- 실제 offset 관련 데이터가 저장되는 곳은 __pv_phys_pfn_offset: 객체이다.
__fixup_a_pv_table:
- r4(__pv_table_begin) 부터 r5(__pv_table_end)까지 r7에 4바이트 엔트리를 가져와서 r7이 가리키는 가상 주소값에서 ip(명령)을 가져온다.
- 가져온 ip(명령)가 add, sub, adds 인 경우 immediate[7:0] 필드에 pv_offset를 변경하고, 추가적으로 ip(명령)가 mov 이면서 pv_offset[63:32]가 0xffff_ffff(-1)인 경우 mvn #0으로 변경한다.
- 변경된 ip(명령)를 다시 원래 가상 주소에 패치를 수행하는 것으로 이 루틴의 수행이 완료된다.
.text __fixup_a_pv_table: adr r0, 3f ldr r6, [r0] add r6, r6, r3 ldr r0, [r6, #HIGH_OFFSET] @ pv_offset high word ldr r6, [r6, #LOW_OFFSET] @ pv_offset low word mov r6, r6, lsr #24 cmn r0, #1 #ifdef CONFIG_THUMB2_KERNEL (...THUMB2 코드 생략...) #else #ifdef CONFIG_CPU_ENDIAN_BE8 moveq r0, #0x00004000 @ set bit 22, mov to mvn instruction #else moveq r0, #0x400000 @ set bit 22, mov to mvn instruction #endif b 2f
- adr r0, 3f
- r0 = 3f 레이블의 물리주소
- 3f 레이블은 _pv_offset 변수를 가리키는 주소
- ldr r6, [r0]
- r6 <- r0가 가리키는 주소 즉 __pv_offset가 담겨있는 가상 주소를 알아온다.
- add r6, r6, r3
- r6 = __pv_offset를 가리키는 가상 주소를 의 물리주소로 변환
- ldr r0, [r6, #HIGH_OFFSET]
- r0 = __pv_offset[63:32]
- rpi2: 0xffff_ffff
- ldr r6, [r6, #LOW_OFFSET]
- r6 = __pv_offset[31:0]
- rpi2: 0x8000_0000
- mov r6, r6, lsr #24
- r6 = add 또는 sub 인스트럭션의 immediate 영역(lsb 8비트)만을 수정하기 위해 r6를 24비트 쉬프트한다.
- rpi2: 0x0000_0080
- cmn r0, #1
- r0(__pv_offset[63:32])와 0xffff_ffff(-1)을 비교
- moveq r0, #0x400000
- __pv_offset[63:32]가 -1인 경우 mov 대신 mvn #0명령을 사용하기 위해 r0에 #0x400000(bit 22 on)을 대입한다.
- b 2f
- 루프의 마지막 부분으로 이동하여 __pv_table에서 첫 엔트리를 가져온다.
1: ldr ip, [r7, r3] #ifdef CONFIG_CPU_ENDIAN_BE8 @ in BE8, we load data in BE, but instructions still in LE bic ip, ip, #0xff000000 tst ip, #0x000f0000 @ check the rotation field orrne ip, ip, r6, lsl #24 @ mask in offset bits 31-24 biceq ip, ip, #0x00004000 @ clear bit 22 orreq ip, ip, r0 @ mask in offset bits 7-0 #else bic ip, ip, #0x000000ff tst ip, #0xf00 @ check the rotation field orrne ip, ip, r6 @ mask in offset bits 31-24 biceq ip, ip, #0x400000 @ clear bit 22 orreq ip, ip, r0 @ mask in offset bits 7-0 #endif str ip, [r7, r3] 2: cmp r4, r5 ldrcc r7, [r4], #4 @ use branch for delay slot bcc 1b ret lr nop nop nop nop nop nop nop nop #endif ENDPROC(__fixup_a_pv_table) .align 3: .long __pv_offset
- ldr ip, [r7, r3]
- ip에는 add(s), sub 및 mov 등의 명령이 담겨있다.
- r7: pv_table에서 가져온 4바이트 엔트리로 가상 주소가 담겨 있다.
- r3: pv_offset
- ip = r7이 가리키는 물리주소에서 4바이트 명령을 하나 가져온다.
- bic ip, ip, #0x000000ff
- 가져온 명령을 구성하는 비트 중 immediate[7:0] 필드 부분을 클리어한다.
- tst ip, #0xf00
- 가져온 명령을 구성하는 비트 중 rotation[11:8] 필드를 체크한다
- rotate 필드 구성 조건
- 32 비트 물리 주소를 사용
- __virt_to_phys()에서 add 사용 시 rotate != 0
- __phys_to_virt()에서 sub 사용 시 rotate != 0
- 64비트 물리 주소를 사용
- __virt_to_phys()에서 mov 사용 시 rotate = 0
- __virt_to_phys()에서 adds 사용 시 rotate != 0
- __phys_to_virt()에서 sub 사용 시 rotate != 0
- 32 비트 물리 주소를 사용
- orrne ip, ip, r6
- rotate != 0인 경우로 add, sub. adds 명령이 있다.
- r6: pv_offset[31:24] >> 24 한 값
- ip = 명령을 구성하는 비트 중 immediate[7:0] 부분에 r6로 채운다.
- biceq ip, ip, #0x400000
- rotate = 0인 경우로 mov 명령이 있다.
- bit22를 clear하여 mov 명령으로 변경한다.
- bit22: 0=mov, 1=mvn
- orreq ip, ip, r0
- ip에 r0(0=mov 유지, 0x400000=mvn으로 변경) 값을 더한다.
- r0가 mvn이 될 수 있는 경우는 pv_offset[64:32]가 0xffff_ffff 인경우로 물리 RAM 시작 주소가 커널시작 가상 주소보다 작은 경우이다.
- 예) 커널가상주소=0x8000_0000, 물리RAM주소=0x0000_0000 인 경우 pv_offset = 0xffff_ffff_8000_0000가 된다.
- str ip, [r7, r3]
- 조작한 명령을 다시 원래 주소에 저장한다.
- 2: cmp r4, r5
- 루프(__pv_table_end)의 끝 인지 비교
- ldrcc r7, [r4], #4
- 끝이 아니면 r4(pv_table의 루프 카운터)에서 r7(pv_table 엔트리)을 가져오고 r4를 4만큼 증가한다.
- bcc 1b
- 루프로 돌아간다.
- ret lr
- 완료 했으므로 리턴
__pv_phys_pfn_offset & __pv_offset 객체
.data .globl __pv_phys_pfn_offset .type __pv_phys_pfn_offset, %object __pv_phys_pfn_offset: .word 0 .size __pv_phys_pfn_offset, . -__pv_phys_pfn_offset .globl __pv_offset .type __pv_offset, %object __pv_offset: .quad 0 .size __pv_offset, . -__pv_offset
- __pv_phys_pfn_offset의 경우 4바이트(1 word)를 사용하고 초기 값은 0이다.
- __pv_offset의 경우 64비트 물리주소의 offset을 표현하기 위해 8바이트(2 word)를 사용하고 초기 값은 0이다.
- .quad 0
- ARM이 아닌 아키텍처에서는 워드가 2바이트 따라서 quad word가 8바이트이다.
- ARM에서는 워드가 4바이트지만 quad 타입은 위와 동일하게 8바이트이다.
주소 변환 함수
virt_to_phys()
static inline phys_addr_t virt_to_phys(const volatile void *x) { return __virt_to_phys((unsigned long)(x)); }
- rpi2:
- virt_to_phys(0x8123_4560)
- add r0, 0x8123_4560, 0x8100_0000
- pv_fixup 후 (pv_fixup=0xffff_ffff_8000_0000)
- add r0, 0x8123_4560, 0x8000_0000
- r0 = 0x0123_4560
- virt_to_phys(0x8123_4560)
- 64비트 예: 물리램=0x0_8000_0000, 커널가상주소=0xC000_0000
- virt_to_phys(0xc123_4560)
- mov r0[64:32], 0x0000_0081
- adds r0[31:0], 0xc123_4560, 0x8100_0000
- adc r0[63:32], r0[63:32], #0
- pv_fixup 후 (pv_fixup=0xffff_ffff_c000_0000)
- mvn r0[64:32], 0xffff_ffff
- adds r0[31:0], 0xc123_4560, 0xc0000_0000
- adc r0[63:32], r0[63:32], #0
- r0 = 0x0000_0000_8123_4560
- virt_to_phys(0xc123_4560)
- 64비트 예: 물리램=0x1_8000_0000, 커널가상주소=0xC000_0000
- virt_to_phys(0xc123_4560)
- mov r0[64:32], 0x0000_0081
- adds r0[31:0], 0xc123_4560, 0x8100_0000
- adc r0[63:32], r0[63:32], #0
- pv_fixup 후 (pv_fixup=0x0_c000_0000)
- mvn r0[64:32], 0x0
- adds r0[31:0], 0xc123_4560, 0xc0000_0000
- adc r0[63:32], r0[63:32], #0
- r0 = 0x0000_0001_8123_4560
- virt_to_phys(0xc123_4560)
phys_to_virt()
static inline void *phys_to_virt(phys_addr_t x) { return (void *)__phys_to_virt(x); }
- rpi2:
- phys_to_virt(0x0123_4560)
- sub r0, 0x0123_4560, 0x8100_0000
- pv_fixup 후 (pv_fixup=0xffff_ffff_8000_0000)
- sub r0, 0x0123_4560, 0x8000_0000
- r0 = 0x8123_4560
- phys_to_virt(0x0123_4560)
- 64비트 예: 물리램=0x0_8000_0000, 커널가상주소=0xC000_0000
- phys_to_virt(0x0_8123_4560)
- sub r0, 0x8123_4560, 0x8100_0000
- pv_fixup 후 (pv_fixup=0xFFFF_FFFF_C000_0000)
- sub r0, 0x8123_4560, 0xC000_0000
- r0 = 0xC123_4560
- phys_to_virt(0x0_8123_4560)
- 64비트 예: 물리램=0x1_8000_0000, 커널가상주소=0xC000_0000
- phys_to_virt(0x_1_8123_4560)
- sub r0, 0x8123_4560, 0x8100_0000
- pv_fixup 후 (pv_fixup=0x0_C000_0000)
- sub r0, 0x8123_4560, 0xC000_0000
- r0 = 0xC123_4560
- phys_to_virt(0x_1_8123_4560)
CONFIG_ARM_PATCH_PHYS_VIRT 옵션을 사용할 때의 주소 변환 함수
__virt_to_phys()
static inline phys_addr_t __virt_to_phys(unsigned long x) { phys_addr_t t; if (sizeof(phys_addr_t) == 4) { __pv_stub(x, t, "add", __PV_BITS_31_24); } else { __pv_stub_mov_hi(t); __pv_add_carry_stub(x, t); } return t; }
- 1) 32bit 가상 주소 -> 32비트 물리 주소
-
- __pv_stub()의 경우
- __PV_BITS_31_24를 오퍼랜드로 사용하여 rotate[21:8] 필드 값이 컴파일 시 4로 고정된다.
- (8 바이트 immediate 부분을 우측으로 8비트 rotation이 필요하다. rotation 1비트당 2번의 rotation 동작되므로 rotation 값을 4로 한다.)
- add 명령어 사용
- __PV_BITS_31_24를 오퍼랜드로 사용하여 rotate[21:8] 필드 값이 컴파일 시 4로 고정된다.
- __pv_stub()의 경우
- 2) 32bit 가상 주소 -> 64bit 물리주소(LPAE)
- __pv_stub_mov_hi()의 경우
- __PV_BITS_7_0을 사용하여 rotate[12:8] 필드값이 컴파일 시 0으로 고정된다.
- mov 명령어 사용
- __pv_add_carry_stub()의 경우
- __PV_BIT_31_24를 오퍼랜드로 사용하여 rotate[12:8] 필드값이 컴파일 시 4로 고정된다.
- adds 명령어 사용
- adc 명령을 사용하여 adds 명령 사용 시 더한 결과가 4G를 넘어 carry가 발생하면 물리주소의 상위 32비트 값에 carry를 더한다.
- __pv_stub_mov_hi()의 경우
__phys_to_virt()
static inline unsigned long __phys_to_virt(phys_addr_t x) { unsigned long t; /* * 'unsigned long' cast discard upper word when * phys_addr_t is 64 bit, and makes sure that inline * assembler expression receives 32 bit argument * in place where 'r' 32 bit operand is expected. */ __pv_stub((unsigned long) x, t, "sub", __PV_BITS_31_24); return t; }
- 64비트 물리주소를 사용하는 시스템(LPAE)에서 64 비트 물리 주소를 32 비트 가상 주소로 변환하는 경우 물리 주소의 상위 32 비트는 무시하고 계산한다.
- __PV_BITS_31_24를 오퍼랜드로 사용하여 rotate[21:8] 필드 값이 컴파일 시 4로 고정된다.
- sub 명령어 사용
__pv_stub()
#define __pv_stub(from,to,instr,type) \ __asm__("@ __pv_stub\n" \ "1: " instr " %0, %1, %2\n" \ " .pushsection .pv_table,\"a\"\n" \ " .long 1b\n" \ " .popsection\n" \ : "=r" (to) \ : "r" (from), "I" (type))
__pv_stub_mov_hi()
#define __pv_stub_mov_hi(t) \ __asm__ volatile("@ __pv_stub_mov\n" \ "1: mov %R0, %1\n" \ " .pushsection .pv_table,\"a\"\n" \ " .long 1b\n" \ " .popsection\n" \ : "=r" (t) \ : "I" (__PV_BITS_7_0))
- %R0
- 인수 %0의 하위 32비트를 취급하는 레지스터
__pv_add_carry_stub()
#define __pv_add_carry_stub(x, y) \ __asm__ volatile("@ __pv_add_carry_stub\n" \ "1: adds %Q0, %1, %2\n" \ " adc %R0, %R0, #0\n" \ " .pushsection .pv_table,\"a\"\n" \ " .long 1b\n" \ " .popsection\n" \ : "+r" (y) \ : "r" (x), "I" (__PV_BITS_31_24) \ : "cc")
- %Q0
- 인수 %0의 상위 32비트를 취급하는 레지스터
- adc 명령은 pv_table에 push 하지 않는다.
pv_table
주소 변환 함수 사용 시 __pv_table에 저장되는 내용
32bit 물리 주소 시스템
- virt_to_phys() 및 phys_to_virt() 함수는 각각 하나의 ARM 명령어를 만들어내고 그 주소를 __pv_table에 저장한다.
- 엔트리 주소는 커널이 사용하는 가상 주소이다.
64bit 물리 주소 시스템(LPAE)
- virt_to_phys() 함수의 경우 3개의 ARM 명령어를 만들어내고 그 중 2 개의 주소를 __pv_table에 저장한다.
- 첫 번째, pv_stub_mov_hi() 매크로를 호출하고 여기서 1개의 mov 명령이 만들어지며 해당 주소는 __pv_table에 저장된다.
- 두 번째, pv_add_carry_stub() 매크로를 호출하고 여기서 1개의 adds 명령이 만들어지고 해당 주소는 __pv_table에 저장된다. 다음 또 1개의 adc 명령어를 만들고 이 명령에 대해서는 __pv_table에 저장하지 않는다.
- phys_to_virt() 함수의 경우 1개의 ARM 명령어를 만들어내고 그 중 1 개의 주소를 __pv_table에 저장한다.
- 물리 주소 64비트를 가상 주소로 변환 시 phys[63:32]는 버리고 계산한다. 이렇게 버려도 상관없는 이유는 1:1 direct mapping이 허가된 lowmem 영역에 대해서만 변환을 보장하는 특성 때문에 이 영역을 벗어난 영역을 정확히 알아낼 수는 없다.
- PAGE_OFFSET:0xC000_0000, 물리램 시작 주소: 0x1_8000_0000 에서의 예)
- 0xC123_0000 = phys_to_virt(0x1_8123_0000);
- 0xC123_0000 = phys_to_virt(0x2_8123_0000); -> lowmem 벗어나는 영역으로 시도
- PAGE_OFFSET:0xC000_0000, 물리램 시작 주소: 0x1_8000_0000 에서의 예)
- 물리 주소 64비트를 가상 주소로 변환 시 phys[63:32]는 버리고 계산한다. 이렇게 버려도 상관없는 이유는 1:1 direct mapping이 허가된 lowmem 영역에 대해서만 변환을 보장하는 특성 때문에 이 영역을 벗어난 영역을 정확히 알아낼 수는 없다.
저장 섹센
.init.pv_table : { __pv_table_begin = .; *(.pv_table) __pv_table_end = .; } .init.data : {
pv_table 엔트리 패치 세부 과정
32bit 물리 주소(물리 RAM 주소: 0x0)
- 계산된 pv_offset의 하위 32비트를 각 명령의 immediate 필드 부분만 교체한다.
64bit 물리 주소(물리 RAM 주소: 0x0_8000_0000)
- 명령의 rotate 필드가 0인 경우
- sub, adds 명령이 해당된다.
- 계산된 pv_offset의 하위 32비트를 각 명령의 immediate 필드 부분만 교체한다.
- 명령의 rotate 필드가 1인 경우
- mov 명령이 해당된다.
- 계산된 pv_offset의 상위 32비트를 mov 명령의 immediate 필드 부분만 교체하는데 만일 음수 값이면 0x0을 넣고 명령을 mvn을 넣는다.
명령어의 비트별 구조
- Rotate 필드는 1증가 시마다 rotate 2에 해당한다.
- 우측으로 8비트를 rotate 하고자 할 경우 Rotate 필드에 4를 대입 한다.
- 컴파일 시 Immediate에 __PV_BITS_31_24(0x8100_0000) 값을 사용하면 Rotate 값이 자동 계산되어 4가 대입된다.
- __PV_BITS_31_24를 2진수로 표현하면 10000001_00000000_00000000_00000000 과 같다.
- Immediate는 인접한 8비트만 표현가능하므로 여기선 0b10000001이란 이진 수를 우측으로 8번을 rotate 시키면 0x8100_0000과 같아진다.
- __PV_BITS_31_24를 2진수로 표현하면 10000001_00000000_00000000_00000000 과 같다.
다양한 주소 변환 방법
- pv_table을 사용하여 런타임 시 패치하는 방법: CONFIG_ARM_PATCH_PHYS_VIRT=y
- DTB가 사용되기 시작한 커널 3.x 에서 같이 사용
- 연속되지 않은 물리메모리(SPARSEMEM) 사용시에도 이 옵션을 사용할 수 없음
- 커널 내부 명령을 수정하는 기능으로 XIP 커널 이미지에서는 이 기능을 사용할 수 없다.
- 컴파일 시 고정시킨 offset을 사용하는 Legacy 방법: CONFIG_ARM_PATCH_PHYS_VIRT=n
- 그 외 제작사가 제공하는 방법: 별도의 함수를 제공해야 한다.
- PFN: 물리 주소를 물리 페이지 단위의 번호로 바꾼 값
- 물리 주소 0x0000_0000~0x0000_0FFF → PFN 0
- 물리 주소 0x0FFF_F000~0x0FFF_FFFF → PFN 0xFFFF
- 라즈베리파이2:
- 커널 초기화(__fixup_pv_table) 후 → __pv_phys_pfn_offset=0x0
- __pv_offset=0xFFFF_FFFF_8000_0000