kernel/head.S – __fixup_pv_table:

  • 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_table8

  • pv_table과 관련된 함수는 __virt_to_phys() 및 __phys_to_virt() 함수이다.
    • __virt_to_phys()
      • 가상 주소를 물리 주소로 바꾸는 inline 함수
    • __phys_to_virt()
      • 물리 주소를 가상 주소로 바꾸는 inline 함수
  • pv_offset를 사용하여 주소 변환을 하는 경우
    • __virt_to_phys()
      • 물리 주소 = 가상 주소 + pv_offset
    • __phys_to_virt()
      • 가상 주소 = 물리 주소 – pv_offset

 

fixup_pv_table 순서도

 

pv_table7

 

fixup_pv_table에 사용한 레지스터들

pv_table6

  • 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
  • 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
  • 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
  • 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
  • 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

 

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
  • 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
  • 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

 

 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 명령어 사용
  • 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를 더한다.

 

__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 물리 주소 시스템

pv_table1a

  • virt_to_phys() 및 phys_to_virt() 함수는 각각 하나의 ARM 명령어를 만들어내고 그 주소를 __pv_table에 저장한다.
  • 엔트리 주소는 커널이 사용하는 가상 주소이다.

 

64bit 물리 주소 시스템(LPAE)

pv_table2c

  • 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 벗어나는 영역으로 시도

 

 

저장 섹센

        .init.pv_table : { 
                __pv_table_begin = .;
                *(.pv_table)
                __pv_table_end = .;
        }
        .init.data : {

 

pv_table 엔트리 패치 세부 과정

32bit 물리 주소(물리 RAM 주소: 0x0)

pv_table3

  • 계산된 pv_offset의 하위 32비트를 각 명령의 immediate 필드 부분만 교체한다.

 

64bit 물리 주소(물리 RAM 주소: 0x0_8000_0000)

pv_table4

  • 명령의 rotate 필드가 0인 경우
    • sub, adds 명령이 해당된다.
    • 계산된 pv_offset의 하위 32비트를 각 명령의 immediate 필드 부분만 교체한다.
  • 명령의 rotate 필드가 1인 경우
    • mov 명령이 해당된다.
    • 계산된 pv_offset의 상위 32비트를 mov 명령의 immediate 필드 부분만 교체하는데 만일 음수 값이면 0x0을 넣고 명령을 mvn을 넣는다.

명령어의 비트별 구조

pv_table5

  • 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_table을 사용하여 런타임 시 패치하는 방법: CONFIG_ARM_PATCH_PHYS_VIRT=y
    • DTB가 사용되기 시작한 커널 3.x 에서 같이 사용
    • 연속되지 않은 물리메모리(SPARSEMEM) 사용시에도 이 옵션을 사용할 수 없음
    • 커널 내부 명령을 수정하는 기능으로 XIP 커널 이미지에서는 이 기능을 사용할 수 없다.
  • 컴파일 시 고정시킨 offset을 사용하는 Legacy 방법: CONFIG_ARM_PATCH_PHYS_VIRT=n
  • 그 외 제작사가 제공하는 방법: 별도의 함수를 제공해야 한다.

pv_table9

  • 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

 

 

댓글 남기기