分类: LINUX
2010-02-03 16:17:36
不是每一行代码都必须读懂,我只是大概地过一下流程
毕竟这些都是比较成熟的代码,没必要去改的
是针对我自己的板子的,硬件配置如下
cpu是s3c2410
board type 是 smdk2410
16M Nor Flash 地址是 0x0---0xFFFFFF
64M SDRAM 地址是 0x30000000---0x33FFFFFF
软件是华恒版的
ppcboot 2.0 和 linux 2.4.18
仔细分析了一下启动的流程,能更好地理解硬件和软件的配合
方便移植。
我们在flash的开始处烧写了ppcboot.bin,这是可执行的二进制文件
注意和ELF可执行性文件是有区别的。
cpu上电后可以从直接从flash地址0处取指令来执行
开始的代码在ppcboot-2.0.0\cpu\arm920t\start.s中
这里需要提一下编译链接时用到的一个很重要的链接文件
ppcboot-2.0.0\board\smdk2410\ppcboot.lds
这个文件给出了代码中各标号的基地址,和各个段的链接顺序
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
armboot_end_data = .;
. = ALIGN(4);
.bss : { *(.bss) }
armboot_end = .;
}
可以看到程序的入口是_start标号指示的,而cpu/arm920t/start.o
则被安排在程序最开始的地方,这个标号就是在start.s中
但是还有一点是需要特别注意的,开始我也是因为这个地方而没有很好地理解程序
虽然lds中有 . = 0x00000000 这一句,指示链接基地址,不过其实这句是不起作用的,
真正的链接基地址在ppcboot-2.0.0\config.mk中指定的
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)
其中-Ttext $(TEXT_BASE)就是指定链接地址为TEXT_BASE的值
因而是可变的,TEXT_BASE在ppcboot-2.0.0\board\smdk2410\config.mk中定义
TEXT_BASE = 0x33F00000
ppcboot-2.0.0\config.mk是包括到Makefile中的,
在Makefile中有$(LD) $(LDFLAGS) $(OBJS) $(LIBS) $(LIBS) -Map ppcboot.map -o ppcboot
所以说起来真正的链接地址是0x33F00000,其实这样在把ppcboot拷到Ram中就可以实现无缝跳转了
.globl _start
_start: b reset
跳到renset
reset: ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
..........................
bl cpu_init_crit //bl跳转会回来
relocate: //下面开始要把ppcboot拷到Ram中
adr r0, _start /* r0 <- current position of code */
ldr r2, _armboot_start
ldr r3, _armboot_end
sub r2, r3, r2 /* r2 <- size of armboot */
ldr r1, _TEXT_BASE /* r1 <- destination address */
add r2, r0, r2 /* r2 <- source end address */
以上代码需要注意的一点是 adr 和 ldr 的区别
adr取得是当前pc相关的偏移地址,在这里程序还是在flash中运行
所以取得地址是以0x0为基址的
而ldr取的是_armboot_start所指的值
.globl _armboot_start
_armboot_start:
.word _start
看到它的值也是_start的地址,不过我们这里取的是绝对地址,是在链接是确定的以
TEXT_BASE为基址的.由于_start的偏移是0,所以r0是0,r2就是TEXT_BASE
copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
循环copy
ldr r0, _armboot_end /* set up the stack */
add r0, r0, #CONFIG_STACKSIZE
sub sp, r0, #12 /* leave 3 words for abort-stack */
ldr pc, _start_armboot
_start_armboot: .word start_armboot
//通过这一句跳转到ppcboot-2.0.0\lib_arm\board.c中的start_armboot函数去执行了
start_armboot的绝对地址也是以TEXT_BASE为基址的,所以可以顺利的实现无缝跳转了.
接着下来就是一系列初始化的工作了
首先定义了一个全局的数据结构 gd_t gd_data;
DECLARE_GLOBAL_DATA_PTR 这个宏定义的是一个全局的gd_t类型的指针gd
gd = &gd_data;
这样以后就可以用gd来访问gd_data这个数据结构了
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_fnc_ptr中是一系列初始化函数的指针
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
display_banner,
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
基本上serial_init后我们就可以用printf函数来打印信息了.
for (;;) {
main_loop ();
}
进入了主循环,在M:\ppcboot-2.0.0\common\main.c中
{
char c = 'y';
unsigned long timedata;
printf("start linux now(y/n):");
timedata = 0;
for (;;) {
while (!tstc()) { /* while no incoming data */
if (timedata++ > 3000 * 100 *3)
goto bootm; /* timed out */
}
c = getc();
}
tstc()是测试串口是否有数据输入,显然没有的话就会等待time out跳出
bootm:
if(c == 'y'||c == 'Y'){
strcpy(lastcommand , "bootm 30008000 30800000\r");
flag = 0;
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
else{
printf("\n\n");
}
}
这样如果串口没有输入或者输入时y Y 的话,就会去执行bootm 30008000 30800000\r这条命令
否则就会到ppcboot的命令行等待输入.
执行bootm 30008000 30800000\r这条命令会调用ppcboot-2.0.0\common\cmd_bootm.c中的
do_bootm函数,具体的命令怎样被分解,选择调用函数的机制我就不多说了,追着run_command去就是了
在do_bootm中调用了do_bootm_linux函数,这个函数在ppcboot-2.0.0\lib_arm\armlinux.c中
ret = memcpy((void *)0x30008000, (void *)0x40000, 0x100000);
if (ret != (void *)0x30008000)
printf("copy kernel failed\n");
else
printf("copy kernel done\n");
ret = memcpy((void *)0x30800000, (void *)0x140000, 0x440000);
if (ret != (void *)0x30800000)
printf("haha failed\n");
else
printf("copy ramdisk done\n");
首先把kernel和ramdisk都拷到Ram相应的地方去.
setup_linux_param(0x30000000 + LINUX_PARAM_OFFSET); //也在armlinux.c中
#define LINUX_PARAM_OFFSET 0x100
建立要传给内核的参数,参数的地址都是固定的,所以内核也知道去这里取参数
参数格式比较复杂,我这里好像传得参数不多
void setup_linux_param(ulong param_base)
{
struct param_struct *params = (struct param_struct *)param_base;
...............
}
只是通过一个param_struct的结构体来传参数的,不过现在一般都用另一种tag标记的传参方法
一个主要的参数时char linux_cmd[] = "initrd=0x30800000,0x440000 root=/dev/ram init=/linuxrc console=ttyS0";
if (linux_cmd == NULL) {
printf("Wrong magic: could not found linux command line\n");
} else {
memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
printf("linux command line is: \"%s\"\n", linux_cmd);
}
是比较重要的.移植的时候常常需要修改
接着call_linux(0, 0xc1, 0x30008000); 看出来是准备调到linux去了
0xc1是machine type,这三个参数分别给了r0,r1,r2,这些都是调用内核的约定
void call_linux(long a0, long a1, long a2)
{
__asm__(
"mov r0, %0\n"
"mov r1, %1\n"
"mov r2, %2\n"
"mov ip, #0\n"
"mcr p15, 0, ip, c13, c0, 0\n" /* zero PID */
"mcr p15, 0, ip, c7, c7, 0\n" /* invalidate I,D caches */
"mcr p15, 0, ip, c7, c10, 4\n" /* drain write buffer */
"mcr p15, 0, ip, c8, c7, 0\n" /* invalidate I,D TLBs */
"mrc p15, 0, ip, c1, c0, 0\n" /* get control register */
"bic ip, ip, #0x0001\n" /* disable MMU */
"mcr p15, 0, ip, c1, c0, 0\n" /* write control register */
"mov pc, r2\n"
"nop\n"
"nop\n"
: /* no outpus */
: "r" (a0), "r" (a1), "r" (a2)
);
}
mov pc, r2 就是这句吧,调到了30008000去执行内核了
接下来就到内核了吧
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dansen_xu/archive/2007/08/12/1739650.aspx