[ARM] compressed kernel

2013/10/20linux::ARMimport

zImageの展開処理

調査対象は、【kernel 3.0.39 "Sneaky Weasel"】です。

例として、u-bootから起動する場合を考えます。
ここで上げるのは、とあるボードへの移植時に調査した値です。
ボード依存部の値は、デタラメに書いている可能性があるので、注意してください。


NOR bootの場合は、ROM上のコード実行が可能ですが、昨今の方式*1ではbootloaderがDRAM上に展開してから実行することが多いでしょう。

u-bootが読み込むファイル、uImageは、zImageにuImageヘッダがついたもの。
図にしてみると以下のイメージ。(AAでスミマセン)
+------------------------+
| u-boot header(64oct.)  |
+------------------------+
| zImage decompress code |
| .text                  |
| (with compressed data )|
| .got                   |
| .got.plt               |
| .bss                   |
+------------------------+
このデータの読み込みは、u-bootによって行われるため、任意のアドレスに展開されます。


ldscriptを見ると、ざっくりとこんな感じ。
  _text = .;
  .text : {}
  _etext = .;
  _got_start = .;
  .got			: { *(.got) }
  _got_end = .;
  .got.plt		: { *(.got.plt) }
  _edata = .;

  __bss_start = .;
  .bss			: { *(.bss) }
  _end = .;

*1 : NAND/SDから2nd boot programを読み出して、本当のローダを呼び出す。

事前準備

実際には、コードを読みながら参照していった結果です。

FILE: arch/arm/boot/compressed/Makefile
# Supply ZRELADDR to the decompressor via a linker symbol.
ifneq ($(CONFIG_AUTO_ZRELADDR),y)
LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)
ZRELADDRは、以下で定義されます。
FILE: arch/arm/boot/Makefile
ZRELADDR    := $(zreladdr-y)
右辺は、SoC依存部にて定義されています。
FILE: arch/arm/mach-*/Makefile.boot
zreladdr-y	:= 0x40008000
params_phys-y	:= 0x40000100
initrd_phys-y	:= 0x40800000


zImage先頭から

エントリーポイントはここ。

bootloaderから飛んでくるところ

アドレスは任意。

FILE: ./boot/compressed/head.S
start:
 ※cache有効, TLBはVA-PA一対一対応, 

restart:
		.align	2
		.type	LC0, #object
LC0:
		.word	LC0					@ r1
		.word	__bss_start			@ r2
		.word	_end				@ r3
		.word	_edata				@ r6
		.word	input_data_end - 4	@ r10 (inflated size location)
		.word	_got_start			@ r11
		.word	_got_end			@ ip(r12)

		.word	.L_user_stack_end	@ sp

		.size	LC0, . - LC0

		/*
		 * 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
LC0(DRAMの位置)から、"LC0"の配置した位置(ldscriptでは先頭0、コード配置後のLC0の位置)を差っ引いて、r0に保持する。
圧縮カーネルの、展開後のサイズは、build systemによって、圧縮カーネルの後ろにLEで付与される。したがって、r10へloadする値は、input_data_endから4引いてある。(32bit決め打ちだね)
spも、relocate後のアドレスをセット。r10にsp+64kのアドレスをセット(malloc用に64k確保)(CONFIG_ZBOOT_ROM未定義の場合。定義時はコード本体がROMに入るから、_edataの後ろに配置する)

このイメージがロードされる位置がどこでも動けるようになっている。

展開後のkernel位置と、この展開コード、edata類の空間が重なっているかをチェックする。重複している場合、reset〜edataまでを、後ろからコピーする(memmove相当)。その後、コピーした後のrestartへjumpする。(これで重複しない位置で再度relocateする)

※ユーザモードで入ってきた場合、SVCで特権を得るようになってます。
ヴェクタ位置へのロードを期待しているようですが、必ずしもそうではないから、ちょっと不味そうな気がするネ。

relocate後

decompress処理のソフトと、圧縮イメージとを再配置して、kernel imageを展開できる状態になって、wont_overwriteへたどり着く。前述のように、最初のバイナリがロードされる位置によって、ここへ直接来ることもあれば、relocateすることもありますね。
wont_overwrite:
/*
 * If delta is zero, we are running at the address we were linked at.
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
・GOT書き換え(オフセット分加算して、飛び先がrelocate先のアドレスになるように)
・BSSをゼロクリア


本来のImage(vmlinux)へ

kernelの展開先アドレスを第一引数、spからsp+64kを第二〜三引数に渡して、第四引数にアーキテクチャIDをのせてcall。
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, unsigned long free_mem_ptr_end_p, int arch_id)
展開が終わったら、cache clean、cache-offしてから、圧縮前のkernel先頭へ飛ぶ。