Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9464734
  • 博文数量: 1750
  • 博客积分: 12961
  • 博客等级: 上将
  • 技术积分: 20091
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-09 11:25
个人简介

偷得浮生半桶水(半日闲), 好记性不如抄下来(烂笔头). 信息爆炸的时代, 学习是一项持续的工作.

文章分类

全部博文(1750)

文章存档

2024年(26)

2023年(26)

2022年(112)

2021年(217)

2020年(157)

2019年(192)

2018年(81)

2017年(78)

2016年(70)

2015年(52)

2014年(40)

2013年(51)

2012年(85)

2011年(45)

2010年(231)

2009年(287)

分类: 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-bootNAND flash中说明)

(7)    对环境变量进行重定位(env_relocate

(8)    如果定义了网卡,则对ipaddrMAC进行配置

(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)主时钟:为锁相环PLLAPLLB提供输入时钟

(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)

(这里的时钟分频CD1

(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,usbttyserialnull

涉及到的函数包括

Drv_lcd_init

Drv_usbtty_init (在u-bootusbtty中说明)

Drv_system_init 初始化serialnull这两个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_dataflag标志包括,

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,serialnullusbtty

       对于设备使用前无需初始化的(调用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字节头的imageboot处理的是不带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"方便

/*------------------------------------------------------------------------------*/

阅读(1767) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~