通常做bootloader和kernel的人都会对memory layout感兴趣,最近修改了一次memory/flash layout,先记录下来:
1.flash layout
flash layout是rom的分布图,通常直接看partion table就能直接明了。
2.memory layout
通常android下memory 可能分成以下几个部分:
bootloader/kernel/share memory。
目前memory一共2个G,划分如下:
1)0M~64M(0x4000000) reserver for Trustzoom
2)64M~72M(0x4800000) reserver for later use
3) 72M~73M(0x4900000) for sysinit
4) 73M~79M(0x4f00000) for ZSP
5)79M~80M(0x50000000) for bootloader
6)80M~1360M(0x55000000) for kernel
7)1360M~2048M end for SHM
1)是ARM TZ加密安全内核,可以看作一个kernel
2)中的64-66M用于sys_init加载bootloader,66-72M用于bootloader加载logo等,原因:eg,在bootloader中没有malloc函数,
所以在读取flash上的logog等分区数据时,直接保存到指定内存地址。
3)5)是bootloader部分.
4)用于ARM TZ加密内核其他部分
因此在非安全启动的时候,启动流程是3)-> 5) -> 6)
2.1 修改sysinit、bootloader的memory
在修改sysinit、bootloader的memory时,其实就是修改各自的 .lds
eg,bootloader.lds
26 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
27 OUTPUT_ARCH(arm)
28 ENTRY(BootLoaderEntry)
29 SECTIONS
30 {
31 . = 0x04200000;
32 __bootloader_buf_start = .;
33
34 . = 0x04F00000;
35 . = ALIGN(0x4000);
36 __ttb_base_start = .;
.......
75 . = 0x05000000;
76 . = ALIGN(4);
77 __irq_stack_start = .;
78 }
在前面2)中定义memory 66M(0x04200000)-72M用于bootloader读取flash logo等分区的数据存储,所以直接在lds中定义了__bootloader_buf_start,然后在bootloader中直接使用这个内存变量:
extern int __bootloader_buf_start;
#define BOOTLOADER_BUF_ADDR ((unsigned int)(&__bootloader_buf_start))
下面34行修改bootloader的加载内存地址为79M(0x4F00000),75行结束地址为80M。
sys_init如同bootloader。
2.2 改kernel的memory地址
如果启动的是android,那么首先要修改的是BoardConfig.mk中的
BOARD_KERNEL_BASE := 0x05000000(79M)
因为linux kernel和ramdisk在android中会被打包到boot.img中,并且会给boot.img加一个头:
system/core/mkbootimg$ vi bootimg.h
28 struct boot_img_hdr
29 {
30 unsigned char magic[BOOT_MAGIC_SIZE];
31
32 unsigned kernel_size; /* size in bytes */
33 unsigned kernel_addr; /* physical load addr */
34
35 unsigned ramdisk_size; /* size in bytes */
36 unsigned ramdisk_addr; /* physical load addr */
37
38 unsigned second_size; /* size in bytes */
39 unsigned second_addr; /* physical load addr */
40
41 unsigned tags_addr; /* physical addr for kernel tags */
42 unsigned page_size; /* flash page size we assume */
43 unsigned unused[2]; /* future expansion: should be 0 */
44
45 unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
46
47 unsigned char cmdline[BOOT_ARGS_SIZE];
48
49 unsigned id[8]; /* timestamp / checksum / sha1 / etc */
50 };
+----------------------+
| boot header | 1 page
+----------------------+
| kernel | n pages
+----------------------+
| ramdisk | m pages
+----------------------+
| second stage | o pages
+----------------------+
boot.img结构就是上面的样子。
bootloader或uboot在加载kernel时会先去读这个头,而这个头中kernel_addr就是BoardConfig.mk中定义的:
BOARD_KERNEL_BASE := 0x05000000
当然也可以不定义这个变量那么kernel_addr默认为0x01000000.
bootloader中读到这个值之后将会把它赋值给TheKernel函数,从而可以正常去加载解压内核。
然后修改linux,如果kernel是atags启动的需要修改bootloader的memory size参数,还要记得ramdisk参数。
是dts启动就不需要,只要在dts中修改linux的可用内存为79M~1360M,share memory为1360M~2048M :
@@ -62,7 +62,7 @@
memory {
name = "memory";
device_type = "memory";
- reg = <0x01000000 0x55000000>;
+ reg = <0x05000000 0x55000000>;
};
chosen {
@@ -419,7 +419,7 @@
shm@69000000 {
compatible = "mrvl,berlin-shm";
- reg = <0x56000000 0x2a000000>, <0x80000000 0x0>;
+ reg = <0x5A000000 0x26000000>, <0x80000000 0x0>;
};
由于kernel是压缩的所以刚才80M的地址是未解压kernel的内存地址不一定是kernel的真正运行地址,我把其运行地址也改为80M:
修改linux config文件:
- CONFIG_PHYS_OFFSET=0x01000000
+ CONFIG_PHYS_OFFSET=0x05000000
如果CONFIG_PHYS_OFFSET没有定义,也就可以不修改了,但是前提是配置了ARM_PATCH_PHYS_VIRT and AUTO_ZRELADDR,只要满足对齐条件:
因为linux/arch/arm$ vi boot/compressed/head.S中
170 #ifdef CONFIG_AUTO_ZRELADDR
171 @ determine final kernel image address
172 mov r4, pc
173 and r4, r4, #0xff000000
174 add r4, r4, #TEXT_OFFSET
175 #else
176 ldr r4, =zreladdr
177 #endif
可以看到如果定义了CONFIG_AUTO_ZRELADDR,则按照要求地址对齐的方式去加载内核,否者使用zreladdr,zreladdr就是在Makefile.boot:
CONFIG_PHYS_OFFSET + 0x8000得到的。
然而如果定义了CONFIG_AUTO_ZRELADDR,这表示只要地址按照一定方式对齐即可,eg:
and r4, r4, #0xff000000
表示16M对齐,#0xfff00000则是1M对齐。
最后有一个疑问:为什么我见过的板子都是默认16M对齐的,比如三星也是0x01000000,高通 、marvell也是16M对齐,其实我试过不是16M对齐也可以启动。
下面的提交可能说明了这个问题:
commit dc21af99fadcfa0ae65b52fd0895f85824f0c288
Author: Russell King <rmk+kernel@arm.linux.org.uk>
Date: Tue Jan 4 19:09:43 2011 +0000
ARM: P2V: introduce phys_to_virt/virt_to_phys runtime patching
This idea came from Nicolas, Eric Miao produced an initial version,
which was then rewritten into this.
有如下代码和解释:
+#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-r5, r7}
+ sub r3, r0, r3 @ PHYS_OFFSET - PAGE_OFFSET
+ add r4, r4, r3 @ adjust table start address
+ add r5, r5, r3 @ adjust table end address
+ str r8, [r7, r3]! @ save computed PHYS_OFFSET to __pv_phys_offset
+ mov r6, r3, lsr #24 @ constant for add/sub instructions
+ teq r3, r6, lsl #24 @ must be 16MiB aligned
+ bne __error
+ str r6, [r7, #4] @ save to __pv_offset
+ b __fixup_a_pv_table