硬件: TQ2440
内核: 2.6.25
uboot将内核从nand flash读到内存的0x3000800处,并解压,此时:
r0 = 0
r1 = machine_number (uboot中设为168)
r2 = 0x30008000 (r2不是参数地址)
真正的参数是在uboot的setup_linux_param设置的,
uboot1.1.6/lib_arm/test_zImage.c
int test_zImage(void)
--> setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET); //将参数拷贝到了0x3000100处
注意: 虽然跳过了uncompress阶段,但是arch / arm / kernel / head . S还是运行在0x3000800处。
一、 整体分析
1.1 从arch/arm/kernel/vmlinux.lds.S可以看出
.text.head : {
- _stext = .;
- _sinittext = .;
- *(.text.head)
-
}
- 79 ENTRY(stext)
- 80 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
- 81 @ and irqs disabled
- 82 mrc p15, 0, r9, c0, c0 @ get processor id
- 83 bl __lookup_processor_type @ r5=procinfo r9=cpuid
- 84 movs r10, r5 @ invalid processor (r5=0)?
- 85 beq __error_p @ yes, error 'p'
- 86 bl __lookup_machine_type @ r5=machinfo
- 87 movs r8, r5 @ invalid machine (r5=0)?
- 88 beq __error_a @ yes, error 'a'
- 89 bl __vet_atags
- 90 bl __create_page_tables
- 91
- 92 /*
- 93 * The following calls CPU specific code in a position independent
- 94 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
- 95 * xxx_proc_info structure selected by __lookup_machine_type
- 96 * above. On return, the CPU will be ready for the MMU to be
- 97 * turned on, and r0 will hold the CPU control register value.
- 98 */
- 99 ldr r13, __switch_data @ address to jump to after
- 100 @ mmu has been enabled
- 101 adr lr, __enable_mmu @ return (PIC) address
- 102 add pc, r10, #PROCINFO_INITFUNC
将CPSR的I与F位置1,关闭IRQ与FRQ,同时模式设为SVC模式
82行 将cpu_id读到r9中
83行 查找_proc_info段,看有没有与以硬件寄存器中的cpu_id相匹配的proc_info, r5指向查找到的procinfo
84行 将r5保存在r10中,即r10指向查找到的procinfo的首地址
85行 为0,打印错误信息
86行 在arch_info中查找machine_nr=r1=168的machine_desc, r5指向查找到的machine_desc
87行 将r5保存在r8中,即r8是指向machine_desc的首地址
88行 为0,打印错误信息
89行 检查参数, 因为r2=0x30008000,不是参数列表的地址,所以执行完后r2=0
90行 创建页表
99-102行 执行顺序 initfunc->__enble_mmu->switch_data->start_kernel
1.2 试想如果有以下要求,自己怎么写?
把r5结果保存到r10中,并且判断是否为0,为0则打印错误
肯定会写出:
mov r10, r5
cmp r10, #0
beq __error_p
但是还有更简炼的写法:
movs r10 , r5
beq __error_p
二、__lookup_processor_type分析
2.1 在文件arch/arm/kernel/head_common.S中
- 151 .type __lookup_processor_type, %function
- 152 __lookup_processor_type:
- 153 adr r3, 3f //L184行的运行地址(0x30008000+offset)存到r3中
- 154 ldmda r3, {r5 - r7} //r5=_proc_info_begin; r6=__proc_info_end; r7=3f; r5,r6,r7都是程序的存储地址
- 155 sub r3, r3, r7 @ get offset between virt&phys // r3=r3-r7=0x30008000
- 156 add r5, r5, r3 @ convert virt addresses to // r5=r5+r3=0x30008000+__proc_info_begin(即r5指向当前内存中第一个proc_info结构体首地址)
- 157 add r6, r6, r3 @ physical address space // r6=r6+r3=0x30008000+__proc_info_end
- 158 1: ldmia r5, {r3, r4} @ value, mask // r3=cpu_val=0x41009200; r4=cpu_mask=0xff00fff0
- 159 and r4, r4, r9 @ mask wanted bits // r4=r4&r9 r9是从协处理器中读取出来的cpu_val
- 160 teq r3, r4 // 将程序中的cpu_val与硬件中的cpu_val相比较看是否相同
- 161 beq 2f //相等说明找到了这个结构体,就返回
- 162 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list) //不相等查找下一个结构体
- 163 cmp r5, r6
- 164 blo 1b
- 165 mov r5, #0 @ unknown processor
- 166 2: mov pc, lr
- 167
- 168 /*
169 * This provides a C-API version of the above function.
170 */
171 ENTRY(lookup_processor_type)
172 stmfd sp!, {r4 - r7, r9, lr}
173 mov r9, r0
174 bl __lookup_processor_type
175 mov r0, r5
176 ldmfd sp!, {r4 - r7, r9, pc}
177
178 /*
179 * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
180 * more information about the __proc_info and __arch_info structures.
181 */
182 .long __proc_info_begin
183 .long __proc_info_end
184 3: .long .
185 .long __arch_info_begin
186 .long __arch_info_end
- arch/arm/kernel/asm-offsets.c
- 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;
- };
- .init : { /* Init code and data */
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
}
- 同时 arch/arm/mm/proc-arm920.S中
- .align
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object - __arm920_proc_info:
- .long 0x41009200
- .long 0xff00fff0
- .long PMD_TYPE_SECT | \
- PMD_SECT_BUFFERABLE | \
- PMD_SECT_CACHEABLE | \
- PMD_BIT4 | \
- PMD_SECT_AP_WRITE | \
- PMD_SECT_AP_READ
- .long PMD_TYPE_SECT | \
- PMD_BIT4 | \
- PMD_SECT_AP_WRITE | \
- PMD_SECT_AP_READ
- b __arm920_setup
- .long cpu_arch_name
- .long cpu_elf_name
- .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
- .long cpu_arm920_name
- .long arm920_processor_functions
- .long v4wbi_tlb_fns
- .long v4wb_user_fns
- #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
- .long arm920_cache_fns
- #else
- .long v4wt_cache_fns
- #endif
- .size __arm920_proc_info, . - __arm920_proc_info
3.1 在文件arch/arm/kernel/head_common.S中
182 .long __proc_info_begin
- 183 .long __proc_info_end
- 184 3: .long .
- 185 .long __arch_info_begin
- 186 .long __arch_info_end
- 199 .type __lookup_machine_type, %function
- 200 __lookup_machine_type:
- 201 adr r3, 3b // r3=r3-r7=0x30008000;小三还是可以重复利用的
- 202 ldmia r3, {r4, r5, r6} // r4=3f, r5=arch_info_begin; r6=arch_info_end; arch_info紧跟 proc_info
- 203 sub r3, r3, r4 @ get offset between virt&phys // r3=r3-r4=0x30008000
- 204 add r5, r5, r3 @ convert virt addresses to // r5=r5+r3
- 205 add r6, r6, r3 @ physical address space // r6=r6+r3
- 206 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type // r3=struct machine_desc->nr,因machine_nr并没有存在结构体的首位,所以需要加上偏移
- 207 teq r3, r1 @ matches loader number? // r3是较内核中的machine_nr, r1是uboot传入的machine_nr,比较两者是否相同
- 208 beq 2f @ found
- 209 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc // 不相同,则查找下一个machine_desc
- 210 cmp r5, r6
- 211 blo 1b
- 212 mov r5, #0 @ unknown machine
- 213 2: mov pc, lr
.init : { /* Init code and data */
- __proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
} - arch_info紧跟 proc_info
-
- 3.2.1 在arch/arm/mach/arch.h中
- struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
- #define MACHINE_START(_type,_name) \
- static const struct machine_desc __mach_desc_##_type \
- __used \
- __attribute__((__section__(".arch.info.init"))) = { \
- .nr = MACH_TYPE_##_type, \
- .name = _name,
- #define MACHINE_END \
- }
对结构体machine_desc进行初始化
MACHINE_START(S3C2440, "SMDK2440")
- /* Maintainer: Ben Dooks <ben@fluff.org> */
- .phys_io = S3C2410_PA_UART,
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100,
- .init_irq = s3c24xx_init_irq,
- .map_io = smdk2440_map_io,
- .init_machine = smdk2440_machine_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
四、检查uboot的参数
4.1 在文件arch/arm/kernel/head_common.S中
238 .type __vet_atags, %function
- 239 __vet_atags:
- 240 tst r2, #0x3 @ aligned?
- 241 bne 1f
- 242
- 243 ldr r5, [r2, #0] @ is first tag ATAG_CORE?
- 244 subs r5, r5, #ATAG_CORE_SIZE
- 245 bne 1f
- 246 ldr r5, [r2, #4]
- 247 ldr r6, =ATAG_CORE
- 248 cmp r5, r6
- 249 bne 1f
- 250
- 251 mov pc, lr @ atag pointer is ok
- 252
- 253 1: mov r2, #0
- 254 mov pc, lr
附:
1.协处理寄存器->arm寄存器
mrc {} p15, 0, , , {,}
a. 条件码,忽略时无条件执行
b. 永远为 0
c. , 保存结果
d. , 协处理器中的寄存器
d. , 协寄存器的附加信息,当不需要附加信息时,要设为c0
d. , 附加信息,省略时默为0
2: 指令
2.1 movs指令
S决定指令操作是否影响CPSR的值
2.2 ldmda指令:
使用多数据传送指令(LDM 和 STM)来装载和存储多个字的数据从/到内存。
LDM/STM 的主要用途是把需要保存的寄存器复制到栈上。如我们以前见到过的 STMFD R13!, {R0-R12, R14}。
指令格式是:
xxM{条件}{类型} Rn{!}, <寄存器列表>{^}
‘xx’是 LD 表示装载,或 ST 表示存储。
再加 4 种‘类型’就变成了 8 个指令:
栈 其他
LDMED (空递减) LDMIB 预先增加装载
LDMFD (满递减) LDMIA 过后增加装载
LDMEA (空递增) LDMDB 预先减少装载
LDMFA ?(满递增) LDMDA 过后减少装载
STMFA ?(满递增) STMIB 预先增加存储
STMEA (空递增) STMIA 过后增加存储
STMFD (满递减) STMDB 预先减少存储
STMED ?(空递减) STMDA 过后减少存储
关于空递减和满递减等等,理解:
指针存储往减少方向发展的是“递减”,反之,存储往增加方向发展的是“递增”。
指针指向下一个存储位置的是“空”,反之,指针指向最后一个存储位置的是“满”。
[参考文章]
1. arm linux kernel 从入口到start_kernel的代码分析
http://bbs.chinaunix.net/thread-2039668-1-1.html
2. Linux 内核启动分析之"arch/arm/kernel/head.S"
http://blog.sina.com.cn/s/blog_63ac1cef0100vbcb.html
3. linux内核启运过程分析
http://chxxxyg.blog.163.com/blog/static/150281193201072603030285/
4. arm体系结构与编程-杜春雷