compressed/head.S – restart:

restart:

restart:        adr     r0, LC0
                ldmia   r0, {r1, r2, r3, r6, r10, r11, r12}
                ldr     sp, [r0, #28]

                /*
                 * We might be running at a different address.  We need
                 * to fix up various pointers.
                 */

                sub     r0, r0, r1              @ calculate the delta offset
                add     r6, r6, r0              @ _edata
                add     r10, r10, r0            @ inflated kernel size location

#ifndef CONFIG_ZBOOT_ROM
                /* malloc space is above the relocated stack (64k max) */
                add     sp, sp, r0
                add     r10, sp, #0x10000
#else
                /*
                 * With ZBOOT_ROM the bss/stack is non relocatable,
                 * but someone could still run this code from RAM,
                 * in which case our reference is _edata.
                 */
                mov     r10, r6
#endif

                mov     r5, #0                  @ init dtb size to 0
  • restart:
    • 이 위치는 압축이 풀려지는 위치와 현재 압축된 커널의 공간이 중복되는 경우 현재의 코드(압축된커널 + 재배치코드(restart-끝))를 이동시킨 후 다시 restart 이 위치로 점프된다.
  • ldr r0, LC0
    • 실제 동작중인 LC0의 주소를 r0에 읽어온다.
    • ldr 명령을 사용하지 않는 이유는 코드가 relocation 되어 다시 이 루틴에 진입하는 경우 LC0의 위치가 변한다. 따라서 현재 코드가 수행되고 있는 위치(pc)로 부터 LC0까지의 위치를 상대적으로 계산하여 주소를 알아와야 하기 때문에 adr 명령을 사용해야 한다. ldr 명령은 컴파일 시 계산된 주소로부터 LC0까지의 상대적인 주소 값을 반영하여 가져오는것과는 비교되어야 한다.
  • ldr sp, [r0, #28]
    • 스택을 설정한다. sp <- L_user_stack_end(stack + 4K)
  • delta 값 계산
    • r0에 현재 실행되는 상태의 LC0의 주소 값이 담기고, r1은 처음 이미지가 만들어질 때 당시의 주소 값(0을 기준으로)이 담겨 있고 이 둘의 차이 값이 r0(delta) 값이 된다.이 델타 값으로 Image의 위치를 가리키는 레지스터들을 보정하는데 사용한다.
    • 코드가 재배치가 이루어진 후 이곳으로 재진입하게 되면 또 한 번 delta가 바뀌고 이 때에도 바뀐 주소로인해 관련 레지스터들의 주소를 교정한다.
  • r6 = _edata + delta
  • r10 = _input_data_end-4 + delta
    • _input_data_end-4 = compressed 커널(piggy)의 마지막 어드레스를 의미함.
    • 데이터 내용은 decompressed 커널의 길이.
                /*
                 * The kernel build system appends the size of the
                 * decompressed kernel at the end of the compressed data
                 * in little-endian form.
                 */
                ldrb    r9, [r10, #0]
                ldrb    lr, [r10, #1]
                orr     r9, r9, lr, lsl #8
                ldrb    lr, [r10, #2]
                ldrb    r10, [r10, #3]
                orr     r9, r9, lr, lsl #16
                orr     r9, r9, r10, lsl #24
  • decompressed 커널 길이가 기록될 때 리틀엔디안으로 기록이되며 이를 엔디안과 관계 없이 알아온다.
    • r9(커널길이) = (r10(리틀엔디안으로 길이가 저장된 주소)
      • 예) 길이=0x44332211 (리틀엔디안형식으로 기록된 저장소: 11 22 33 44)
        • 0x00000011 <- 처음 바이트만 r9에 가져옮
        • 0x00000011 <- 처음 바이트만 r9에 가져옮
        • 0x00000022 <- 두번째 바이트를 lr에 가져옮
        • 0x00002211 <- lr레지스터 내용을 8회 shift후 r9 레지스터와 or 연산
        • 0x00000033 <- 세번째 바이트를 lr에 가져옮
        • 0x00000044 <- 네번째 바이트를 r10에 가져옮
        • 0x00332211 <- lr레지스터 내용을 16회 shift후 r9 레지스터와 or 연산
        • 0x44332211 <- r10레지스터 내용을 24회 shift후 or r9 레지스터와 연산
    • 만일 SoC가 리틀엔디안으로 구동되고 있는 경우 변환전과 변환후가 동일.
    • 만일 SoC가 빅엔디안으로 구동되고 있는 경우 변환전과 변환후가 다름.
#ifndef CONFIG_ZBOOT_ROM
                /* malloc space is above the relocated stack (64k max) */
                add     sp, sp, r0
                add     r10, sp, #0x10000
#else
                /*
                 * With ZBOOT_ROM the bss/stack is non relocatable,
                 * but someone could still run this code from RAM,
                 * in which case our reference is _edata.
                 */
                mov     r10, r6
#endif
  • CONFIG_ZBOOT_ROM
    • ROM이나 플래쉬에 있는 compressed 이미지를 RAM으로복사하지 않고 직접 구동하여 사용하려고 할 때 사용.
    • rpi2:
      • 각 패키징 업체가 준비해 놓은 설정을 사용하는데 특정패키징을 사용하지 않고 다운로드한 라즈베리파이 원 소스의 기본 설정으로빌드하였을 때 이 옵션은 사용하지 않는 것으로 되어 있음.
      • 라즈베리안이라는 기본 패키징도 마찬가지로 이 옵션을 사용하지 않음
  • RAM에서 구동하는 경우
    • 기존에 설정한 스택 주소를 delta만큼 재조정
    • r10에 64K의 버퍼 마지막을 저장(delta 재조정된 스택 + 64K)
  • ROM에서 구동하는 경우
    • 부트롬 또는 플래쉬에서 이미지 코드를 직접 동작시킨 경우 r10(이미지의 끝)에 r6(_edata)를 대입한다.

DTB 사이즈 관련

                mov     r5, #0                  @ init dtb size to 0

#ifdef CONFIG_ARM_APPENDED_DTB
/*
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = final kernel address (possibly with LSB set)
 *   r5  = appended dtb size (still unknown)
 *   r6  = _edata
 *   r7  = architecture ID
 *   r8  = atags/device tree pointer
 *   r9  = size of decompressed image
 *   r10 = end of this image, including  bss/stack/malloc space if non XIP
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 *
 * if there are device trees (dtb) appended to zImage, advance r10 so that the
 * dtb data will get relocated along with the kernel if necessary.
 */


                ldr     lr, [r6, #0]
#ifndef __ARMEB__
                ldr     r1, =0xedfe0dd0         @ sig is 0xd00dfeed big endian
#else
                ldr     r1, =0xd00dfeed
#endif
                cmp     lr, r1
                bne     dtb_check_done          @ not found
  • CONFIG_ARM_APPENDED_DTB
    • compressed 커널뒤에 DTB를 붙여서 제공하는 경우 enable 하여 빌드한다.
    • 부트로더는 커널이 DTB를 찾을 수 있도록 compressed 커널의 바로 뒤에 공백없이 붙여야 한다.
  • DTB(Device Tree Blob)가 compressed 커널 뒤에 존재하는지 확인한다
    • DTB 매직넘버를 찾아낸다.
    • r6(_edata = _bss_start)가 가리키는 곳에 0xd00dfeed라는 DTB magic number)가 있는지 확인하여 없으면 dtb_check_done 레이블로 이동
#ifdef CONFIG_ARM_ATAG_DTB_COMPAT
                /*
                 * OK... Let's do some funky business here.
                 * If we do have a DTB appended to zImage, and we do have
                 * an ATAG list around, we want the later to be translated
                 * and folded into the former here. No GOT fixup has occurred
                 * yet, but none of the code we're about to call uses any
                 * global variable.
                */

                /* Get the initial DTB size */
                ldr     r5, [r6, #4]

#ifndef __ARMEB__

                /* convert to little endian */
                eor     r1, r5, r5, ror #16
                bic     r1, r1, #0x00ff0000
                mov     r5, r5, ror #8
                eor     r5, r5, r1, lsr #8
#endif
  • CONFIG_ARM_ATAG_DTB_COMPAT
    • DTB를 뿐만 아니라 옛날 부트로더가 사용한 ATAG도 지원한다.
    • DTB를 지원하지 못하는 오래된 부트로더로 부터 전달받은 ATAG로 부터 받은 정보는 메모리 정보, 램디스크 주소, 커널 cmdline, etc… 등이 있고 이들 정보는 모두 DTB로 변환되어 사용한다.
    • 특이하게 DTB와 ATAG 둘 다 사용해야 하는 경우가 있다. 이런 경우 DTB 사이즈가 부족한 경우가 있어 이를 일정부분 확장해서 사용해야 한다.
    • 처음에 하드코딩된 64k의 DTB 사이즈 때문에 ATAG 정보를 추가하면 용량이 부족해서 DTB 사이즈를 읽어서 확장(32K ~ 1M)시키는 코드가 추가되었다.
    • 참고: ATAG_DTB_COMPAT: remove the DT workspace’s hardcoded 64K… 
  • ldr, r5, [r6, #4]
    • r5 <- 초기 DTB size를 읽어온다. (fdt_header->dt_total_size(fdt_size))
  • 시스템이 리틀엔디안으로 동작하는 경우 읽은 DTB 사이즈(DTB는 항상 빅엔디안으로 저장)를 리틀엔디안으로 변환.
  • 다음의 알고리즘을 사용하여 빅엔디안 바이트들을 리틀 엔디안으로 변환한다. (A=msb, D=lsb)
 *         r5          A    B    C    D
 *              ror    C    D    A    B
 *              eor   A^C  B^D  C^A  D^B
 *              bic   A^C   0   C^A  D^B
 *              ror    D    A    B    C       
 *              lsr    0   A^C   0   C^A
 *         r5          D    C    B    A
                /* 50% DTB growth should be good enough */
                add     r5, r5, r5, lsr #1
                /* preserve 64-bit alignment */
                add     r5, r5, #7
                bic     r5, r5, #7
                /* clamp to 32KB min and 1MB max */
                cmp     r5, #(1 << 15)
                movlo   r5, #(1 << 15)
                cmp     r5, #(1 << 20)
                movhi   r5, #(1 << 20)
                /* temporarily relocate the stack past the DTB work space */
                add     sp, sp, r5
  • DTB 사이즈를 50% 증가. (r5 += shift to right) 증가시킨다.
  • 사이즈를 7 byte를 더한 후 8 byte round down align 한다.
    • (64비트 align 하기위해 7바이트를 추가)
    • 이렇게 7바이트를 더해놔야 마지막 데이터를 안전하게(8바이트) 꺼낼 수 있다.
    • 데이터를 읽는 루틴에서 항상 8바이트씩 읽기 때문에 8바이트 align이 중요하다.
  • DTB 사이즈가 32KB보다 작으면 32K로 설정, 1M보다 크면 1M로 제한한다
    • clamp는 하위와 상위 범위 제한을 두어 벗어나는 경우 잘라내어 항상 범위내에 있을 수 있게 한다.
  • 스택을 임시적으로 DTB 데이터 작업 영역 위로 설정한다.
    • C 함수를 사용할 때에는 스택이 필요하다.
                stmfd   sp!, {r0-r3, ip, lr}
                mov     r0, r8
                mov     r1, r6
                mov     r2, r5
                bl      atags_to_fdt

                /*
                 * If returned value is 1, there is no ATAG at the location
                 * pointed by r8.  Try the typical 0x100 offset from start
                 * of RAM and hope for the best.
                 */
                cmp     r0, #1
                sub     r0, r4, #TEXT_OFFSET
                bic     r0, r0, #1
                add     r0, r0, #0x100
                mov     r1, r6
                mov     r2, r5
                bleq    atags_to_fdt

                ldmfd   sp!, {r0-r3, ip, lr}
                sub     sp, sp, r5
#endif

                mov     r8, r6                  @ use the appended device tree
  • stmfd
    • 레지스터 몇 개를 잠시 백업 목적으로 스택에 보관한다.
    • stmfd = full descending = decrement before
  • atags_to_fdt()
    • ATAG 정보가 별견되면 분석하여 DTB로 변환한다.
    • 전달하는 인수:
      • r0:      void *atag_list         = ATAG 또는 DTB가 놓인 주소
      • r1:      void *fdt                     = _edata (커널 데이터의 마지막 주소)
      • r2:      int total_space         = DTB 사이즈
  • 만일 설정된 장소에서 ATAG를 발견하지 못하면 1이 리턴되는데 이 때 다시 한 번 시작램+0x100에서 ATAG 발견을 시도한다.
    • 물론 리눅스는 ATAG를 시작램(물리메모리)+0x100에 위치하도록 추천함.
    • r4=decompressed 커널 시작 주소
  • ldmfd
    • 백업해둔 레지스터를 다시 복원한다.
  • 임시로 스택을 사이즈 조절된 DTB 만큼 위로 올려놨던 것을 다시 원위치 시킨다.

bss 사용 시 DTB 손상 여부 판단

decompress_head.s분석12

 

bss 사용 시 DTB 손상을 보호하기 위해 공간(gap) 추가

 

decompress_head.s분석13

 

  • DTB가 decompressed kernel의 bss 영역안에 위치하지 않도록 하여야 한다.
  • .bss 영역 사이즈가 재배치 코드 사이즈(_edata – wont_overwrite)보다 큰 경우 재배치 코드 뿐만 아니라 DTB 영역도 bss 영역에 들어가게된다.
  • relocation 되고 압축이 풀려 decompressed 커널이 동작할 때에 relocation code는 삭제되어도 상관없다. 그러나 DTB 영역은 보존된 상태에 있어야 한다.
  • decompressed 커널이 동작하면 decompressed 커널의 바로 상단에 위치한 bss 영역이 사용되면서 그 영역이 DTB 영역과 겹치는 경우 DTB가 손상이 갈 것이다.
  • 그렇기 때문에 bss 영역과 relocation 코드가 겹치는 것은 상관이 없지만 DTB 코드영역까지 겹치면 안되기 때문에 이러한 영우를 피하기 위해 bss 사이즈가 relocation 영역 크기보다 큰 경우에는 그 초과되는 사이즈 만큼 r9(decompressed 커널 사이즈)에 추가하여 relocation 코드가 더 위쪽으로 자리할 수 있도록 한다.
                /*
                 * Make sure that the DTB doesn't end up in the final
                 * kernel's .bss area. To do so, we adjust the decompressed
                 * kernel size to compensate if that .bss size is larger
                 * than the relocated code.
                 */
                ldr     r5, =_kernel_bss_size
                adr     r1, wont_overwrite
                sub     r1, r6, r1
                subs    r1, r5, r1
                addhi   r9, r9, r1
  • ldr r5, =_kernel_bss_size
  • adr     r1, wont_overwrite
    • r1에 wont_overwrite 주소를 일단 담는다.
  • sub     r1, r6, r1
    • r1에 relocation 코드의 사이즈를 담는다.
      • r1 = _edata – addr(wont_overwrite)
  • subs    r1, r5, r1
    • kernel bss size에서 r1(relocation 코드 사이즈)을 뺀다.
  • addhi   r9, r9, r1
    • kernel bss size가 r1보다 크면 DTB 영역의 시작 주소가 bss 영역에 들어가게되므로 이를 방지하기 위해 decompressed 커널 사이즈가 담긴 r9을 r1(bss가 재배치 코드를 초과한 사이즈) 만큼 더 키운다.

DTB 사이즈를 다시 읽어 몇 개 주소에 반영

위에서 CONFIG_ARM_ATAG_DTB_COMPAT 옵션을 사용하면서 DTB 사이즈를 읽어온 것과 달리 CONFIG_ARM_ATAG_DTB_COMPAT 옵션을 사용하지 않더라도 DTB를 사용하는 경우 DTB 사이즈만큼 relocation 주소들을 바꿔야 한다.

decompress_head.s분석14

 

                /* Get the current DTB size */
                ldr     r5, [r6, #4]
#ifndef __ARMEB__
                /* convert r5 (dtb size) to little endian */
                eor     r1, r5, r5, ror #16
                bic     r1, r1, #0x00ff0000
                mov     r5, r5, ror #8
                eor     r5, r5, r1, lsr #8
#endif

                /* preserve 64-bit alignment */
                add     r5, r5, #7
                bic     r5, r5, #7

                /* relocate some pointers past the appended dtb */
                add     r6, r6, r5
                add     r10, r10, r5
                add     sp, sp, r5
dtb_check_done:
#endif
  • ldr     r5, [r6, #4]
    • dtb 사이즈를 다시 r5에 로드한다.
    • 다시 아키텍처가 빅엔디안인 경우 리틀엔디안으로 컨버팅한다.
    • 64비트 alignment를 수행한다.
  • 다음 3 개의 주소에 대해 DTB 사이즈를 더해준다.
    • r6(_edata)는 relocation 시 복사할 compressed 커널의 끝을 가리킨다.
      • _end를 사용하지 않고 _data를 사용한 이유
        • _data는 compressed 커널의 data 섹션의 끝 부분으로 compressed 커널의 bss 영역 시작과 같다. relocation 시 compressed 커널의 bss 영역은 더 이상 사용할 데이터가 없으므로 복사를 하지 않아도 된다. 따라서 _end 대신 _data를 사용한다.
    • r10(_end)은 compressed 커널의 마지막 끝으로 XIP 커널로 빌드된 경우가 아니면 bss 및 스택을 포함한 끝 주소이다.
    • 스택을 DTB 위로 이동시킨다.

 

참고

댓글 남기기