分析 arm linux的启动(一)

来源:互联网 发布:微领袖学堂源码 编辑:程序博客网 时间:2024/05/01 06:27
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)


连接的虚拟地址为
. = PAGE_OFFSET + TEXT_OFFSET;
CONFIG_PAGE_OFFSET=0xC0000000  配置文件 .config 中设定
textofs-y := 0x00008000  在 arch makefile 中定义


所以一般来说linux的运行地址为 0xC0008000 


#define PHYS_OFFSET UL(0x30000000)


所以一般的加载地址为 0x30008000




/arch/arm/kernel/head.S


从连接脚本可以看到 .text.head 是最先被连接的段,所以第一条指令就是从这里开始的。


 The requirements are: 
MMU = off, D-cache = off, I-cache = dont care, r0 = 0, r1 = machine nr.


.section ".text.head", "ax"
.type stext, %function
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
其中 cpsr_c 是掩码,c 表示 bit 0-7 ,刚好就是工作模式 I F 等位,所以这里可以这样操作
#define SVC_MODE 0x00000013
#define PSR_F_BIT 0x00000040
#define PSR_I_BIT 0x00000080
操作 CPSR 和 SPSR 的指令为 MCR MRC


mrc p15, 0, r9, c0, c0@ get processor id
表示操作 p15 的 c0 寄存器,ARM 的标准用法而已,存放在 R9 中




bl __lookup_processor_type@ r5=procinfo r9=cpuid
movs r10, r5@ invalid processor (r5=0)?
beq __error_p@ yes, error 'p'
bl __lookup_machine_type@ r5=machinfo
movs r8, r5@ invalid machine (r5=0)?
beq __error_a@ yes, error 'a'
bl __create_page_tables










/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 * r9 = cpuid
 * Returns:
 * r3, r4, r6 corrupted
 * r5 = proc_info pointer in physical address space
 * r9 = cpuid (preserved)
 */
.type __lookup_processor_type, %function
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7@ get offset between virt&phys
add r5, r5, r3@ convert virt addresses to
add r6, r6, r3@ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9@ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ@ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0@ unknown processor
2: mov pc, lr


/*
 * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
 * more information about the __proc_info and __arch_info structures.
 */
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end


CPU 的ID 是这样定义的,在 procinfo.h 中定义 proc_info_list
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;
};


在 proc-arm920.S 中有其中的一个实现


.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




开始程序不断的比较 CPU 的ID是否已经存在在系统当中,是的话则成功,很显然,这是一类架构的ID,例如 ARM 920T 




接着是判断板子的ID,这个是系统注册的ID,具体分析如下


#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 \
};


struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head-armv.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);
};


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


被替换成


static const struct machine_desc __mach_desc_S3C2440 __used __attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_S3C2440,
.name = "SMDK2440",

.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,
};






#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn


#define arch_initcall(fn) __define_initcall("3",fn,3)




#define arch_initcall(customize_machine)
代入后得到
static initcall_t __initcall_customize_machine3 __attribute_used__ \
__attribute__((__section__(".initcall" "3" ".init"))) = customize_machine




这个定义需要自己去做,在 arch/arm/mach-24xx/menory.H 中定义
#define PHYS_OFFSET UL(0x30000000) 
TEXT_OFFSET 是在 arch makefile 中定义的,值为 0x00008000


#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)


.macro pgtbl, rd
ldr \rd, =(KERNEL_RAM_PADDR - 0x4000)
.endm


这里的意思是,page table 存放的位置为 0x30004000


.long   PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
2410的 proc 结构在 proc-920t.S 中定义,MMU的属性为上面,上面选中了MMU的功能,具体可以看


mov r6, pc, lsr #20@ start of kernel section
orr r3, r7, r6, lsl #20@ flags + kernel base
str r3, [r4, r6, lsl #2]@ identity mapping


首先PC值就是当前的代码段的,取其高12bit,也就是得到 section ,然后R3 是之前得到的MMU标志
然后填页表。这里注意,这里出于一些考虑,第一就是运行到这里的PC指针肯定小于1M。第二就是,MMU生效
前和生效之后,PC指针指向的程序段必须映射到同一地址上面,否则会跑飞的。
这里看出,第一个section是拿出来独立处理的,为的就是这个原因,下面进行页表的统一处理,根据连接后
的VA 来规划。
R4 ,page table 的起始地址
add r0, r4,  #(KERNEL_START & 0xff000000) >> 18
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
ldr r6, =(KERNEL_END - 1)
add r0, r0, #4
add r6, r4, r6, lsr #18
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
KERNEL_START 就是  VA 开始,注意,刚才第一块1MB已经映射了,所以 add r3, r3, #1 << 20 其实是
加上1MB的意思,也就是说,剩下的SDRAM中的代码段全部映射到虚拟地址上去了。
接着只需要使能MMU就行了


ldr r13, __switch_data@ address to jump to after @ mmu has been enabled
adr lr, __enable_mmu@ return (PIC) address


ldr 表示装载的是运行地址,R13 存放的是 __switch_data 的运行地址(VA),当MMU使能的时候就直接跳转到
在 __enable_mmu 的最后一句也是这样
__turn_mmu_on:
        .....
mov pc, r13


接着分析 __switch_data ,注意这里已经是在虚拟地址上面运行了
接着就是运行时初始化,copy data段,清bss段,保存几个变量的值,processor_id 是之前
通过 CP15 读出来的值,这里就是 920T 的id,__machine_arch_type 是mach 的指针,指向找到
的那个单板的类型,也就是 struct machine_desc


.long processor_id@ r4
.long __machine_arch_type@ r5
.long __atags_pointer@ r6


可以看出,linux的处理办法就是维护一个列表,然后在开始的时候通过查找对应的结构体来实现的
所以实现一个新的板子最重要的是维护这个结构体。


到VA上运行的第一个函数就是 __mmap_switched: 这个时候我终于明白了这个标号的意思了 ...


接着分析 start_kernel 函数了,第二阶段初始化函数,C语言,其实UBOOT 是完全按照linux的思路
去做的 ....


setup_arch 函数
CONFIG_CMDLINE 指定了默认的命令行


首先执行 setup_processor ,这里其实是调用汇编 head.S 里面的lookup_processor_type函数
CONFIG_CPU_CP15 控制是否使用 cp15 ,有则启动内嵌汇编代码读取cp15得到 CPUID ,其实这里
的函数汇编都已经做过了,这里重新做一遍,是因为需要填一下C的变量,也就是上面提到的一个
CPU的结构 proc_info_list  里面的成员


具体的成员要配合 proc-arm920.S


linux内核启动的时候打印的信息,第一行是 banner ,第二行是cpu信息,就是在 setup_processor 
中找到CPU后并且读取信息之后打印出来的。


Linux version 2.6.22.6 (root@etual-desktop) (gcc version 3.4.5) #1 Tue Apr 3 11:49:06 CST 2012
CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177


cpu_name, [id], id & 15 (idversion) ,


icache 部分不明白,暂时放下


接着就是 setup_machine 函数了,入口参数是之前汇编中找到的 machine_arch_type
找到了之前定义的 __mach_desc_S3C2440 结构,打印里面的名字,linux输出
Machine: SMDK2440
就这样,CPUINFO 和 MACH INFO 都已经被内核识别了。


所以,这个是移植开发板的思路,一般来说,移植linux都是到一个已经认识的CPU 架构,否则
工作量就不是一般的大了,大得你可以进入开源社区工作了。但是,相对来说,移植一个开发板
的工作量就小的多,这也是实际工作中需要的,因为移植linux面对的不可能是某某开发板那样
大部分相类似的,修改个分区就移植成功的,改动量是有点大的,所以必须摸清楚linux的架构。


接着分析 tag


 r2 = atags pointer.


if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);


现在的linux需要启动参数列表,在 R2 中,是在uboot中设置的。不过其实这个参数的位置就算
没有设置,也能从参数的初始化设置中获得,并且变换为虚拟地址
.boot_params = S3C2410_SDRAM_PA + 0x100,


接着当然是分析命令行的参数了,在函数
static void __init parse_cmdline(char **cmdline_p, char *from)
**cmdline_p 是一个临时变量指针,start_kernel 用来接收命令行的,from 是默认的命令行
在config中指定的。不过这里只是处理默认的命令行???


parse_tags(tags);
判断如果传入参数里面有 command、 line tag 的话,则使用传入参数代替默认的TAG
static void __init parse_tags(const struct tag *t)
    static int __init parse_tag(const struct tag *tag)
        strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);




paging_init(mdesc); 初始化页表,不过这里传递进去板子,所以是否和板子相关??
static void __init devicemaps_init(struct machine_desc *mdesc)
执行了 devicemaps 初始化,而里面又调用了 mdesc->map_io(); 所以只需要关注 map_io 函数
这里是有实现的,在 smdk2440_map_io 中


这时应该转到 Mach-smdk2440.c 中去执行初始化函数




这么函数中貌似是申请mem的,但是 mdesc 主要用在 video_start ,这里没有,以后可能
用到,暂时放下
request_standard_resources(struct meminfo *mi, struct machine_desc *mdesc)


cpu_init(); 初始化异常的堆栈






板子的函数和内核函数连接起来了
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
这里分别就是
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,


貌似是异常处理方面的,暂时看不懂
early_trap_init();




--------------------------------------------------------------------------------------
customize_machine 里面执行 init_machine(); 而这个
arch_initcall(customize_machine); 被安装到
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn


可见,被安装到 .initcall3.init 里面去了,查看链接脚本和相关的函数后知道,这些
__initcall_start = 段中 __initcall_end


init_machine 在这些函数下面被调用
start_kernel
    rest_init
         kernel_init
             do_basic_setup
                 do_initcalls




setup_arch(char **cmdline_p)
    paging_init(struct machine_desc *mdesc)
        build_mem_type_table(void)
            Memory policy: ECC disabled, Data cache writeback


做page初始化,linux输出,具体代码有点多,原理也比较复杂,要慢慢学习
Memory policy: ECC disabled, Data cache writeback
On node 0 totalpages: 16384
  DMA zone: 128 pages used for memmap
  DMA zone: 0 pages reserved
  DMA zone: 16256 pages, LIFO batch:3
  Normal zone: 0 pages used for memmap




接着还是分析时钟初始化




首先处理的是 map_io ,对应的是 smdk2440_map_io


#define S3C_ADDR_BASE (0xF4000000)
#define S3C_VA_UART S3C_ADDR(0x01000000)/* UART */
/* UARTs */
#define S3C24XX_VA_UART   S3C_VA_UART
#define S3C2410_PA_UART   (0x50000000)
#define S3C24XX_SZ_UART   SZ_1M
#define S3C_UART_OFFSET   (0x4000)


s3c24xx_init_clocks(16934400);


struct cpu_table {
unsigned long idcode;
unsigned long idmask;
void (*map_io)(void);
void (*init_uarts)(struct s3c2410_uartcfg *cfg, int no);
void (*init_clocks)(int xtal);
int (*init)(void);
const char *name;
};
然后调用 (cpu->init_clocks)(xtal); 设置时钟,


其中 cpu 是通过 cpu = s3c_lookup_cpu(idcode, cputab, cputab_size); 确定的
smdk2440_map_io
    s3c24xx_init_io
        s3c_init_cpu
            s3c_lookup_cpu


也就是说在 map io 的时候已经找到了cpu并且设置对应的处理函数了,查表的方式
这边表在 cpu.c 里面
static struct cpu_table cpu_ids[] __initdata = {
.idcode = 0x32440001,
.idmask = 0xffffffff,
.map_io = s3c244x_map_io,
.init_clocks = s3c244x_init_clocks,
.init_uarts = s3c244x_init_uarts,
.init = s3c2440_init,
.name = name_s3c2440a


其中我们需要的是 2440a 的芯片,从而可以跟踪几个初始化函数了
接着执行 s3c244x_map_io
找到后linux输出cpu型号
CPU S3C2440A (id 0x32440001)


s3c244x_init_clocks
    s3c244x_setup_clocks 设置分频比


linux输出
S3C244X: core 400.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz


于是 初始化clock变成了执行 s3c244x_init_clocks 函数
linux 输出 
S3C24XX Clocks, (c) 2004 Simtec Electronics
原创粉丝点击