u-boot-1.1.4代码阅读(转)

写在前面:通过uboot的阅读我学到了很多东西,高手写的代码就是不一样。

代码阅读顺序:
1.第一阶段(Stage 1)
第一阶段的启动代码在 cpu\<cpu type>\start.s中,完成的工作主要有:
  CPU自身初始化:包括 MMU,Cache,时钟系统,SDRAM 控制器等的初始化
   重定位:把自己从非易失性存储器搬移到 RAM中
   分配堆栈空间,设置堆栈指针
   清零 BSS 数据段
   跳转到第二阶段入口函数 start_armboot()
/Uboot114/u-boot-1.1.4/cpu/arm926ejs/start.S

2.第二阶段(Stage 2)
第二阶段是 u-boot 的主体,入口点是 lib_arm\board.c 中的 start_armboot()函数,完成的主要工作包括:
  为 U-boot 内部私有数据分配存储空间,并清零
  依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化
  如果系统支持 NOR Flash,调用 flash_init ()和 display_flash_config ()初始化并显示检测到的器件信息(AT91SAM9260EK不需要)
  如果系统支持LCD或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓冲(Framebuffer)大小,然后在 BSS 数据段之后为 Framebuffer 分配空间,初始化 gd->fb_base 为Framebuffer的起始地址(AT91SAM9260EK不需要)
  调用 mem_malloc_init()进行存储分配系统(类似于 C 语言中的堆)的初始化和空间分配
  如果系统支持 NAND Flash,调用 nand_init()进行初始化
  如果系统支持 DataFlash,调用 AT91F_DataflashInit()和 dataflash_print_info()进行初始化并显示检测到的器件信息
  调用 env_relocate ()进行环境变量的重定位,即从 Flash中搬移到 RAM 中
  如果系统支持 VFD,调用 drv_vfd_init()进行 VFD 设备初始化(AT91SAM9260EK 不需要)
   从环境变量中读取 IP 地址和 MAC 地址,初始化 gd->bd->bi_ip_addr和gd->bd->bi_enetaddr
   调用 jumptable_init()进行跳转表初始化,跳转表在global_data中,具体用途尚不清楚
   调用 console_init_r()进行控制台初始化
   如果需要,调用 misc_init_r()进行杂项初始化
   调用enable_interrupts()打开中断
   如果需要,调用 board_late_init()进行单板后期初始化,对于 AT91SAM9260EK,主要是以太网初始化
   进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令
/Uboot114/u-boot-1.1.4/lib_arm/board.c

3.U-boot 的初始化
 3.1  私有数据 global_data
 global_data /Uboot114/u-boot-1.1.4/include/asm-arm/global_data.h
 bd_info /Uboot114/u-boot-1.1.4/include/asm-arm/u-boot.h
 3.2  初始化序列 init_sequence
 Init_sequence是一个函数指针数组,数组中每一个元素都指向一个初始化函数。
 init_sequence /Uboot114/u-boot-1.1.4/lib_arm/board.c 
   (1)cpu_init u-boot-1.1.4\cpu\arm920t\cpu.c
      这个函数的功能是设置irq和fiq模式的堆栈起始点。AT91SAM9260EK 没有使用U-boot的中断机制,所以这个函数实际上什么也没做。
    (2)board_init \u-boot-1.1.4\board\at91rm9200dk\at91rm9200dk.c
    (3)interrupt_init \cpu\arm926ejs\interrupts.c
    (4)env_init \common\env_dataflash.c
    (5)serial_init \cpu\arm920t\at91rm9200\serial.c
 3.3  NAND Flash 初始化
 首先初始化NAND Flash接口,包括分配片选,设置片选的时序和模式,配置一些相关的IO口,然后调用nand_probe()检测NAND Flash。nand_probe()函数在common/cmd_nand.c中定义(这个文件实现了所有和NAND Flash相关的功能),调用NanD_ScanChips()函数搜索系统中的NAND Flash芯片(NanD_ScanChips()又调用NanD_IdentChip()检测芯片,实际上就是向NAND FLASH芯片发复位和读 ID命令,根据返回值判断芯片的型号和容量),具体实现细节不再描述。在这个版本的uboot中有一些出入,但是基本过程都差不多
 nand_init()完成NAND Flash的初始化。这个函数在board/at91rm9200dk/at91rm9200dk.c中。
 3.4  DataFlash 初始化
 AT91F_DataflashInit()完成 DataFlash 的初始化。这个函数在 drivers/dataflash.c 中。首先调用AT91F_SpiInit ()初始化SPI接口,然后调用AT91F_DataflashProbe()扫描所有的 SPI 片选,检测DataFlash是否存在,实现原理和NAND Flash类似,都是向芯片发送查询ID命令,根据返回值判断芯片的类型和容量。
 AT91F_SpiInit ()函数的定义在 cpu/arm926ejs/at91sam9260/spi.c中, 但是我这套代码中没有,我就把源码粘贴到这里了.
void AT91F_SpiInit(void) {
   volatile unsigned int dummy;
    
  AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA,
          (AT91C_PA0_SPI0_MISO | AT91C_PA1_SPI0_MOSI | 
           AT91C_PA2_SPI0_SPCK | AT91C_PA3_SPI0_NPCS0 |
           AT91C_PC11_SPI0_NPCS1 | AT91C_PC16_SPI0_NPCS2 | 
           AT91C_PC17_SPI0_NPCS3),
          0);
      
   AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_SPI0;
        
  AT91C_BASE_SPI0->SPI_CR = AT91C_SPI_SWRST;
       
   
  AT91C_BASE_SPI0->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS |
AT91C_SPI_PCS;
     
  AT91C_BASE_SPI0->SPI_CSR[0] = (AT91C_SPI_CPOL | (AT91C_SPI_DLYBS &
DATAFLASH_TCSS) |  
     (AT91C_SPI_DLYBCT & DATAFLASH_TCHS) | 
     (AT91C_MASTER_CLOCK / AT91C_SPI_CS0_CLK) << 8);
  AT91C_BASE_SPI0->SPI_CSR[3] = (AT91C_SPI_CPOL | (AT91C_SPI_DLYBS &
DATAFLASH_TCSS) |  
     (AT91C_SPI_DLYBCT & DATAFLASH_TCHS) | 
     (AT91C_MASTER_CLOCK / AT91C_SPI_CS3_CLK) << 8);
     
  AT91C_BASE_SPI0->SPI_CR = AT91C_SPI_SPIEN;
  while(!(AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_SPIENS));  
  // Add tempo to get SPI in a safe state.
    // Should be removed for new silicon (Rev B)
  udelay(500000);
  dummy = AT91C_BASE_SPI0->SPI_SR;
  dummy = AT91C_BASE_SPI0->SPI_RDR;
}
  AT91F_DataflashProbe()在\board\at91rm9200dk\at45.c

 3.5  环境变量重
 common/env_common.c 中的 env_relocate()函数实现环境变量的重定位:
 3.6  初始化设备 
 U-boot中设备的类型是 device_t,在 include/devices.h 中定义:
 device_t 的主体是一系列操作设备的函数指针,另外还包含了设备名称,标记和私有数据等等。
 common/devices.c 中的 devices_init 函数实现设备的初始化
 函数 drv_system_init ()总是要执行的,这个函数创建并注册了一个串行口设备和一个空设备(空设备是可选的),在common/devices.c 中定义:
 3.7  控制台初始化
 控制台初始化分两个阶段:console_init_f()和console_init_r()。console_init_f()完成的功能很简单,只是根据环境变量设置了global_data中的一些数据成分(hasconsole,flag); console_init_r()在common/console.c中定义,完成主要的控制台初始化工作:在设备链表中搜索 stdin,stdout,stderr设备;将搜索结果分别设为置控制台的in, out和err设备。
 通过console_setfile()函数可以看出,控制台有一个包含 3 个 device_t 元素的数组stdio_devices,分别对应 stdin,stdout,stderr。通过 stdio_devices[file] = dev 就可以将dev设成设置控制台的某个设备。这样就实现了控制台任意选择设备的功能。这和 linux 的设计思想有点类似。
 3.8 单板后期初始化
 函数 board_late_init()完成单板后期的初始化,对于AT91SAM9260EK,这个函数在board/at91sam9260ek/at91sam9260ek.c中定义,调用 cpu/arm926ejs/at91sam9260/at91_emac.c中的函数 eth_init()完成以太网的初始化。
 我的版本中没有cpu/arm926ejs/at91sam9260/at91_emac.c这个文件下面是部分源码:
int eth_init (bd_t * bd)
{
  unsigned int periphAEnable, periphBEnable;
  unsigned int val, i;
 int ret;
 
 p_mac = AT91C_BASE_EMACB;
 

#ifdef CONFIG_AT91C_USE_RMII
  periphAEnable = ((unsigned int) AT91C_PA21_EMDIO     ) |
  ((unsigned int) AT91C_PA20_EMDC    ) |
  ((unsigned int) AT91C_PA19_ETXCK   ) |
  ((unsigned int) AT91C_PA18_ERXER   ) |
  ((unsigned int) AT91C_PA14_ERX0    ) |
  ((unsigned int) AT91C_PA17_ERXDV   ) |
  ((unsigned int) AT91C_PA15_ERX1    ) |
  ((unsigned int) AT91C_PA16_ETXEN   ) |
    ((unsigned int) AT91C_PA12_ETX0    ) |
    ((unsigned int) AT91C_PA13_ETX1    );
 
 periphBEnable = 0;
#else
  periphAEnable = ((unsigned int) AT91C_PA21_EMDIO     ) |
  ((unsigned int) AT91C_PA19_ETXCK   ) |
  ((unsigned int) AT91C_PA20_EMDC    ) |
  ((unsigned int) AT91C_PA18_ERXER   ) |
  ((unsigned int) AT91C_PA14_ERX0    ) |
  ((unsigned int) AT91C_PA17_ERXDV   ) |
  ((unsigned int) AT91C_PA15_ERX1    ) |
 ((unsigned int) AT91C_PA16_ETXEN   ) |
    ((unsigned int) AT91C_PA12_ETX0    ) |
    ((unsigned int) AT91C_PA13_ETX1    );
  
  periphBEnable = ((unsigned int) AT91C_PA27_ERXCK     ) |
    ((unsigned int) AT91C_PA29_ECOL    ) |
  ((unsigned int) AT91C_PA25_ERX2    ) |
  ((unsigned int) AT91C_PA26_ERX3    ) |
  ((unsigned int) AT91C_PA22_ETXER   ) |
    ((unsigned int) AT91C_PA10_ETX2    ) |   
    ((unsigned int) AT91C_PA11_ETX3    ) |
  ((unsigned int) AT91C_PA28_ECRS    );
#endif
  AT91C_BASE_PIOA->PIO_ASR = periphAEnable;
 AT91C_BASE_PIOA->PIO_BSR = periphBEnable;
  AT91C_BASE_PIOA->PIO_PDR = (periphAEnable | periphBEnable);
  
/ * 禁止收发,清除收发状态寄存器 */  
  p_mac->EMAC_NCR = 0;
  p_mac->EMAC_TSR = 0xFFFFFFFF;
 p_mac->EMAC_RSR = 0xFFFFFFFF;
 
/ * 打开 MAC模块的时钟 */  
  *AT91C_PMC_PCER = 1 << AT91C_ID_EMAC; 
 
/ * 禁止 PA17(RXDV)  的上拉电阻 */
  AT91C_BASE_PIOA->PIO_PPUDR = 1 << 17;
 
  / *  选择 MAC接口为 MII 模式 */
  p_mac->EMAC_USRIO = AT91C_EMAC_CLKEN;
 
#ifdef CONFIG_AT91C_USE_RMII
 p_mac->EMAC_USRIO |= AT91C_EMAC_RMII;
#endif
 
 
 RxBuffIndex = 0;
 TxBuffIndex = 0;
 
/ * 初始化接收缓冲区描述符链表 */
  for (i = 0; i < RBF_FRAMEMAX; ++i) {
  val = (unsigned int)(rbf_framebuf[i]);
    RxtdList[i].addr = val & 0xFFFFFFF8;
     RxtdList[i].U_Status.status = 0;
 
 
 RxtdList[RBF_FRAMEMAX-1].addr |= RBF_WRAP;
 
/ * 初始化发送缓冲区描述符链表 */
  for (i = 0; i < TBF_FRAMEMAX; ++i) {
  val = (unsigned int)(tbf_framebuf[i]);
    TxtdList[i].addr = val & 0xFFFFFFF8;
  TxtdList[i].U_Status.status = 0;
  TxtdList[i].U_Status.S_Status.BuffUsed = 1;
 }
  TxtdList[0].U_Status.S_Status.BuffUsed = 0;
 
 TxtdList[TBF_FRAMEMAX-1].U_Status.S_Status.Wrap = 1;
 
  / *  获取 PHY芯片的操作函数 */
 at91sam9260_GetPhyInterface (&PhyOps);
   
  / *  判断 PHY芯片是否正常连接 */
  if (!PhyOps.IsPhyConnected (p_mac))
 {
    printf ("PHY not connected!\n\r");
  return -1;
 }
 else
    printf ("PHY is connected!\n\r");
 
/ * 调用 PHY芯片的初始化函数:读取 PHY芯片的工作模式,把 MAC的工作模式设
为和 PHY一致 */
 
  ret = PhyOps.Init (p_mac);
  if ( !ret && 0 )
 {
    printf ("MAC: error during MAC initialization\n");
  return -1;
 }
 
/ * MAC初始化 */
  AT91F_EMACInit(bd, (unsigned int)RxtdList, (unsigned int)TxtdList);
   
 return 0;
}
int AT91F_EMACInit(bd_t * bd,
     unsigned int pRxTdList,
     unsigned int pTxTdList)
{
  unsigned int tick = 0;
  unsigned short status;
    
/ * 等待 PHY芯片自协商完成 */
 at91sam9260_EmacEnableMDIO(p_mac);
    
 do {
  at91sam9260_EmacReadPhy(p_mac, AT91C_PHY_ADDR, MII_BMSR, &status);
  at91sam9260_EmacReadPhy(p_mac, AT91C_PHY_ADDR, MII_BMSR, &status);  
 
  tick++;
 }
  while (!(status & BMSR_ANEGCOMPLETE) && (tick < AT91C_ETH_TIMEOUT));
 at91sam9260_EmacDisableMDIO(p_mac);
 printf ("End of Autonegociation\n\r");
    
  / *  设置 MAC地址 */
 
  p_mac->EMAC_SA1L = (bd->bi_enetaddr[3] << 24) | (bd->bi_enetaddr[2] << 16)
    | (bd->bi_enetaddr[1] <<   8) | (bd->bi_enetaddr[0]);
  p_mac->EMAC_SA1H = (bd->bi_enetaddr[5] <<   8) | (bd->bi_enetaddr[4]);
 
/ * 设置接收、发送缓冲区队列头指针 */
 p_mac->EMAC_RBQP = pRxTdList;
 p_mac->EMAC_TBQP = pTxTdList;
 
/ * 清除接收状态寄存器相关位 */
  p_mac->EMAC_RSR  &= ~(AT91C_EMAC_OVR | AT91C_EMAC_REC |
AT91C_EMAC_BNA);
 
/ * 设置配置寄存器:拷贝所有帧,不支持广播  */
 p_mac->EMAC_NCFGR  |= (AT91C_EMAC_CAF | AT91C_EMAC_NBC );
 p_mac->EMAC_NCFGR  &= ~(AT91C_EMAC_CLK);
 
/ * 设置 PHY管理接口的时钟速率,不能超过 2.5MHz  */
#if (AT91C_MASTER_CLOCK > 40000000)
 
 p_mac->EMAC_NCFGR |= AT91C_EMAC_CLK_HCLK_64;
#endif
/ * 设置控制寄存器:接收允许,发送允许,允许统计功能 */ 
p_mac->EMAC_NCR  |= (AT91C_EMAC_TE | AT91C_EMAC_RE |
AT91C_EMAC_WESTAT);
 
 return 0;
}

命令处理    
 4.1 命令数据结构
  U-boot的命令用struct  cmd_tbl_t来实现。cmd_tbl_t的主要数据成分是命令名称(name)和命令处理函数(cmd),此外还包括最大参数个数(maxargs),是否可重复执行(repeatable),使用方法和帮助信息(usage,help)等。这个数据结构在文件include/command.h中定义:
  包含在 include/command.h中的宏 U_BOOT_CMD用来定义命令:
   #define Struct_Section  __attribute__ ((unused,section (".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}
 例如 bootm命令的定义:
 U_BOOT_CMD(
    bootm,  CFG_MAXARGS, 1,  do_bootm,
    "bootm      - boot application image from memory\n",
    "[addr [arg ...]]\n    - boot application image stored in memory\n"   
  "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
    "\t'arg' can be the address of an initrd image\n"
 );
 展开后就变成:
 cmd_tbl_t  __u_boot_cmd_bootm  __attribute__ ((unused,section (".u_boot_cmd"))) =
 {
   "bootm", 
    CFG_MAX_ARGS,
    1,  
    do_bootm,   
    "bootm   - boot application image from memory\n",
    "[addr [arg ...]]\n    - boot application image stored in memory\n"      
    "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
    "\t'arg' can be the address of an initrd image\n"        
 };
 
 这样就为bootm命令定义了一个cmd_tbl_t 结构。注意定义的结构是放在”.u_boot.cmd”段中的,这是为了实现命令的查找功能。
 4.2 命令查找
 要想执行命令,必须先找到这个命令对应的cmd_tbl_t,然后才能调用命令处理函数。文件common/command.c 中的find_command()用来查找命令。这个函数根据命令名称来搜索命令列表,返回命令的cmd_tbl_t指针。
 4.3 主循环
  主循环的实现函数main_loop()在common/main.c中。这个函数包含了很多与AT91SAM9260EK关系不大的条件编译语句。
  其中最重要的函数有两个:abortboot ()和 run_command()。
  (1) abortboot ()
  这个函数用来判断在指定的时间期限内用户是否选择了中止系统的引导。函数的定义也在common/main.c中。
  (2) run_command()
  这个函数用来执行命令。它可以执行一条命令,也可以执行用”;”分隔的多条命令(例如“nandread 20400000 0 200000; nand read 21100000 200000 400000; bootm 20400000”)。
  
linux 的引导
  5.1 映象格式
  映象文件必须满足U-boot的格式要求,才能被识别和引导。U-boot中映象文件必须以一个固定格式的头部开始。这个头部由struct image_header_t描述,image_header_t的定义在文件include/image.h中。   
  U-boot以源代码的形式提供了一个映象文件制作工具 mkimage(在 tools目录下),这个工具可以为指定的映象文件增加一个 image_header_t头部。
  5.2 linux引导
  通过前面的分析,我们知道,如果启动过程中用户不按键中止引导,命令序列bootcmd将会被执行。对于AT91SAM9260eK板,bootcmd的内容是"nand read 20400000 0 200000;nand read 21100000 200000 400000;bootm 20400000"。这意味着将执行三条命令:
  (1) nand read 20400000 0 200000
   将NAND Flash中从0开始长度为200000(2MB)的数据块读入地址20400000。 NAND Flash中从0开始存放的是linux内核映象,20400000是SDRAM 的地址。所以这条命令的功能是把linux内核映象从NAND Flash读到SDRAM中。注意这个映象是满足Uboot格式要求,即是以image_header_t头部开始的。
  (2) nand read 21100000 200000 400000
  将NAND Flash中从200000开始长度为400000(4MB)的数据块读入地址21100000。 NAND Flash中从200000开始存放的是linux根文件系统映象,21100000是SDRAM的地址。所以这条命令的功能是把linux根文件系统映象从NAND Flash读到SDRAM中。这个映象不需要满足U-boot的格式要求。
 (3) bootm 20400000
  执行bootm命令引导linux。命令的参数是linux内核所在的地址20400000。
 
 由此可见,linux的引导是通过bootm命令实现的。这个命令的处理函数是do_bootm(),在文件common/cmd_bootm.c中。U-boot可以引导多种操作系统。例如linux,vxworks,netbsd,QNX等等。
 linux引导的第二阶段do_bootm_linux(),这个函数在lib_arm/armlinux.c 中。
 5.3 linux的内核参数传递
  linux引导的最后阶段,需要设置传递给内核的参数块,参数块的地址是物理内存起点+0x100(0x20000100 for AT91SAM9260EK)。Linux 2.6 要求使用tagged list的方式设置参数块。do_bootm_linux()中使用 setup_start_tag(),setup_end_tag(),setup_XXX-tag()来完成参数块的设置。具体到AT91SAM9260EK板,调用的函数依次是setup_start_tag(),setup_memory_tags(),setup_commandline_tag(),setup_initrd_tag()和setup_end_tag()。这些函数的定义都在lib_arm/armlinux.c中。
  (1) setup_start_tag()
  (2) setup_memory_tags()
   每个memory tag表示一个存储区间。setup_memory_tags()设置所有的存储区间。
  (3) setup_commandline_tag()
  (4) setup_initrd_tag()
  (5) setup_end_tag()
  这个函数表示整个tagged list的结束。
 至于tag类型的定义和基本操作,则是在include/asm-arm/setup.h中。

posted @ 2009-08-20 15:35  Owen Wilson  阅读(2687)  评论(0编辑  收藏  举报