uboot学习笔记

来源:互联网 发布:假钞在淘宝中的暗语 编辑:程序博客网 时间:2024/06/05 02:59
学习uboot也有一段时间了,今天就对uboot做一个总结
简而言之,uboot是用来引导系统启动的。所以,uboot的功能首先是启动系统。但是,我们使用的
uboot功能除了启动系统,还可以进行很多操作:操作存储设备、烧写文件系统、烧写操作系统、操
作板子上的硬件设备等等。所以学习uboot的过程中就要按照uboot的功能分布进行。
1、首先是启动系统,那么,我们启动的Linux的要求是什么?
   对于ARM答案是:>R0 =0  
                  >R1= machine type number  
                  >R2= physical address of tagged list in system RAM
                  >CPU must be in SVC mode
                  >All forms of interrupts must be disabled (IRQs and FIQs)
                  >The MMU must be off.
                  >Instruction cache may be on or off.
                  >Data cache must be off.
                  >The boot loader is expected to call the kernel image by jumping
                   directly to the first instruction of the kernel image.
   那么我们只要是CPU满足上述这些条件不就行了么。
   要满足上述三个条件,最难恐怕是启动参数了吧,启动参数为了是将板子的核心资源及其分布传 
   递给内核,我们就要了解这个启动参数包含哪些参数了。 
   我们先从启动过程来分析板子的初始化和这些启动条件的实现
   uboot的启动过程分为stage1和stage2。前者使用汇编写的,短小精悍,实现CPU核心资源的初始化
   并把自己拷贝到,RAM中,跳到第二阶段。后者使用C语言写的,实现开发板上资源的初始化。
   stage1的入口点是start.s。为什么是这个文件是有原因的,代码经过汇编处理下一步就是链接,
   链接后得到文件就是可执行文件。那么,只要链接时候把一段代码放到最前面,就可以使得这段代码
   在机器上首先执行。uboot.lds文件,就是定义了uboot的代码分布。其中start.s放在首位,done
   那么start.s做了写什么?
     设置状态寄存器设置当前的CPU模式svc
     关闭看门狗、中断、MMU、catche
     初始化时钟
     自拷贝到RAM
     建立堆栈
     清除bss段
     跳转到start_armboot
   start_armboot函数是用C语言写的,它是stage2的开始。
   start_armboot实现了哪些功能?
     分配全局数据区gd和板子数据区bd,其中gd和bd是两个结构体类型,第二阶段的任务主要是初始化gd
     然后根据gd设置启动参数并引导内核启动。先来看看这两个结构体
     typedef structglobal_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off;   /* Relocation Offset */
unsigned long env_addr;      /* Address  of Environment struct */
unsigned long env_valid;   /* Checksum of Environment valid? */
unsigned long fb_base;     /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type;     /* display type */
#endif
#if 0
unsigned long cpu_clk;     /* CPU clock in Hz!*/
unsigned long bus_clk;
unsigned long ram_size;     /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt;            /* jump table */
} gd_t;  

typedef struct bd_info {
   int bi_baudrate;           /* serial console baudrate */
   unsigned longbi_ip_addr;       /* IP Address */
   unsigned charbi_enetaddr[6];    /* Ethernet adress */
   struct environment_s  *bi_env;
   ulong  bi_arch_number;         /* unique id for this board */
     ulong bi_boot_params;          /* where this board expects params */
     struct                   /* RAM configuration */
             {
              ulong start;
ulong size;
     } bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
该结构定义了串口的波特率、IP地址、Ethnernet地址、环境结构体、arch number、启动参数、
RAM结构体数组(RAM的起始地址和大小)
    那么可以看到DECLARE_GLOBAL_DATA_PTR; 就是声明了一个指针变量,并且它就在r8这个寄存器
    里它所指向的地址就是存储开发板的一些参数设置的信息。
跟着代码跑吧:
1、一些列函数的执行,都是硬件初始化
   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 */
console_init_f,/* stage 1 init of console */
display_banner,/* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
2、flash初始化
 size = flash_init ();
   :
   ;
   :
   :  
       main_loop
      这个函数主要是实现 
   
   
   
   
   
   
   
2、存储设备的操作
   存储常见的有flash、ram、rom、eeprom它们在Linux嵌入式设备中的的常用功能
      >flash:分为nor和nand,主要存储bootloader、kernel、启动参数、文件系统
      >ram:就是内存
      >rom:
      >eeprom:
  1.1 首先,学习一下nand flash,我个人觉得nand flash的学习是学习uboot的重中之重,nand
      flash作为存储设备广泛用于嵌入式设备,uboot也支持对它的读写等操作,此外,我们要使用
      的操作系统大多也烧写在nand中,由于uboot本身就支持对一些操作系统的烧写,如yaffs/yaff2
      jffs2等。其重要性不言而喻。
      /---------------------------------/
           <此处介绍一下nand,待补充>  
      /---------------------------------/
      对于nand的学习,uboot中的突破口有两个,一个是nand_init函数,此在分析uboot的启动过程中
      会遇到,它主要是实现
      /---------------------------------/
           <此处介绍一下nand——init,待补充>  
      /---------------------------------/
      另外一个是cmd_nand.c文件,此文件是实现uboot中关于nand操作的一些函数的,还是好好学习一下吧
      我来介绍一下这个文件中函数的调用流程,首先要有一个层次感,这些层次本不存在,只是为了便于理解
      从上到下第一层  do_nand  实现命令层,主要实现命令的解析,根据解析结果调用下一层
              第二层  nand_rw  根据传递来的命令参数判断调用具体的读或者写
              第三层  nand_write_ecc  具体的读写函数,多页读写,调用单独页的读写函数
              第四层  nand_write_page 具体的读写函数,单页读写,根据规则调用一些宏命令来实现
              第五层  NandCommand、NandAddr 用来实现发送命令或者地址 
          如果有第六层那就是对于不同的nand的具体的操作命令了,它们对上层的接口一定要统一,下面来
          看看这个文件的具体调用    
       do_nand ----调用--> nand_rw|----调用--> nand_read_ecc ----调用-->
                                  |----调用--> nand_write_ecc ----调用-->nand_write_page
                    
                   
          do_nand的主要功能:实现nand命令
                   主要实现:根据命令参数argc,通过一个switch语句分别判断nand的具体命令,特别注意的
                             是读写命令调用了nand_rw。
          nand_rw的主要功能:根据命令参数读写flash
                   主要实现:1、坏块检测,如果出现坏块,根据命令做出相应处理
                                a>如果NANDRW_READ | NANDRW_JFFS2:向buf中写入start到块结束或len长度的0xff
                                b>如果NANDRW_READ | NANDRW_JFFS2 | NANDRW_JFFS2_SKIP
                                   或NANDRW_WRITE | NANDRW_JFFS2 ,跳过一个块的大小
                             2、根据命令判读,以最大一个块的大小读写nand,这里调用的读写函数nand_read_ecc
                                和nand_write_ecc。 
          nand_read_ecc的主要功能:从flash中读出数据,支持ECC,如果定义了MTD就支持ECC,否则就是一般读取
                         主要实现:MTD的读:
                                  1、将数据读到databuf
                                  2、将databuf拷贝到datacatche
                                  3、从databuf中获得ecc
                                  4、计算ecc判断,如果valid则直接跳到readdata,否则纠正databuf中的数据
                                  5、将纠正后的数据考到datacatche
                                  6、read data
                                    普通读就是在读数据时不读取ECC,不进行有效性验证,注释很详细
                        注意:由于读取的操作的最大单位是block,在读取时是同while(retlen<len)循环来分块读取的      
          nand_write_ecc的主要功能:写flash函数,同时写入了ECC,调用了nand_write_page
                          主要实现:就是在循环里调用nand_write_page                                                 
          nand_write_page的主要功能:这个函数是把nand这个参数的nand->data_buf中从col到last的数据写入flash中
                                     static int nand_write_page (struct nand_chip *nand,
                                    int page, int col, int last, u_char * ecc_code)
                               其中,col是nand->data_buf中要写入flash的开始地址,last是nand->data_buf
                               中要写入flash的结束地址     
                           主要实现: 1、清空obb
                                      2、如果定义了MTD_NAND_ECC,就进行ECC校验并把ECC值写入相应地址,eccvalid_pos对应地址的值0x0f
                                      3、向databuf 中0~col和last~oobblock写入0xff并进行program
                                      4、如果定义了读确认,读出数据并与原数据比较,如果不同则打印信息
                                      5、如果定义了MTD_NAND_ECC,读出ECC并与原数据比较
                          备注:eccvalid_pos应该是表示在oob区分配一个字节用来表示ECC是否valid,如果没有这个验证功能
                                eccvalid_pos=-1.应该是这样的。 
   1.2 nor flash 
       nor flash也是嵌入式设备中常用的存储设备,nor 与 nand 相比,我觉得最大的区别是访问方式。nor 有独立的数据线和地址线,
       而nand使用通用的输入输出接口线。此外,nor可以片上执行,这就使得nor更适合存放bootloader最开始的代码了。所以,nor
       flash也是要好好分析的。
       <----------------------对norflash做介绍----------------------->
       在uboot中涉及到nor flash的地方有两个,一个是启动流程中的flash_init ,另一处是cmd_flash.c文件
       我们首先对flash_init实现的功能做简要介绍:
       说白了这个init函数主要实现对 flash_info_t  flash_info[CFG_MAX_FLASH_BANKS],这个数组的初始化,flash_info_t的定义如下
       typedef struct {
                 ulongsize; /* total bank size in bytes*/
                 ushortsector_count; /* number of erase units*/
                 ulongflash_id; /* combined device & manufacturer code*/
ulong start[CFG_MAX_FLASH_SECT];   /* physical sector start addresses */  每个物理块的开始地址
uchar protect[CFG_MAX_FLASH_SECT]; /* sector protection status*/        每个物理块保护状态  
#ifdef CFG_FLASH_CFI
uchar portwidth;/* the width of the port */
uchar chipwidth;/* the width of the chip */
ushort buffer_size; /* # of bytes in write buffer*/
ulong erase_blk_tout;/* maximum block erase timeout */
ulong write_tout;/* maximum write timeout */
ulong buffer_write_tout;/* maximum buffer write timeout */
ushort vendor; /* the primary vendor id*/
ushort cmd_reset; /* Vendor specific reset command*/
ushort interface; /* used for x8/x16 adjustments*/
#endif
} flash_info_t
确实,在这个函数中依次初始化了flash_id、size、sector_count、start、并对uboot和ENV写保护,上面几个元素都对上了。
flash_init里面调用了flash_protect函数里面有两个算法用得挺妙的,我也做个记录.
<----------------------对flash_pritect做介绍----------------------->
除了初始化函数顺便把对nor flash操作的实现也一并做个了结:
nor擦除函数
int flash_erase (flash_info_t * info, int s_first, int s_last)
1、首先是检查要擦除flash的信息检验一下ID,没有ID说明初始化的时候就失败了何谈擦除,判断擦除区域是否超出范围
2、判断是否有sector被保护,只要擦除区域中有一个sector被保护,该擦除不得进行
3、关闭中断和catche,这个我还没弄懂,虽然上面有注释。
4、连续几个周期向特定的地址发送特定的命令。flash的命令就是分几个周期向特定地址发送不同的命令
5、下面判断结束状态,打开关闭的中断和catche
 
nor写操作
volatile static int write_hword (flash_info_t * info, ulong dest, ushort data)
1、关中断和catche
2、检查flash有没有被擦除过,没有擦除的flash是不可以写数据的。
3、写入命令和数据并设置计时器等待结束,计时器是为了防止超时
 
当然,nor的读操作是非常非常简单的,就跟内存一样,不然怎能片上执行。
 
这里还要介绍一下nor与CPU的链接问题
nor flash的数据访问有byte、half word、word三种方式。
1、byte的方式很简单不用介绍
2、half word的接线方式是,arm的A1接flash的A0,这是因为ARM是以byte编址的,而flash是以 half word
          编址的,所以地址的对应关系
          ARM                   flash
          0x0                    0x0
          0x1                    0x0
          0x2                    0x1
          0x3                    0x1
          这个关系是addr(ARM)>> 1 == addr(flash),地址线的错位正好解决了这一点。
          #define SysAddr16(sysbase, offset)  ((volatile U16*)(sysbase)+(offset))
          这样定义了访问flash的宏,注意offset的偏移量是16位的。所以offset就是flsah中的地址了
          但是从ARM的角度看偏移量就是 2*offset
       3、word的访问方式和half word 的相同,就是错两位。  
                                    
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
///write最大只能写一个页一次   ,ECC对于每次写入数据时,先计算出ECC的值,并写入对应区域,
  然后在eccvalid_pos处写入0x0f,读出数据时,要读出ECC,并检查eccvalid_pos处的值是否为0x0f
  如果不是,根据读出ECC和计算得到的ECC对数据进行纠正                             
0 0
原创粉丝点击