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 레지스터와 연산
- 예) 길이=0x44332211 (리틀엔디안형식으로 기록된 저장소: 11 22 33 44)
- 만일 SoC가 리틀엔디안으로 구동되고 있는 경우 변환전과 변환후가 동일.
- 만일 SoC가 빅엔디안으로 구동되고 있는 경우 변환전과 변환후가 다름.
- r9(커널길이) = (r10(리틀엔디안으로 길이가 저장된 주소)
#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 손상 여부 판단
bss 사용 시 DTB 손상을 보호하기 위해 공간(gap) 추가
- 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)
- r1에 relocation 코드의 사이즈를 담는다.
- 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 주소들을 바꿔야 한다.
/* 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를 사용한다.
- _end를 사용하지 않고 _data를 사용한 이유
- r10(_end)은 compressed 커널의 마지막 끝으로 XIP 커널로 빌드된 경우가 아니면 bss 및 스택을 포함한 끝 주소이다.
- 스택을 DTB 위로 이동시킨다.
- r6(_edata)는 relocation 시 복사할 compressed 커널의 끝을 가리킨다.
참고
- start: | 문c
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- cache_on: | 문c
- cache_clean_flush: | 문c
- 영역 검사를 하여 하단부가 중복된 경우만 캐시 on 보류 | 문c
- restart: (현재 글)
- wont_overwrite: | 문c