#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 <
[email protected]> */
.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