kernel/head.S – __v7_ca7mp_setup:

  1. SMP 시스템에서 캐시 coherent가 disable되어 있으면 enable시킨다.
  2. d-cache를 모두 flush(invalidate + clean)한다.
  3. 현재 프로세스와 관련 있는 ERRATA(프로세스 에러 패치)를 적용한다.
  4. i-cache를 모두 invalidate한다.
  5. TLB cache를 모두 invalidate한다.
  6. TTBCR을 0으로 설정한다. (페이지테이블을 나누어 사용하지 않는다)
  7. TTBR1에 TTB_RGN_WBWA | TTB_S를 설정한다.
  8. PRRR에 =PRRR 값을 설정한다.
  9. NMRR에 =NMRR 값을 설정한다.

kernel_head.s분석11

__v7_ca7mp_setup:

/*
 *      __v7_setup
 *
 *      Initialise TLB, Caches, and MMU state ready to switch the MMU
 *      on.  Return in r0 the new CP15 C1 control register setting.
 *
 *      This should be able to cover all ARMv7 cores.
 *
 *      It is assumed that:
 *      - cache type register is implemented
 */
__v7_ca7mp_setup:
        mov     r10, #0
#ifdef CONFIG_SMP
        ALT_SMP(mrc     p15, 0, r0, c1, c0, 1)
        ALT_UP(mov      r0, #(1 << 6))          @ fake it for UP
        tst     r0, #(1 << 6)                   @ SMP/nAMP mode enabled?
        orreq   r0, r0, #(1 << 6)               @ Enable SMP/nAMP mode
        orreq   r0, r0, r10                     @ Enable CPU-specific SMP bits
        mcreq   p15, 0, r0, c1, c0, 1
#endif
        b       __v7_setup
  • ACTLR.SMP를 읽어 0으로 되어 있으면 1로 다시 바꾼다.
    • Enables coherent requests to the processor: 0=disable, 1=enable

 

__v7_setup:

__v7_setup:
        adr     r12, __v7_setup_stack           @ the local stack
        stmia   r12, {r0-r5, r7, r9, r11, lr}
        bl      v7_flush_dcache_louis
        ldmia   r12, {r0-r5, r7, r9, r11, lr}
  • adr     r12, __v7_setup_stack
    • r12=임시 스택용도로 사용할 공간 주소
  • stmia   r12, {r0-r5, r7, r9, r11, lr}
    • 레지스터들을 잠시 백업한다.
  • bl      v7_flush_dcache_louis
    • LoUIS 레벨까지의 d-cache를 flush한다.
  • 백업해 두었던 레지스터들을 복원하다.
        mrc     p15, 0, r0, c0, c0, 0           @ read main ID register
        and     r10, r0, #0xff000000            @ ARM?
        teq     r10, #0x41000000
        bne     3f
        and     r5, r0, #0x00f00000             @ variant
        and     r6, r0, #0x0000000f             @ revision
        orr     r6, r6, r5, lsr #20-4           @ combine variant and revision
        ubfx    r0, r0, #4, #12                 @ primary part number
  •  mrc     p15, 0, r0, c0, c0, 0
    • ERRATA 루틴들을 적용하기 위해 확인목적으로 read MIDR 한다.
    • MIDR(Main ID Register)
      • Implementer[31:24]: 0x41=ARM
      • Variant[23:20]: Major Revision Number
      • Architecture[19:16]: 0xf=ARMv7
      • Primary_Part_Number[15:4]: 0xc07=Cortex A7 MPCore
      • Revision[3:0]: Minor Revision Number
  • bne     3f
    • ARM이 아니면 3f로 jump
  • and     r5, r0, #0x00f00000
    • r5=MIDR.variant 부분만 추출
  • and     r6, r0, #0x0000000f
    • r6=MIDR.revision 부분만 추출
  • orr     r6, r6, r5, lsr #20-4
    • r5를 우측으로 16비트 쉬프트하여 r6와 합친다.
    • r6[7:4]=variant, r6[3:0]=revision
  • ubfx    r0, r0, #4, #12
    • r0=r0를 우측으로 4비트 쉬프트한 후 lsb 12비트만을 가져온다.
    • 결국 MIDR.Primary_Part_Number 부분만 추출한다.
    • rpi2: r0=0xc07
        /* Cortex-A8 Errata */
        ldr     r10, =0x00000c08                @ Cortex-A8 primary part number
        teq     r0, r10
        bne     2f
  • 다음은 Cortex-A8용 ERRATA이므로 Cortex-A8이 아니면 2f로 점프
#if defined(CONFIG_ARM_ERRATA_430973) && !defined(CONFIG_ARCH_MULTIPLATFORM)

        teq     r5, #0x00100000                 @ only present in r1p*
        mrceq   p15, 0, r10, c1, c0, 1          @ read aux control register
        orreq   r10, r10, #(1 << 6)             @ set IBE to 1
        mcreq   p15, 0, r10, c1, c0, 1          @ write aux control register
#endif
#ifdef CONFIG_ARM_ERRATA_458693
        teq     r6, #0x20                       @ only present in r2p0
        mrceq   p15, 0, r10, c1, c0, 1          @ read aux control register
        orreq   r10, r10, #(1 << 5)             @ set L1NEON to 1
        orreq   r10, r10, #(1 << 9)             @ set PLDNOP to 1
        mcreq   p15, 0, r10, c1, c0, 1          @ write aux control register
#endif
#ifdef CONFIG_ARM_ERRATA_460075
        teq     r6, #0x20                       @ only present in r2p0
        mrceq   p15, 1, r10, c9, c0, 2          @ read L2 cache aux ctrl register
        tsteq   r10, #1 << 22
        orreq   r10, r10, #(1 << 22)            @ set the Write Allocate disable bit
        mcreq   p15, 1, r10, c9, c0, 2          @ write the L2 cache aux ctrl register
#endif
        b       3f

 

        /* Cortex-A9 Errata */
2:      ldr     r10, =0x00000c09                @ Cortex-A9 primary part number
        teq     r0, r10
        bne     3f
  • 다음은 Cortex-A9용 ERRATA이므로 Cortex-A9이 아니면 3f로 점프
#ifdef CONFIG_ARM_ERRATA_742230
        cmp     r6, #0x22                       @ only present up to r2p2
        mrcle   p15, 0, r10, c15, c0, 1         @ read diagnostic register
        orrle   r10, r10, #1 << 4               @ set bit #4
        mcrle   p15, 0, r10, c15, c0, 1         @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_742231
        teq     r6, #0x20                       @ present in r2p0
        teqne   r6, #0x21                       @ present in r2p1
        teqne   r6, #0x22                       @ present in r2p2
        mrceq   p15, 0, r10, c15, c0, 1         @ read diagnostic register
        orreq   r10, r10, #1 << 12              @ set bit #12
        orreq   r10, r10, #1 << 22              @ set bit #22
        mcreq   p15, 0, r10, c15, c0, 1         @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_743622
        teq     r5, #0x00200000                 @ only present in r2p*
        mrceq   p15, 0, r10, c15, c0, 1         @ read diagnostic register
        orreq   r10, r10, #1 << 6               @ set bit #6
        mcreq   p15, 0, r10, c15, c0, 1         @ write diagnostic register
#endif
#if defined(CONFIG_ARM_ERRATA_751472) && defined(CONFIG_SMP)
        ALT_SMP(cmp r6, #0x30)                  @ present prior to r3p0
        ALT_UP_B(1f)
        mrclt   p15, 0, r10, c15, c0, 1         @ read diagnostic register
        orrlt   r10, r10, #1 << 11              @ set bit #11
        mcrlt   p15, 0, r10, c15, c0, 1         @ write diagnostic register
1:
#endif

 

        /* Cortex-A15 Errata */
3:      ldr     r10, =0x00000c0f                @ Cortex-A15 primary part number
        teq     r0, r10
        bne     4f
  • 다음은 Cortex-A15용 ERRATA이므로 Cortex-A15가 아니면 4f로 점프
#ifdef CONFIG_ARM_ERRATA_773022
        cmp     r6, #0x4                        @ only present up to r0p4
        mrcle   p15, 0, r10, c1, c0, 1          @ read aux control register
        orrle   r10, r10, #1 << 1               @ disable loop buffer
        mcrle   p15, 0, r10, c1, c0, 1          @ write aux control register
#endif

 

4:      mov     r10, #0
        mcr     p15, 0, r10, c7, c5, 0          @ I+BTB cache invalidate
#ifdef CONFIG_MMU
        mcr     p15, 0, r10, c8, c7, 0          @ invalidate I + D TLBs
        v7_ttb_setup r10, r4, r8, r5            @ TTBCR, TTBRx setup
        ldr     r5, =PRRR                       @ PRRR
        ldr     r6, =NMRR                       @ NMRR
        mcr     p15, 0, r5, c10, c2, 0          @ write PRRR
        mcr     p15, 0, r6, c10, c2, 1          @ write NMRR
#endif
        dsb                                     @ Complete invalidations
  • mcr     p15, 0, r10, c7, c5, 0
    • invalidate all i-cache to PoU (ICIALLU)
  • mcr     p15, 0, r10, c8, c7, 0
    • invalidate TLB cache all (TLBIALL)
  • v7_ttb_setup r10, r4, r8, r5
    • arch/arm/mm/proc-v7-2level.S
    • .macro      v7_ttb_setup, zero, ttbr0, ttbr1, tmp
      • zero: TTBCR에서 사용할 0값이 담겨 있어야 한다.
      • ttbr0 |= TTB_RGN_WBWA|TTB_S -> 기록하지 않음.
      • ttbr1 |= TTB_RGN_WBWA|TTB_S -> 실제기록까지함.
      • tmp: 사용하지 않는다.
    • r10: 0
    • r4, r8: 페이지테이블 물리주소 |= TTB_RGN_WBWA|TTB_S
  • mcr     p15, 0, r5, c10, c2, 0
    • r5: 0xff0a_81a8 (PRRR)
  • mcr     p15, 0, r6, c10, c2, 1
    • r6: 0x40e0_40e0(NMRR)
#ifndef CONFIG_ARM_THUMBEE
        mrc     p15, 0, r0, c0, c1, 0           @ read ID_PFR0 for ThumbEE
        and     r0, r0, #(0xf << 12)            @ ThumbEE enabled field
        teq     r0, #(1 << 12)                  @ check if ThumbEE is present
        bne     1f
        mov     r5, #0
        mcr     p14, 6, r5, c1, c0, 0           @ Initialize TEEHBR to 0
        mrc     p14, 6, r0, c0, c0, 0           @ load TEECR
        orr     r0, r0, #1                      @ set the 1st bit in order to
        mcr     p14, 6, r0, c0, c0, 0           @ stop userspace TEEHBR access
1:
#endif
  • mrc     p15, 0, r0, c0, c1, 0
    • read ID_PFR0.ThumbEE[15..12]: 12번비트가 1이면 ThumbEE가 지원됨.
  • teq     r0, #(1 << 12)
    • ThumbEE 비트가 1인 경우 z flag가 1이 된다. (xor)
  • bne     1f
    • 0이 아니면(ThumbEE가 없으면) jump
        adr     r5, v7_crval
        ldmia   r5, {r5, r6}
 ARM_BE8(orr    r6, r6, #1 << 25)               @ big-endian page tables
#ifdef CONFIG_SWP_EMULATE
        orr     r5, r5, #(1 << 10)              @ set SW bit in "clear"
        bic     r6, r6, #(1 << 10)              @ clear it in "mmuset"
#endif
        mrc     p15, 0, r0, c1, c0, 0           @ read control register
        bic     r0, r0, r5                      @ clear bits them
        orr     r0, r0, r6                      @ set them
 THUMB( orr     r0, r0, #1 << 30        )       @ Thumb exceptions
        ret     lr                              @ return to head.S:__ret
        .space 256
ENDPROC(__v7_setup)
  • adr     r5, v7_crval
    • v7_crval는 arch/arm/mm/proc-v7-2level.S에 있음
  • ldmia   r5, {r5, r6}
    • r5=0x2120c302 (for bit clear)
    • r6=0x10c03c7d (for orr)
  • mrc     p15, 0, r0, c1, c0, 0
    • SCTLR을 r0에 읽어 들인다.
  • bic     r0, r0, r5
    • r5로 bit clear
  • orr     r0, r0, r6
    • r6를 더한다
  • ret     lr
    • lr=__enable_mmu: 의 물리 주소가 담겨 있음.

 

__v7_setup_stack:

        .align  2
__v7_setup_stack:
        .space  4 * 11                          @ 11 registers

 

 

v7_ttb_setup 매크로

        /*
         * Macro for setting up the TTBRx and TTBCR registers.
         * - \ttb0 and \ttb1 updated with the corresponding flags.
         */

        .macro  v7_ttb_setup, zero, ttbr0, ttbr1, tmp 
        mcr     p15, 0, \zero, c2, c0, 2        @ TTB control register
        ALT_SMP(orr     \ttbr0, \ttbr0, #TTB_FLAGS_SMP)
        ALT_UP(orr      \ttbr0, \ttbr0, #TTB_FLAGS_UP)
        ALT_SMP(orr     \ttbr1, \ttbr1, #TTB_FLAGS_SMP)
        ALT_UP(orr      \ttbr1, \ttbr1, #TTB_FLAGS_UP)
        mcr     p15, 0, \ttbr1, c2, c0, 1       @ load TTB1
        .endm
  • mcr     p15, 0, \zero, c2, c0, 2
    • TTBCR에 zero(0)를 기록한다.
  • ALT_SMP:
    • ttbr0 |= (TTB_RGN_WBWA|TTB_S)
    • ttbr1 |= (TTB_RGN_WBWA|TTB_S)
  • mcr     p15, 0, \ttbr1, c2, c0, 1
    • TTBR1에 ttbr1을 기록한다.
    • global 커널 페이지 테이블용으로 백업해두고 커널에 진입할 때 마다 ttbr0에 복사하여 사용

 

v7_crval:

arch/arm/mm/proc-v7-2level.S

        /*   AT 
         *  TFR   EV X F   I D LR    S
         * .EEE ..EE PUI. .T.T 4RVI ZWRS BLDP WCAM
         * rxxx rrxx xxx0 0101 xxxx xxxx x111 xxxx < forced 
         *   01    0 110       0011 1100 .111 1101 < we want
         */
        .align  2
        .type   v7_crval, #object
v7_crval:
        crval   clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c
  • clear
    • AFE, VE, FI, Bit[15], RR, Bit[9], Bit[8], A
  • mmuset
    • TRE, Bit[23], U, V, I, Z, SW, Bit[6], CP15BEN, Bit[4], Bit[3], C, M
  • ucset
    • Bit[23], U, I, Z, SW, Bit[6], CP15BEN, Bit[4], Bit[3], C
  • SCTLR
    • Bit[31]: 0
    • TE[30]: Thumb Exception enable. 0=ARM state, 1=Thumb state
    • AFE[29]: Access Flag Enable. 0=full range, 1=simplified model
    • TRE[28]: TEX remap enable.
    • NMFI[27]: Non-maskable FIQ support. 0=SW can mask, 1=SW can not mask
    • Bit[26]: 0
    • EE[25]: Exception Endianness. 0=Little-endian, 1=Big-endian
    • VE[24]: Interrupt Vectors Enable: 0=use FIQ and IRQ vectors. 1=impl.
    • Bit[23]: 1
    • U[22]: Alignment support on page (ARMv7에서 1)
    • FI[21]: Fast Interrupts configuration enable. 0=All performance features enabled, 1=low interrupt latency. (some performance feature is disabled)
    • UWXN[20]: Unprivileged Write permission implies PL1 XN (for VE)
    • WXN[19]: Write permission implies XN (for VE)
    • Bit[20:19]: 0
    • Bit[18]: 1
    • HA[17]: Hardware Access flag enable.
    • Bit[16]: 1
    • Bit[15]: 0
    • RR[14]: Round Robin select. 0=normal(random), 1=Predictable strategy. (RR)
    • V[13]: Vectors bit. 0=0x0000_0000, 1=0xFFFF_0000
    • I[12]: Instruction Cache enable.
    • Z[11]: Branch Prediction enable.
    • SW[10]: SWP and SWPB enable
    • Bit[9:8]: 0
    • B[7]: Endian support on page (ARMv7에서 0)
    • Bit[6]: 1
    • CP15BEN[5]: CP15 barrier enable
    • Bit[4:3]: 1
    • C[2]: Cache enable
    • A[1]: Alignment check enable
    • M[0]: MMU enable

sctlr

 

crval 매크로

        .macro  crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
        .word   \clear
        .word   \mmuset
#else
        .word   \clear
        .word   \ucset
#endif
        .endm

 

참고

kernel/head.S – __create_page_table:

create_page_table의 주요 동작

  • 페이지 테이블(PT)을 초기화한다.
  • 페이지 테이블 엔트리(PTE)에 사용할 메모리 속성 정보인 mm_mmuflags를 프로세스 정보에서 가져온다.
  • __turn_mmu_on 루틴을 1:1 identity(VA=PA) mapping 한다.
  • 커널 코드 시작부터 커널 끝(_end)까지 매핑 한다.
    • 커널의 물리주소로 매핑하지 않고 가상주소로 매핑한다.
  • XIP  커널 이미지의 경우 ROM/Nor-Flash에서 실행되므로 이 영역도 매핑한다.
  • ATAGs/DTB 영역도 2개 섹션에 매핑한다.
    • DTB가 최대 1M 이므로 섹션에 걸쳐진다 해도 1M 페이지 섹션 2개면 충분하다.
    • ATAG가 0x100 위치에 있는 경우는 매핑하지 않는다.

kernel_head.s분석9a

 kernel/head.S – 페이지 테이블 구성 Diagram

커널 영역을 매핑할 때 kernel space에서 동작하게 해야 하므로 물리 메모리의 주소로 매핑하지 않고 커널의 가상 주소에 해당하는 주소로 매핑한다.

kernel_head.s분석10a

1:1 identity mapping

1:1 identity mapping은 가상주소를 물리주소로 변환한 주소가 같아야 할 때 필요하다. MMU를 on할 때 이러한 경우가 필요하다.

  • __turn_mmu_on 루틴을 수행할 때, 즉 MMU가 켜지기 전이므로 루틴은 물리메모리 주소로 동작 하고 있다.
  • MMU를 on 시킨 후 부터는 CPU가 물리주소(PA)를 얻기 위해 해당 주소에 해당하는 TLB 캐시를 조회한다.
  • TLB 캐시에 해당 가상 주소의 엔트리가 없는 경우 페이지테이블로부터 얻어낸다.
  • 결국 물리주소와 동일한 가상주소의 매핑이 없는 경우 페이지 fault가 발생하므로 이를 방지하기 위해 해당 루틴의 주소에 해당하는 영역의 매핑이 필요하다.
  • 해당 루틴의 매핑은 해당 루틴 영역(8개의 명령)에 대해 1:1 VA=PA 매핑이 필요하다.
  • 1:1 VA=PA 해당 매핑은 MMU를 on 한 후 루틴의 마지막에 커널 가상 주소로 점프하기 전까지 사용된다.

identity

 

create_page_table:

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 *
 * Returns:
 *  r0, r3, r5-r7 corrupted
 *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)
 */
__create_page_tables:
        pgtbl   r4, r8                          @ page table address
  • r8에 물리메모리 시작 주소를 담고 pgtbl 매크로를 수행하면 r4에 페이지 테이블 시작 주소가 담긴다.
        /*
         * Clear the swapper page table
         */
        mov     r0, r4
        mov     r3, #0
        add     r6, r0, #PG_DIR_SIZE
1:      str     r3, [r0], #4
        str     r3, [r0], #4
        str     r3, [r0], #4
        str     r3, [r0], #4
        teq     r0, r6
        bne     1b
  • 페이지 테이블 엔트리(r4 ~ r6 주소까지, r0=카운터) 전체를 0으로 초기화한다.
#ifdef CONFIG_ARM_LPAE
	(..생략..)
#endif
  • LPAE를 사용하면 3단계 주소 변환 테이블을 사용하는데 이 루틴에서 PMD 테이블을 가리키는 PGD 테이블을 생성한다.
  • PGD 엔트리는 64비트이다.
	ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
  • r10: 프로세서 타입에 따른 정보
    • rpi2: __v7_ca7mp_proc_info 레이블
  • #PROCINFO_MM_MMUFLAGS: 8
  • r7 (rpi2):
    • PMD_TYPE_SECT     |           <- 섹션타입
    • PMD_SECT_AP_WRITE |   <- write
    • PMD_SECT_AP_READ  |    <- read
    • PMD_SECT_AF       |
    • PMD_FLAGS_SMP                 <- PMD_SECT_WBWA | PMD_SECT_S
        /*
         * Create identity mapping to cater for __enable_mmu.
         * This identity mapping will be removed by paging_init().
         */
        adr     r0, __turn_mmu_on_loc
        ldmia   r0, {r3, r5, r6}
        sub     r0, r0, r3                      @ virt->phys offset
        add     r5, r5, r0                      @ phys __turn_mmu_on
        add     r6, r6, r0                      @ phys __turn_mmu_on_end
        mov     r5, r5, lsr #SECTION_SHIFT
        mov     r6, r6, lsr #SECTION_SHIFT
  • __turn_mmu_on 함수의 매핑(1:1 VA=PA(identity mapping))
  • __turn_mmu_on ~ __turn_mmu_on_end에는 mmu를 켜서 가상주소의 커널로 넘어가는 코드가 담겨있는데 mmu를 켜기 전에 가상주소와 물리메모리의 주소가 같은 1:1 매핑(identity mapping)을 한다. CPU가 가상 커널 주소로 리턴(jump)하기 전까지는 물리메모리의 위치에 해당하는 가상주소가 동작중이므로 완전히 커널이 위치한 가상주소로 스위칭되기 전까지 1-2개의 섹션이 필요하여 매핑한다.
1:      orr     r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel base
        str     r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
        cmp     r5, r6
        addlo   r5, r5, #1                      @ next section
        blo     1b
  • orr     r3, r7, r5, lsl #SECTION_SHIFT
    • __turn_mmu_on이 시작하는 섹션(31..20비트)과 위에서 읽은 mm_mmuflags(19..0비트)를 orr 시켜 계산된 테이블 엔트리에 저장한다
  • r3: 페이지테이블+r5(__turn_mmu_on~__turn_mmu_on_end의 1M 단위의 인덱스*4)
        /*
         * Map our RAM from the start to the end of the kernel .bss section.
         */
        add     r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
        ldr     r6, =(_end - 1)
        orr     r3, r8, r7
        add     r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:      str     r3, [r0], #1 << PMD_ORDER
        add     r3, r3, #1 << SECTION_SHIFT
        cmp     r0, r6
        bls     1b
  • 커널영역에 대한 페이지 테이블 매핑
  • r0: 페이지테이블 시작
    • 저장할 페이지 테이블 엔트리 주소로 4(8)바이트씩 증가하는 카운터
  • r4(물리페이지테이블주소) + 가상커널주소의 섹션 인덱스 값*4
    • rpi2: 처음 값은 0x0000_6000 부터 시작
  • r6: 페이지테이블 끝
    • 매핑할 커널의 마지막(.bss 섹션) 가상주소-1
    • 기존 r6에 대응하는 페이지 테이블 엔트리 주소로 바꿈.
  • r3: 기록할 섹션엔트리 값
    • = r8:phys_offset (물리메모리주소) + r7:mm_mmuflags
#ifdef CONFIG_XIP_KERNEL
        /*
         * Map the kernel image separately as it is not located in RAM.
         */
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
        mov     r3, pc
        mov     r3, r3, lsr #SECTION_SHIFT
        orr     r3, r7, r3, lsl #SECTION_SHIFT
        add     r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
        str     r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
        ldr     r6, =(_edata_loc - 1)
        add     r0, r0, #1 << PMD_ORDER
        add     r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:      cmp     r0, r6
        add     r3, r3, #1 << SECTION_SHIFT
        strls   r3, [r0], #1 << PMD_ORDER
        bls     1b
#endif
  • XIP 커널영역(XIP_START ~ _edata_loc-1)을 매핑한다.
        /*
         * Then map boot params address in r2 if specified.
         * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
         */
        mov     r0, r2, lsr #SECTION_SHIFT
        movs    r0, r0, lsl #SECTION_SHIFT
        subne   r3, r0, r8
        addne   r3, r3, #PAGE_OFFSET
        addne   r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
        orrne   r6, r7, r0
        strne   r6, [r3], #1 << PMD_ORDER
        addne   r6, r6, #1 << SECTION_SHIFT
        strne   r6, [r3]

#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
        sub     r4, r4, #4                      @ Fixup page table pointer
                                                @ for 64-bit descriptors
#endif
  • ATAG/DTB 영역에 대해 2개의 섹션으로 매핑한다.
  • r0: r2(ATAG/DTB)주소의 섹션인덱스 -> 다시 주소로
  • r3: 대상에 대해 수정할 페이지테이블주소
    • r0주소에 대한 가상 주소값 계산
      • r3 = r0(ATAG/DTB 1M align된 주소) – r8(phys_offset) + PAGE_OFFSET
    • 페이지 테이블 주소로 계산
      • r4(물리페이지테이블주소) + r3(r0의 가상 주소)의 섹션 인덱스의 offset
  • r6: 엔트리 값
    • r7(mm_mmuflags) + r0(r2주소의 섹션인덱스)
  • r0가 0이되는 케이스(ATAG나 DTB가 주어지지 않은 경우)는 ne 조건의 문장이 수행되지 않아 매핑을 하지 않는다.
    • ATAG가 0x100에 위치하는 경우도 매핑하지 않는다.
  • DTB는 최대 1M이며, 최대 2개의 섹션이 필요 할 수 있으므로 1M 증가하여 한번 더 매핑한다
#ifdef CONFIG_DEBUG_LL
(..생략..)
#endif
  • 디버그 출력 옵션을 사용한 경우 디버깅용 io 입출력 주소에 대한 매핑이 필요하다.
#ifdef CONFIG_ARM_LPAE
        sub     r4, r4, #0x1000         @ point to the PGD table
        mov     r4, r4, lsr #ARCH_PGD_SHIFT
#endif
        ret     lr
ENDPROC(__create_page_tables)
  • 종료

pgtbl 매크로

        .macro  pgtbl, rd, phys
        add     \rd, \phys, #TEXT_OFFSET
        sub     \rd, \rd, #PG_DIR_SIZE
        .endm
  • rd = phys + #TEXT_OFFSET – #PG_DIR_SIZE
  • 페이지 테이블이 커널 바로 밑에 위치하기 때문에 물리메모리 시작 주소에서 커널 시작 OFFSET를 더해 커널 시작 물리 주소를 구하고 페이지 디렉토리만큼 빼면 페이지 디렉토리 시작 물리 주소가 담기게 된다.

__turn_mmu_on_loc:

        .ltorg
        .align
__turn_mmu_on_loc:
        .long   .
        .long   __turn_mmu_on
        .long   __turn_mmu_on_end
  • __turn_mmu_on 코드가 시작되는 주소와 __turn_mmu_on 코드가 끝나는 주소가 담겨있다.

 

 

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

 

 

kernel/head.S – __fixup_smp:

SMP(Multi core) 운용을 위해 빌드한 커널이 실제 사용 시 UP(Uni core)에서 동작을 시키는 경우 SMP 전용 명령들을 모두 UP 명령으로 치환(fixup)해주는 루틴

  • 2010년 9월 kernel v2.6.37-rc1에 처음 추가되었고 2011년 1월 v2.6.38-rc3에서 추가 보강되었다.

 

전체 순서도

kernel_head.s분석5

 

UP 시스템 체크

__fixup_smp:

CPU id를 분석하여 4가지 조건 case에서 매치되는 경우 UP 시스템으로 인식하여 __fixup_smp_on_up: 루틴으로 이동하여 처리하고 SMP인 경우 루틴을 빠져나간다.

1) UP case A: ARMv7이 아니면 UP

MIDR을 분석하여 ARMv7이 아니면 UP로 판단한다.

#ifdef CONFIG_SMP_ON_UP
        __HEAD
__fixup_smp:
        and     r3, r9, #0x000f0000     @ architecture version
        teq     r3, #0x000f0000         @ CPU ID supported?
        bne     __fixup_smp_on_up       @ no, assume UP

MIDR을 읽어서 MIDR.architecture가 f가 아니면(armv7이 아니면) UP에서
* 동작한 것이라고 판단한다.

  • and r3, r9, #0x000f0000
    • r9(MIDR, cpu id)에 비트마스크 0x000f_0000(MIDR.architecture)를 한다.
    • MIDR
      • Implementer[31..24]: 0x41 =ARM
      • Variant[23..20]: 0x0 =Major revision number
      • Architecture[19..16]: 0xf =ARMv7
      • Primary part number[15..4]: 0xc07 =Cortex-A7 MPCore part number
      • Revision[3..0]: 0x3 =Minor revision number
  • teq r3, #0x000f0000
    • MIDR.architecture와 ARMv7(0x000f_0000)을 비교한다.
    • tst는 and 연산 후 상태 반영, teq는 xor 연산 후 상태 반영
  • MIDR.architecture가 ARMv7이 아니면 __fixup_smp_on_up으로 이동

 

2) ARM11MPCore인 경우 SMP

ARMv7인 경우 이 루틴에 도착하는데 cpu id를 분석하여 ARM11MPCore 인지 확인되면 SMP라 판단되어 루틴을 종료한다.

        bic     r3, r9, #0x00ff0000
        bic     r3, r3, #0x0000000f     @ mask 0xff00fff0
        mov     r4, #0x41000000
        orr     r4, r4, #0x0000b000
        orr     r4, r4, #0x00000020     @ val 0x4100b020
        teq     r3, r4                  @ ARM 11MPCore?
        reteq   lr                      @ yes, assume SMP
  • bic r3, r9, #0x00ff0000
    • r3 = MIDR.Variant 및 MIDR.Architecture 비트 clear
    • r3 = MIDR.Revision 비트 clear
  • r4 = #0x4100_b020
  • teq r3, r4
    • MIDR.Implementer가 0x41이고 MIDR.Primary part number가 0xb02이면 ARM11MPCore로 판단한다.
  • reteq lr
    • 이 프로세서는 MP 프로세서 이므로 루틴을 더 이상 실행하지 않고 종료.

 

3) UP case B: Multiprocessing Extension 포함되었지만 UP로 구성된 시스템은 UP

ARM11MPCore가 아닌 경우 이 루틴으로 오게되는데 아마 Cortex 시리즈들일 것이라 판단된다. MPIDR을 읽어 Multiprocess Extensions이 설정되어 있지만 UP로 구성되어 동작하게 되어 있는 시스템은 여기서 UP로 판단한다.

        mrc     p15, 0, r0, c0, c0, 5   @ read MPIDR
        and     r0, r0, #0xc0000000     @ multiprocessing extensions and
        teq     r0, #0x80000000         @ not part of a uniprocessor system?
        bne    __fixup_smp_on_up        @ no, assume UP
  • mrc p15, 0, r0, c0, c0, 5
    • MPIDR(Multi Processor Affinity Register)를 읽는다.
  • and r0, r0, #0xc0000000
    • MPIDR.Bits[31]: Multiprocessing Extensions 구현 포함 여부(0=미포함, 1=포함)
    • MPIDR.Bits[30]: 0=MP, 1=UP
  • teq r0, #0x80000000
    • Multiprocessing Extensions가 구현되었지만 UP인 경우 __fixup_smp_on_up 루틴으로 이동

 

4) Cortex-A9 MPCore가 아니면 SMP

Cortex-A9 아키텍처는 MP용도로 설계되었다. 그러나 실제 SoC 제작 시 1 코어 용으로 설계된 사례가 있다. 그래서 이경우만 아니면 SMP로 판단한다. (Aegis 플랫폼에 사용된 SoC 중 하나가 ARM Cortex-A9 with single core)

  •  여기서 부터 추가된 조건들은 2013년 9월 kernel v.3.12-rc6에 추가된 항목이다.
        @ Core indicates it is SMP. Check for Aegis SOC where a single
        @ Cortex-A9 CPU is present but SMP operations fault.
        mov     r4, #0x41000000
        orr     r4, r4, #0x0000c000
        orr     r4, r4, #0x00000090
        teq     r3, r4                  @ Check for ARM Cortex-A9
        retne   lr                      @ Not ARM Cortex-A9,
  • r4 = 0x4100_c090
  • teq r3, r4
    • ARM Cortex-A9가 아닌 경우 루틴을 빠져나간다.
5) UP case C – Cortex-A9 이면서 IO base 주소가 0이면 UP
        @ If a future SoC *does* use 0x0 as the PERIPH_BASE, then the
        @ below address check will need to be #ifdef'd or equivalent
        @ for the Aegis platform.
        mrc     p15, 4, r0, c15, c0     @ get SCU base address
        teq     r0, #0x0                @ '0' on actual UP A9 hardware
        beq     __fixup_smp_on_up       @ So its an A9 UP
  • mrc p15, 4, r0, c15, c0
    • Cortex A9이 UP로 설계되어 있는지 확인하기 위해 CBAR에서 IO 장치의 base 주소를 가져온다.
    • IO base 주소가 0이면 UP 이므로 __fixup_smp_on_up 루틴으로 이동한다.
6) UP case D – Cortex-A9 이면서 CPU 수가 1개이면 UP
        ldr     r0, [r0, #4]            @ read SCU Config
ARM_BE8(rev     r0, r0)                 @ byteswap if big endian
        and     r0, r0, #0x3            @ number of CPUs
        teq     r0, #0x0                @ is 1?
        retne   lr
  • ldr r0, [r0, #4]
    • IO base 주소 + 4의 위치에서 SCU 설정값을 읽어온다.
  • and r0, r0, #0x3
    • 이 값의 하위 2비트로 CPU 수를 표현한다.
  • teq r0, #0x0
    • 이 값이 0이 아니면 SMP로 판단하여 루틴을 빠져나간다.
      • 0=CPU 1개, 1=CPU 2개, 2=CPU 3개, 3=CPU 4개
    • 이 값이 0이면 UP로 인식하여 아래 루틴(__fixup_smp_on_up:)으로 계속 진행한다.

 

ALT_SMP() & ALT_UP() 매크로

ALT_SMP() 매크로는 SMP에서만 동작하는 명령이 들어가고 ALT_UP() 매크로는 UP 시스템에서만 동작하는 명령이 들어간다.

  • SMP 시스템에서는 smp_func() 함수를 수행하는데 UP 시스템에서는 아무것도 하지 않을 때의 예)
    • ALT_SMP(bl smp_func);
    • ALT_UP(nop);

kernel_head.s분석4

두 매크로의 규칙은 다음과 같다.

  • ALT_SMP()와 ALT_UP()는 항상 짝을 이루어 붙어 다닌다.
  • ALT_SMP()와 ALT_UP()에 들어가는 명령(instruction)은 항상 4바이트 이다.
  • ALT_SMP()는 실제 코드섹션 영역에 그대로 저장된다.
  • ALT_UP()는 .alt.smp.init 섹션에 4바이트씩 두 번 push되는데 처음 word는 ALT_SMP()의 명령주소를 가리키고 있고(나중에 치환할 주소를 알아내기 위함), 두 번째 word는 동작할 UP 코드(명령)이다.

SMP 코드를 UP 코드로 fixup

__fixup_smp_on_up:

__fixup_smp_on_up:
        adr     r0, 1f
        ldmia   r0, {r3 - r5}
        sub     r3, r0, r3
        add     r4, r4, r3
        add     r5, r5, r3
        b       __do_fixup_smp_on_up
ENDPROC(__fixup_smp)

        .align
1:      .word   .
        .word   __smpalt_begin
        .word   __smpalt_end
  • r3: offset
  • r4: __smpalt_begin
  • r5: __smpalt_end
  • 위의 레지스터를 준비한 후 __do_fixup_smp_on_up: 루틴으로 이동

 

__do_fixup_smp_on_up:

이 루틴에서 루프를 돌며 SMP 코드를 UP 코드로 치환한다.

        .text
__do_fixup_smp_on_up:
        cmp     r4, r5
        reths   lr
        ldmia   r4!, {r0, r6}
 ARM(   str     r6, [r0, r3]    )
 THUMB( add     r0, r0, r3      )
#ifdef __ARMEB__
 THUMB( mov     r6, r6, ror #16 )       @ Convert word order for big-endian.
#endif
 THUMB( strh    r6, [r0], #2    )       @ For Thumb-2, store as two halfwords
 THUMB( mov     r6, r6, lsr #16 )       @ to be robust against misaligned r3.
 THUMB( strh    r6, [r0]        )
        b       __do_fixup_smp_on_up
ENDPROC(__do_fixup_smp_on_up)
  • cmp r4, r5
    • r4(__smpalt_begin으로 시작되는 카운터)와 r5(__smpalt_end)와 비교
  • reths lr
    • 끝까지 간 경우 종료(r4 >= r5)
  • ldmia r4!, {r0, r6}
    • r4 카운터 주소가 가리키는 곳에서 2 워드를 r0와 r6레지스터로 읽어오고 r4 카운터 주소를 1 word 감소시킨다.
    • r0: ALT_SMP에 있는 instr 주소
    • r6: ALT_UP에 있는 instr 값
  • str r6, [r0, r3]
    • r6(ALT_UP에 있는 instr 값)을 r0(ALT_SMP를 가리키는 instr 주소) + r3(offset)  주소에 저장한다.
  • b __do_fixup_smp_on_up
    • 루프를 돌기 위해 처음으로 이동한다.

 

kernel/head.S – __lookup_processor_type:

__lookup_processor_type:

processor id를 사용하여 링커가 만들어내는 별도의 섹션 공간에서 지원되는 프로세서 리스트를 검색하여 찾아낸다. MMU를 사용하지 않으므로 여기서 절대주소로 __proc_info 리스트를 사용할 수 없어서 offset을 계산하여 사용해야 한다.

arch/arm/kernel/head-common.S

        __FINIT
        .text

/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 *      r9 = cpuid
 * Returns:
 *      r3, r4, r6 corrupted
 *      r5 = proc_info pointer in physical address space
 *      r9 = cpuid (preserved)
 */
__lookup_processor_type:
        adr     r3, __lookup_processor_type_data
        ldmia   r3, {r4 - r6} 
        sub     r3, r3, r4                      @ get offset between virt&phys
        add     r5, r5, r3                      @ convert virt addresses to
        add     r6, r6, r3                      @ physical address space
1:      ldmia   r5, {r3, r4}                    @ value, mask
        and     r4, r4, r9                      @ mask wanted bits
        teq     r3, r4
        beq     2f
        add     r5, r5, #PROC_INFO_SZ           @ sizeof(proc_info_list)
        cmp     r5, r6
        blo     1b
        mov     r5, #0                          @ unknown processor
2:      ret     lr
ENDPROC(__lookup_processor_type)
  • __FINIT
    • 해당 매크로엔 .previous 지시자가 담겨있다.
    • 현재 사용하고 있는 섹션 이전에 사용했던 섹션으로 변경된다.
  • r3를 사용하여 offset을 계산한다.
    • rpi2: 0x8000_0000 (물리 주소 – 가상 주소)
  • r5: __proc_info_begin 물리 주소
  • r6: __proc_info_end 물리 주소
  • ldmia r5, {r3, r4}
    • 루프의 시작으로 proc_info_list 구조체의 첫 부분에 있는 cpu value와 cpu mask를 읽어서 r3, r4에 잃어온다.
  • beq     2f
    • cpu를 판별하여 일치하면 2f 레이블로 이동하고 리턴한다.
  • add     r5, r5, #PROC_INFO_SZ
    • r5를 구조체 사이즈만큼 증가시킨다.
    • #PROC_INFO_SZ = 52
    • 구조체 proc_info_list와 동일한 사이즈
  • cmp r5, r6
    • r5가 아직 r6보다 작으면 1f 레이블로 이동하여 루프를 돈다.

 

프로세서 정보 리스트(proc_info_list)가 저장되는 곳

  • 지원되는 각종 프로세서 정보들은 .init.proc.info 섹션에 저장된다.
  • rpi2:
    • 이 위치에 저장된 정보는 ARMv7 관련된 것만 약 520여 바이트로 구성되어 있다.
    • arch/arm/mm/proc-v7.S – __v7_ca7mp_proc_info: 레이블 위치
  • 저장되는 섹션 위치를 기술한 소스

arch/arm/kernel/vmlinux.lds

.init.proc.info : {
        . = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .;
}

 

__lookup_processor_type_data: 객체

arch/arm/kernel/head-common.S

/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
        .align  2
        .type   __lookup_processor_type_data, %object
__lookup_processor_type_data:
        .long   .
        .long   __proc_info_begin
        .long   __proc_info_end
        .size   __lookup_processor_type_data, . - __lookup_processor_type_data

 

__v7_ca7mp_proc_info: 객체

아래는 ARM Cortex-A7 아키텍처가 사용하는 프로세서 정보 리스트로 proc_info_list 구조체와 동일하다.

arch/arm/mm/proc-v7.S

        /*
         * ARM Ltd. Cortex A7 processor.
         */
        .type   __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
        .long   0x410fc070
        .long   0xff0ffff0
        __v7_proc __v7_ca7mp_setup
        .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info
  • __v7_proc __v7_ca7mp_setup
    • __v7_proc 매크로
      • ARMv7 아키텍처는 모두 공통적으로 사용된다.
        • 현재 소스에는 11개 프로세서 정보 리스트가 등록되어 있다.
    • __v7_ca7mp_setup
      • ARM Cortex-A7의 설정 함수가 __v7_proc 매크로의 인수로 사용된다.
      • proc_info_list->__cpu_flush와 동일하다.

 

__v7_proc 매크로

arch/arm/mm/proc-v7.S

        .section ".rodata"

        string  cpu_arch_name, "armv7"
        string  cpu_elf_name, "v7"
        .align

        .section ".proc.info.init", #alloc, #execinstr

        /*
         * Standard v7 proc info content
         */

.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
        ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
        ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
        .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
        W(b)    \initfunc
        .long   cpu_arch_name
        .long   cpu_elf_name
        .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
                HWCAP_EDSP | HWCAP_TLS | \hwcaps
        .long   cpu_v7_name
        .long   \proc_fns
        .long   v7wbi_tlb_fns
        .long   v6_user_fns
        .long   v7_cache_fns
.endm
  • 44 바이트로 구성되어 있고 proc_info_list(56 바이트)에서 cpu value와 mask 정보 이후의 중간을 구성하는 정보이다.
  • 참고: start_kernel() – setup_arch() – setup_processor() | 문c

 

proc_info_list 구조체

arch/arm/include/asm/procinfo.h

/*      
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *      
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
struct proc_info_list {
        unsigned int            cpu_val;
        unsigned int            cpu_mask;
        unsigned long           __cpu_mm_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_io_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_flush;            /* used by head.S */
        const char              *arch_name;
        const char              *elf_name;
        unsigned int            elf_hwcap;
        const char              *cpu_name;
        struct processor        *proc;
        struct cpu_tlb_fns      *tlb;
        struct cpu_user_fns     *user;
        struct cpu_cache_fns    *cache;
};

 

compressed 커널의 내부를 hexdump하여 분석

 

kernel_head.s분석2

 

kernel_head.s분석3