偷得浮生半桶水(半日闲), 好记性不如抄下来(烂笔头). 信息爆炸的时代, 学习是一项持续的工作.
全部博文(1750)
分类: LINUX
2012-03-10 15:07:17
AT91SAM9260
ARM926EJ-S
U-BOOT
Stage2
一.start_armboot
流程如下,
(1) 为各参量分配空间
(2) 依次调用函数指针数组init_sequence中定义的函数,如果中途出错,则hang()进入死循环
(3) 如果是从nor flash启动,则对nor flash进行初始化
(4) 如果定义了VFD或者LCD,为其分配空间
(5) 初始化分配的堆空间(长为CFG_MEM_LEN)
(6) 如果配置了NAND flash,则对该flash进行初始化,如果定义了DATA flash,同样对data flash进行初始化(NAND flash部分在u-boot之NAND flash中说明)
(7) 对环境变量进行重定位(env_relocate)
(8) 如果定义了网卡,则对ipaddr和MAC进行配置
(9) 单板外设需要reset 则调用 board_ext_reset
(10)设备初始化(devices_init)
(11)初始化跳转表(jumptable_init)
(12)控制台后期初始化(console_init_r)
(13)如果定义了后期初始化,则调用单板后期初始化(board_late_init)
(14)进入主循环,等待命令输入(main_loop())
具体的一些函数,
(1) init_sequence
(a) cpu_init
对于AT91SAM9260,没有使用中断机制,所以直接返回
(b) board_init
板级初始化,包括锁相环PLLB的初始化,板级时钟的初始化,一些口线的配置,如果配置了SPI,则进行SPI的配置(这里的SPI主要是用于LCD的显示,如果使用framebuffer不需要),如果配置PWM_BEEP,则进行BEEP的配置.时钟相关如下,
(1)慢时钟:用于驱动RTC和其他掉电保留部分,一般频率为32.768KHZ。此外,也可以用来提供CPU时钟和总线时钟
(2)主时钟:为锁相环PLLA和PLLB提供输入时钟
(3)锁相环PLLA输出时钟:可以用来提供CPU时钟和总线时钟
(4)锁相环PLLB输出时钟:可以用来提供USB主机端(48MHZ)和设备端控制器时钟,也可以用来提供CPU时钟和总线时钟
(5)CPU时钟:驱动处理器的CPU核心
(6)总线时钟:驱动处理器总线上的设备
(c) interrupt_init
由于不使用中断,这里只是初始化计数器
(d) env_init
对于配置了CFG_ENV_IS_EMBEDED,则将环境变量重新赋值,如果定义该宏,表明环境变量定义在某内存区域(可能是定义了一个变量),否则使用缺省的环境变量,即
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
先将环境变量的设置如下,一些函数都是通过取环境变量进行相关操作的,环境变量例如,
Baudrate=115200
Usbtty=cdc_acm
Serialport=4
…
默认的环境变量后还定义了CONFIG_EXTRA_ENV_SETTINGS
#define CONFIG_EXTRA_ENV_SETTINGS "autostart=yes/0"/
"usbtty=cdc_acm/0"/
"serialport=4/0"/
"usbbootdelay=-1/0"/
"silent=1/0"
这里也与serial_init能初始化成功有关(在未进行重定位之前,使用的均是默认的环境变量)
(e) init_baudrate
通过读取环境变量baudrate,设置波特率,填充进gd中
(f) serial_init
读取环境变量”serialport”(读取的是缺省的环境变量),根据读到的串口号配置相应的串口,如上图的配置表中serialPort=4,配置的就是AT91_URTT4(口线),接着配置串口相关寄存器,包括CR,MR,IMR,BRGR等
一般都是先口线配置,在相关寄存器配置
CR 重启发送器和接收器,使能发送接收
MR 模式寄存器 正常模式,8bit,无校验,1位停止位
IMR 掩码为全F
BRGR
这里设置的MR为不等于ISO7816的模式,所以波特率设置如下,
US_BRGR = AT91F_MASTER_CLOCK()/(baudrate * 16)
(这里的时钟分频CD为1)
(g) console_init_f
控制台前期初始化,填写gd->have_console=1,设置成支持控制台操作,
如果配置了silent_config,读取环境变量silent,然后对gd->flags进行配置
(h) dram_init
简单的填充
gd->bd->bi_dram[0].start=PHY_SDRAM
gd->bd->bi_dram[0].size = AT91F_SDRAM_SIZE();
(2) env_relocate
在env_init中使用的是缺省的环境变量,只有env_relocate后才使用flash中的环境变量。
(3) devices_init
创建设备链表devlist,根据配置选择相应的init
基本Dev配置LCD,usbtty,serial,null
涉及到的函数包括
Drv_lcd_init
Drv_usbtty_init (在u-boot之usbtty中说明)
Drv_system_init 初始化serial和null这两个Dev,添加到devlist中
(4) console_init_r
对于console的后期初始化,就是将控制台的标准输入输出设备定位为serial
(5) board_late_init
对于板级的后期初始化,如果配置了usbtty,就是将控制台的标准输入输出重定向成usbtty设备
(6) main_loop
这里设计的灵活性较大,等待命令输入,做下载更新操作,做一些权限验证等等
对于等待命令输入操作,
len = readline (CFG_PROMPT);
if (len > 0)
strcpy (lastcommand, console_buffer);
…
run_command (lastcommand, flag);
二.Stage2中的一些细节
1.空间分配
2.环境变量
基本引导参数
RO 根分区在刚启动时只读,如果没有改参数,则根文件系统可写
Quiet 表明启动时不显示Linux内核提示的信息
对于非易失性存储方式,环境变量支持两个独立存储区域的交替使用,以避免修改环境变量时掉电引起所有环境变量非法。
在没有进行环境变量重定位,就可以读取环境变量了,此时环境变量所处的位置是(与global_data有关)默认的环境变量(将env_addr设置为默认的环境变量指针,如果没有定义ENV_IS_EMBEDDED则使用的是默认的环境变量,否则需要自己设置)。具体见env_init(这里要看是从什么flash中启动)
Global_data中,
unsigned long have_console; /* 控制台初始化是否完成*/
unsigned long reloc_off; /* 重定位偏移*/
unsigned long env_addr; /* 环境变量(或临时缓冲区)地址*/
unsigned long env_valid; /* 环境变量有效标志 */
控制台的flag决定是否输出,在Globaa_data中flag标志包括,
flags标志包括下面位:
1、GD_FLG_RELOC:代码被重定位到RAM中
2、GD_FLG_DEVINIT:控制台设备已经被初始化
3、GD_FLG_SILENT:控制台不输出
3. Devlist
(1)注册设备
在devices_init中,调用devlist=ListCreate(sizeof(device_t))创建设备链表,
然后添加了四个设备,包括LCD,serial,null,usbtty
对于设备使用前无需初始化的(调用device_t.start方法)设备,填充device_t的以下结构就可以实现注册了。
包括name,putc,puts,getc,tstc,ext,flags(标示设备是否支持输入输出出错)
最后调用device_register将设备添加到devlist链表中
在board_late_init,如果配置了usbtty,需要通过usbtty提供console输出时,会对控制台进行重定向
console_assign(stdin,”usbtty”)
console_assign (stdout,”usbtty”)
这里设置标准输入输出设备均为USBTTY
Stdin,stdout,stderr 分别为0,1,2
(2)方法调用
当调用puts,putc,tstc,getc时,会根据此时设定的stdin,stdout来进行重定向输入输出函数。这种方法类似于面向对象中的多态。
例如,调用putc(const char c)时,会调用fputc(stdout,c),而fputc(stdout,c)则通过stdio_devices找到当前设定的标准输出设备,然后调用该设备的putc方法。
4. U_BOOT_CMD
U_BOOT_CMD的实现使得u-boot中更方便的添加命令
通过U_BOOT_CMD定义一个命令之后,实现其对应的操作,就可以通过
S=Getenv(“XXX”)和Run_command(s,0),调用相应的方法。
随意找一个定义的U_BOOT_CMD为例,
U_BOOT_CMD(
eeprom, 6, 1, do_eeprom,
"eeprom - EEPROM sub-system/n",
"read devaddr addr off cnt/n"
"eeprom write devaddr addr off cnt/n"
" - read/write `cnt' bytes from `devaddr` EEPROM at offset `off'/n"
);
其中U_BOOT_CMD为一个宏,定义如下,
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
这里cmd_tbl_t为一个cmd结构,不细说
##为宏中去掉空格,并且如果后面不跟参数,主动去除‘,’
#name使得参数为‘name’
struct_section为,
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
就是将定义的u_boot_cmd放入section叫“.u_boot_cmd”的段中
具体的通过run_command找到对应方法的机制比较简单,就是通过名字得到对应的cmd_tbl_t结构的指针,然后通过
-----à(cmdtp->cmd)(cmdtp,flag,argc,argv)
调用对应的方法操作
5. Tag
前面讲到main_loop,如果不进入u-boot的命令行模式,则直接引导系统,通过命令bootm实现,调用的是do_bootm
这里用bootm是因为bootm处理的带64字节头的image,boot处理的是不带64字节头的image
解析完image_head_t之后,如果是Linux kernel则会调用do_boom_linux引导系统,对于不使用initrd的,建立taglist之后,就跳转到kernel image处开始运行。
具体的在代码中
//theKernel指向内核入口地址为 hdr->ih_ep
//调用theKernel就是从hdr->ih_ep开始运行
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
最后会调用
//调用内核,寄存器R0=0,R1=机器类型,R2=参数块地址
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
kernel读取taglist也是从这个bd->bi_boot_params中读取,因为在分配tag的时候是从这个地址开始分配的。前面的存储结构图中也提到这个内容。
下面看具体的Tag代码,
根据配置选择tag,不过总流程就是
Setup_start_tag(bd)
Setup_serial_tag
Setup_memory_tags
…
Setup_end_tag
在setup_start_tag中,params=(struct tag *)bd->bi_boot_params,
Struct tag结构包括一个tag_header hdr和一个union,该union中包括各种不同的具体tag_XXX。
struct tag_header {
u32 size;
u32 tag;
};
Tag_header用于标示这个tag内容和大小,具体的union中的东东可以看代码,不重复了
通过这种方式就建立起taglist(其实不算list,只是很多连续的结构体)
在kernel中解读这些结构体得到所需引导参量
看tag这部分代码的时候,也有个问题,一般来说对于未分配的内存空间对其进行操作是不好的(将这些空间的原有数据覆盖),而tag代码中,定义了params指针后,就开始顺序通过指针的操作,对以它其实的内存进行赋值,从而完成taglist,不过对于引导而言,需要将具体的参量保存在一个起始位置开始的连续内存区域,这样操作也可以。
下面是tag 的琐碎东东 可以跳过
tag_size >> 2
左移相当于/4,由于均为U32格式的变量,所以计算偏移个数时,需要/4
主要原因还是tag_next,是以U32的指针做偏移的,就是指针的++操作,对应的是移动4字节的指针位置
Cmdline +1 +4
之所以+1是因为strlen 后的 ‘/0’
+4是为了保证移动U32指针时,数据不会被覆盖(指针操作)
例如,
Strlen(cmdline)=5;
(4+4+1+5)/4=3 而实际上数据被覆盖了,这是由于/4操作导致的,
如果+4的话就能保证数据的完整(指针+1)
(4+4+1+5+4)/4 =4 分配8个字节存放6字节的数据
6.
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
// 申明gd_t的指针,放在寄存器R8中
__asm__ __volatile__("": : :"memory");
/*------------------------------------------------------------------------------*/
/*?memory描述符告知GCC:
1)不要将该段内嵌汇编指令与前面的指令重新排序;
也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加"memory",告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。
使用"volatile"也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用"memory"方便
/*------------------------------------------------------------------------------*/