加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

u-boot源码分析

发布时间:2020-12-15 18:31:26 所属栏目:百科 来源:网络整理
导读:本文从以下几个方面粗浅地分析u-boot并移植到FS2410板上: 1、u-boot工程的总体结构 2、u-boot的流程、主要的数据结构、内存分配。 3、u-boot的重要细节,主要分析流程中各函数的功能。 4、基于FS2410板子的u-boot移植。实现了NOR Flash和NAND Flash启动,网

本文从以下几个方面粗浅地分析u-boot并移植到FS2410板上:
1、u-boot工程的总体结构
2、u-boot的流程、主要的数据结构、内存分配。
3、u-boot的重要细节,主要分析流程中各函数的功能。
4、基于FS2410板子的u-boot移植。实现了NOR Flash和NAND Flash启动,网络功能。 
这些认识源于自己移植u-boot过程中查找的资料和对源码的简单阅读。下面主要以smdk2410为分析对象。

一、u-boot工程的总体结构:
1、源代码组织
对于ARM而言,主要的目录如下:
board????????????????? 平台依赖????????  存放电路板相关的目录文件,每一套板子对 应一个目录。如smdk2410(arm920t)
?????????????????????????????????????  ?????????????????????????????????????????????????????????????????????????????
cpu??????????????????? 平台依赖?????????  存放CPU相关的目录文件,每一款CPU对应一个目录,例如:arm920t、 xscale、i386等目录
lib_arm??????????????? 平台依赖??????????  存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数,如软件浮点。
common????????????? 通用????????? 通用的多功能函数实现,如环境,命令,控制台相关的函数实现。
include??????????????? 通用?????????????? 头文件和开发板配置文件,所有开发板的配置文件都在configs目录下???????????????????????????????????????
lib_generic???????? 通用???????????? 通用库函数的实现
net??????????????????? 通用??????????????? 存放网络协议的程序
drivers????????????? 通用?????????????? 通用的设备驱动程序,主要有以太网接口的驱动,nand驱动。
.......
2.makefile简要分析
所有这些目录的编译连接都是由顶层目录的makefile来确定的。
在执行make之前,先要执行make $(board)_config 对工程进行配置,以确定特定于目标板的各个子目录和头文件。
$(board)_config:是makefile 中的一个伪目标,它传入指定的CPU,ARCH,BOARD,SOC参数去执行mkconfig脚本。
这个脚本的主要功能在于连接目标板平台相关的头文件夹,生成config.h文件包含板子的配置头文件。
使得makefile能根据目标板的这些参数去编译正确的平台相关的子目录。
以smdk2410板为例,执行 make smdk2410_config,
主要完成三个功能:
@在include文件夹下建立相应的文件(夹)软连接,
#如果是ARM体系将执行以下操作:
#ln -s???? asm-arm??????? asm??
#ln -s? arch-s3c24x0??? asm-arm/arch
#ln -s?? proc-armv?????? asm-arm/proc
@生成Makefile包含文件include/config.mk,内容很简单,定义了四个变量:
ARCH?? = arm
CPU??? = arm920t
BOARD? = smdk2410
SOC??? = s3c24x0
@生成include/config.h头文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"
顶层makefile先调用各子目录的makefile,生成目标文件或者目标文件库。
然后再连接所有目标文件(库)生成最终的u-boot.bin。
连接的主要目标(库)如下:
OBJS? = cpu/$(CPU)/start.o
LIBS? = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
显然跟平台相关的主要是:
cpu/$(CPU)/start.o
board/$(BOARDDIR)/lib$(BOARD).a 
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a 
lib_$(ARCH)/lib$(ARCH).a
这里面的四个变量定义在include/config.mk(见上述)。
其余的均与平台无关。
所以考虑移植的时候也主要考虑这几个目标文件(库)对应的目录。
关于u-boot 的makefile更详细的分析可以参照
http://blog.mcuol.com/User/lvembededsys/Article/4355_1.htm

3、u-boot的通用目录是怎么做到与平台无关的?
include/config/smdk2410.h??
这个头文件中主要定义了两类变量。
 一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等,主要用来 决定是否编译某些文件或者函数。
另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。这些常数参量主要用来支持通用目录中的代码,定义板子资源参数。
这两类宏定义对u-boot的移植性非常关键,比如drive/CS8900.c,对cs8900而言,很多操作都是通用的,但不是所有的板子上面都有这个芯片,即使有它在内存中映射的基地址也是平台相关的。所以对于smdk2410板,在smdk2410.h中定义了
#define CONFIG_DRIVER_CS8900 1????????????? /* we have a CS8900 on-board */
#define CS8900_BASE 0x19000300????????????? /*IO mode base address*/
CONFIG_DRIVER_CS8900的定义使得cs8900.c可以被编译(当然还得定义CFG_CMD_NET才行),因为cs8900.c中在函数定义的前面就有编译条件判断:#ifdef CONFIG_DRIVER_CS8900 如果这个选项没有定义,整个cs8900.c就不会被编译了。
而常数参量CS8900_BASE则用在cs8900.h头文件中定义各个功能寄存器的地址。u-boot的CS8900工作在IO模式下,只要给定IO寄存器在内存中映射的基地址,其余代码就与平台无关了。
?
? u-boot的命令也是通过目标板的配置头文件来配置的,比如要添加ping命令,就必须添加CFG_CMD_NET和CFG_CMD_PING才行。不然common/cmd_net.c就不会被编译了。
从这里我可以这么认为,u-boot工程可配置性和移植性可以分为两层:
一是由makefile来实现,配置工程要包含的文件和文件夹上,用什么编译器。
二是由目标板的配置头文件来实现源码级的可配置性,通用性。主要使用的是#ifdef #else #endif 之类来实现的。
4、smkd2410其余重要的文件:
include/s3c24x0.h  ????? 定义了s3x24x0芯片的各个特殊功能寄存器(SFR)的地址。
cpu/arm920t/start.s???????? 在flash中执行的引导代码,也就是bootloader中的stage1,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳到lib_arm/board.c中的start_armboot中去执行。
lib_arm/board.c  ??????? u-boot的初始化流程,尤其是u-boot用到的全局数据结构gd,bd的初始化,以及设备和控制台的初始化。
board/smdk2410/flash.c?????? 在board目录下代码的都是严重依赖目标板,对于不同的CPU,SOC,ARCH,u-boot都有相对通用的代码,但是板子构成却是多样的,主要是内存地址,flash型号,外围芯片如网络。对fs2410来说,主要考虑从smdk2410板来移植,差别主要在nor flash上面。
二、u-boot的流程、主要的数据结构、内存分配
1、u-boot的启动流程:
  从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c, 
  1)start.s 
?? 在flash中执行的引导代码,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳到lib_arm/board.c中的start_armboot中去执行。
1.1.6版本的start.s流程:
硬件环境初始化:
??   进入svc模式;关闭watch dog;屏蔽所有IRQ掩码;设置时钟频率FCLK、HCLK、PCLK;清I/D cache;禁止MMU和CACHE;配置memory control;
重定位:
??   如果当前代码不在连接指定的地址上(对smdk2410是0x3f000000)则需要把u-boot从当前位置拷贝到RAM指定位置中;
建立堆栈,堆栈是进入C函数前必须初始化的。
清.bss区。
跳到start_armboot函数中执行。(lib_arm/board.c)
2)lib_arm/board.c:
?  start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。这里只简要列出了主要执行的函数流程:
?? void start_armboot (void)
?? {
?????? //全局数据变量指针gd占用r8。
????????? DECLARE_GLOBAL_DATA_PTR;
?????????
????????? /* 给全局数据变量gd安排空间*/
????????? gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
????????? memset ((void*)gd,sizeof (gd_t));
?????????
????????? /* 给板子数据变量gd->bd安排空间*/
????????? gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
????????? memset (gd->bd,sizeof (bd_t));
????????? monitor_flash_len = _bss_start - _armboot_start;//取u-boot的长度。
?????????
????????? /* 顺序执行init_sequence数组中的初始化函数 */
????????? for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
???????????????? if ((*init_fnc_ptr)() != 0) {
???????????????????????? hang ();
???????????????? }
????????? }
?????????
????????? /*配置可用的Flash */
????????? size = flash_init ();
??????  ……
????????? /* 初始化堆空间 */
????????? mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
????????? /* 重新定位环境变量, */
????????? env_relocate ();
????????? /* 从环境变量中获取IP地址 */
????????? gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
????????? /* 以太网接口MAC 地址 */
????????? ……
????????? devices_init ();????? /* 设备初始化 */
????????? jumptable_init ();? //跳转表初始化
????????? console_init_r ();??? /* 完整地初始化控制台设备 */
????????? enable_interrupts (); /* 使能中断处理 */
????????? /* 通过环境变量初始化 */
????????? if ((s = getenv ("loadaddr")) != NULL) {
????????????????? load_addr = simple_strtoul (s,NULL,16);
????????? }
????????? /* main_loop()循环不断执行 */
????????? for (;;) {
????????????????? main_loop ();????? /* 主循环函数处理执行用户命令 -- common/main.c */
????????? }
?? }
初始化函数序列init_sequence[]
? init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。
??
? init_fnc_t *init_sequence[] = {
???????? cpu_init,???????????? /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */
???????? board_init,?????????? /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */
???????? interrupt_init,?????? /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */
???????? env_init,???????????? /* 初始化环境变量 -- common/env_flash.c */
???????? init_baudrate,??????? /* 初始化波特率设置 -- lib_arm/board.c */
???????? serial_init,????????? /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */
???????? console_init_f,?????? /* 控制台初始化阶段1 -- common/console.c */
???????? display_banner,?????? /* 打印u-boot信息 -- lib_arm/board.c */
???????? dram_init,??????????? /* 配置可用的RAM -- board/smdk2410/smdk2410.c */
???????? display_dram_config,? /* 显示RAM的配置大小 -- lib_arm/board.c */
???????? NULL,
? };
整个u-boot的执行就进入等待用户输入命令,解析并执行命令的死循环中。
2、u-boot主要的数据结构
u-boot的主要功能是用于引导OS的,但是本身也提供许多强大的功能,可以通过输入命令行来完成许多操作。所以它本身也是一个很完备的系统。u-boot的大部分操作都是围绕它自身的数据结构,这些数据结构是通用的,但是不同的板子初始化这些数据就不一样了。所以u-boot的通用代码是依赖于这些重要的数据结构的。这里说的数据结构其实就是一些全局变量。
 1)gd 全局数据变量指针,它保存了u-boot运行需要的全局数据,类型定义:
 typedef struct global_data {
?????????? bd_t? *bd;????? //board data pointor板子数据指针
?????????? unsigned long flags;  //指示标志,如设备已经初始化标志等。
?????????? unsigned long baudrate; //串口波特率
?????????? unsigned long have_console; /* 串口初始化标志*/
?????????? unsigned long reloc_off;?? /* 重定位偏移,就是实际定向的位置与编译连接时指定的位置之差,一般为0 */
?????????? unsigned long env_addr; /* 环境参数地址*/
?????????? unsigned long env_valid; /* 环境参数CRC检验有效标志 */
?????????? unsigned long fb_base; /* base address of frame buffer */
?????????  #ifdef CONFIG_VFD
?????????? unsigned char vfd_type; /* display type */
?????????  #endif
?????????? void? **jt;? /* 跳转表,1.1.6中用来函数调用地址登记 */
????????? } gd_t;
? 2)bd 板子数据指针。板子很多重要的参数。 类型定义如下:???
?? typedef struct bd_info {
???????????? int?? bi_baudrate;???? /* 串口波特率 */
???????????? unsigned long bi_ip_addr;?? /* IP 地址 */
???????????? unsigned char bi_enetaddr[6]; /* MAC地址*/
???????????? struct environment_s??????? *bi_env;
???????????? ulong???????? bi_arch_number; /* unique id for this board */
???????????? ulong???????? bi_boot_params; /* 启动参数 */
???????????? struct??? /* RAM 配置 */
???????????? {
??????????? ulong start;
??????????? ulong size;
???????????? }bi_dram[CONFIG_NR_DRAM_BANKS];
???????? } bd_t;
? 3)环境变量指针 env_t *env_ptr = (env_t *)(&environment[0]);(common/env_flash.c)
?  env_ptr指向环境参数区,系统启动时默认的环境参数environment[],定义在common/environment.c中。 
?  参数解释:
??? bootdelay 定义执行自动启动的等候秒数
??? baudrate 定义串口控制台的波特率
??? netmask 定义以太网接口的掩码
??? ethaddr 定义以太网接口的MAC地址
??? bootfile 定义缺省的下载文件
??? bootargs 定义传递给Linux内核的命令行参数
??? bootcmd 定义自动启动时执行的几条命令
??? serverip 定义tftp服务器端的IP地址
??? ipaddr 定义本地的IP地址
??? stdin 定义标准输入设备,一般是串口
??? stdout 定义标准输出设备,一般是串口
??? stderr 定义标准出错信息输出设备,一般是串口
? 4)设备相关:
?? 标准IO设备数组?evice_t *stdio_devices[] = { NULL,NULL };
?? 设备列表    list_t??? devlist = 0;
?? device_t的定义:includedevices.h中:
??? typedef struct {
???? int flags;???       /* Device flags: input/output/system */
???? int ext;?????      /* Supported extensions?? */
???? char name[16];??      /* Device name??? */???
??? /* GENERAL functions */???
???? int (*start) (void);?    /* To start the device?? */
???? int (*stop) (void);?     /* To stop the device?? */???
??? /* 输出函数 */???
???? void (*putc) (const char c); /* To put a char?? */
???? void (*puts) (const char *s); /* To put a string (accelerator) */??
??? /* 输入函数 */??
???? int (*tstc) (void);?     /* To test if a char is ready... */
???? int (*getc) (void);?     /* To get that char?? */??
??? /* Other functions */???
???? void *priv;??        /* Private extensions?? */
??? } device_t;
?  u-boot把可以用为控制台输入输出的设备添加到设备列表devlist,并把当前用作标准IO的设备指针加入stdio_devices数组中。
?  在调用标准IO函数如printf()时将调用stdio_devices数组对应设备的IO函数如putc()。
???? 5)命令相关的数据结构,后面介绍。
???? 6)与具体设备有关的数据结构,
????  如flash_info_t flash_info[CFG_MAX_FLASH_BANKS];记录nor flash的信息。
????  nand_info_t nand_info[CFG_MAX_NAND_DEVICE]; nand flash块设备信息
3、u-boot重定位后的内存分布:
   对于smdk2410,RAM范围从0x30000000~0x34000000. u-boot占用高端内存区。从高地址到低地址内存分配如下:

 显示缓冲区??????????????? (.bss_end~34000000)
???? u-boot(bss,data,text)? (33f00000~.bss_end)
???? heap(for malloc)
???? gd(global data)
???? bd(board data)
???? stack????????????????????????
???? ....
???? nor flash????????????????????? (0~2M)
三、u-boot的重要细节。
主要分析流程中各函数的功能。按启动顺序罗列一下启动函数执行细节。按照函数start_armboot流程进行分析:
??? 1)DECLARE_GLOBAL_DATA_PTR;
???? 这个宏定义在include/global_data.h中:
???? #define DECLARE_GLOBAL_DATA_PTR???? register volatile gd_t *gd asm ("r8")
???? 声明一个寄存器变量 gd 占用r8。这个宏在所有需要引用全局数据指针gd_t *gd的源码中都有申明。
???? 这个申明也避免编译器把r8分配给其它的变量. 所以gd就是r8,这个指针变量不占用内存。
??? 2)gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
??? 对全局数据区进行地址分配,_armboot_start为0x3f000000,CFG_MALLOC_LEN是堆大小+环境数据区大小,config/smdk2410.h中CFG_MALLOC_LEN大小定义为192KB.
??? 3)gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
??? 分配板子数据区bd首地址。
??? 这样结合start.s中栈的分配,
??? stack_setup:
??? ldr r0,_TEXT_BASE? /* upper 128 KiB: relocated uboot?? */
??? sub r0,r0,#CFG_MALLOC_LEN /* malloc area????????????????????? */
??? sub r0,#CFG_GBL_DATA_SIZE /* bdinfoCFG_GBL_DATA_SIZE =128B */
??? #ifdef CONFIG_USE_IRQ
??? sub r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
??? #endif
??? sub sp,#12? /* leave 3 words for abort-stack??? */
? 不难得出上文所述的内存分配结构。
? 下面几个函数是初始化序列表init_sequence[]中的函数:
? 4)cpu_init();定义于cpu/arm920t/cpu.c
? 分配IRQ,FIQ栈底地址,由于没有定义CONFIG_USE_IRQ,所以相当于空实现。
? 5)board_init;极级初始化,定义于board/smdk2410/smdk2410.c
?  设置PLL时钟,GPIO,使能I/D cache.
??? 设置bd信息:gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;//板子的ID,没啥意义。
?????????? gd->bd->bi_boot_params = 0x30000100;//内核启动参数存放地址
??? 6)interrupt_init;定义于cpu/arm920t/s3c24x0/interrupt.c
???  初始化2410的PWM timer 4,使其能自动装载计数值,恒定的产生时间中断信号,但是中断被屏蔽了用不上。
??? 7)env_init;定义于common/env_flash.c(搜索的时候发现别的文件也定义了这个函数,而且没有宏定义保证只有一个被编译,这是个问题,有高手知道指点一下!)
? 功能:指定环境区的地址。default_environment是默认的环境参数设置。
?? gd->env_addr? = (ulong)&default_environment[0];
?? gd->env_valid = 0;
8)init_baudrate;初始化全局数据区中波特率的值
? gd->bd->bi_baudrate = gd->baudrate =(i > 0)
?? ? (int) simple_strtoul (tmp,10)
?? : CONFIG_BAUDRATE;
??? 9)serial_init; 串口通讯设置 定义于cpu/arm920t/s3c24x0/serial.c
???  根据bd中波特率值和pclk,设置串口寄存器。
??? 10)console_init_f;控制台前期初始化common/console.c
??? 由于标准设备还没有初始化(gd->flags & GD_FLG_DEVINIT=0),这时控制台使用串口作为控制台
??? 函数只有一句:gd->have_console = 1;
??? 10)dram_init,初始化内存RAM信息。board/smdk2410/smdk2410.c
??? 其实就是给gd->bd中内存信息表赋值而已。
??? gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
 gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
 初始化序列表init_sequence[]主要函数分析结束。
? 11)flash_init;定义在board/smdk2410/flash.c
?? 这个文件与具体平台关系密切,smdk2410使用的flash与FS2410不一样,所以移植时这个程序就得重写。
?? flash_init()是必须重写的函数,它做哪些操作呢?
?? 首先是有一个变量flash_info_t flash_info[CFG_MAX_FLASH_BANKS]来记录flash的信息。flash_info_t定义:
?? typedef struct {
??? ulong size;?? /* 总大小BYTE? */
??? ushort sector_count;? /* 总的sector数*/
??? ulong flash_id;? /* combined device & manufacturer code */
??? ulong start[CFG_MAX_FLASH_SECT];?? /* 每个sector的起始物理地址。 */
??? uchar protect[CFG_MAX_FLASH_SECT]; /* 每个sector的保护状态,如果置1,在执行erase操作的时候将跳过对应sector*/
???? #ifdef CFG_FLASH_CFI //我不管CFI接口。
??? .....
???? #endif
?? } flash_info_t;
??? flash_init()的操作就是读取ID号,ID号指明了生产商和设备号,根据这些信息设置size,sector_count,flash_id.以及start[]、protect[]。
??? 12)把视频帧缓冲区设置在bss_end后面。
???  addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
?? size = vfd_setmem (addr);
?? gd->fb_base = addr;
? 13)mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
?? 设置heap区,供malloc使用。下面的变量和函数定义在lib_arm/board.c
?? malloc可用内存由mem_malloc_start,mem_malloc_end指定。而当前分配的位置则是mem_malloc_brk。
?? mem_malloc_init负责初始化这三个变量。malloc则通过sbrk函数来使用和管理这片内存。
??? static ulong mem_malloc_start = 0;
??? static ulong mem_malloc_end = 0;
??? static ulong mem_malloc_brk = 0;
??? static
??? void mem_malloc_init (ulong dest_addr)
??? {
???? mem_malloc_start = dest_addr;
???? mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
???? mem_malloc_brk = mem_malloc_start;
???
???? memset ((void *) mem_malloc_start,
?????? mem_malloc_end - mem_malloc_start);
??? }
??? void *sbrk (ptrdiff_t increment)
??? {
???? ulong old = mem_malloc_brk;
???? ulong new = old + increment;
???
???? if ((new? mem_malloc_end)) {
????? return (NULL);
???? }
???? mem_malloc_brk = new;
???? return ((void *) old);
??? }
14)env_relocate() 环境参数区重定位
? 由于初始化了heap区,所以可以通过malloc()重新分配一块环境参数区,
? 但是没有必要,因为默认的环境参数已经重定位到RAM中了。
? /**这里发现个问题,ENV_IS_EMBEDDED是否有定义还没搞清楚,而且CFG_MALLOC_LEN也没有定义,也就是说如果ENV_IS_EMBEDDED没有定义则执行malloc,是不是应该有问题?**/
? 15)IP,MAC地址的初始化。主要是从环境中读,然后赋给gd->bd对应域就OK。
? 16)devices_init ();定义于common/devices.c
? int devices_init (void)//我去掉了编译选项,注释掉的是因为对应的编译选项没有定义。
?? {
???? devlist = ListCreate (sizeof (device_t));//创建设备列表
??? i2c_init (CFG_I2C_SPEED,CFG_I2C_SLAVE);//初始化i2c接口,i2c没有注册到devlist中去。
??? //drv_lcd_init ();
??? //drv_video_init ();
??? //drv_keyboard_init ();
??? //drv_logbuff_init ();
??? drv_system_init ();  //这里其实是定义了一个串口设备,并且注册到devlist中。
??? //serial_devices_init ();
??? //drv_usbtty_init ();
??? //drv_nc_init ();
?? }
  经过devices_init(),创建了devlist,但是只有一个串口设备注册在内。显然,devlist中的设备都是可以做为console的。
16) jumptable_init ();初始化gd->jt。1.1.6版本的jumptable只起登记函数地址的作用。并没有其他作用。
17)console_init_r ();后期控制台初始化
???? 主要过程:查看环境参数stdin,stdout,stderr中对标准IO的指定的设备名称,再按照环境指定的名称搜索devlist,将搜到的设备指针赋给标准IO数组stdio_devices[]。置gd->flag标志GD_FLG_DEVINIT。这个标志影响putc,getc函数的实现,未定义此标志时直接由串口serial_getc和serial_putc实现,定义以后通过标准设备数组stdio_devices[]中的putc和getc来实现IO。
下面是相关代码:
??? void putc (const char c)
???????? {
???????? #ifdef CONFIG_SILENT_CONSOLE
????????? if (gd->flags & GD_FLG_SILENT)//GD_FLG_SILENT无输出标志
?????????? return;
???????? #endif
????????? if (gd->flags & GD_FLG_DEVINIT) {//设备list已经初始化
?????????? /* Send to the standard output */
?????????? fputc (stdout,c);
????????? } else {
?????????? /* Send directly to the handler */
?????????? serial_putc (c);//未初始化时直接从串口输出。
????????? }
???????? }
?????? void fputc (int file,const char c)
??????? {
???????? if (file putc (c);
??????? }
为什么要使用devlist,std_device[]?
为了更灵活地实现标准IO重定向,任何可以作为标准IO的设备,如USB键盘,LCD屏,串口等都可以对应一个device_t的结构体变量,只需要实现getc和putc等函数,就能加入到devlist列表中去,也就可以被assign为标准IO设备std_device中去。如函数
int console_assign (int file,char *devname); /* Assign the console 重定向标准输入输出*/
这个函数功能就是把名为devname的设备重定向为标准IO文件file(stdin,stderr)。其执行过程是在devlist中查找devname的设备,返回这个设备的device_t指针,并把指针值赋给std_device[file]。
18)enable_interrupts(),使能中断。由于CONFIG_USE_IRQ没有定义,空实现。
  ? #ifdef CONFIG_USE_IRQ
??? /* enable IRQ interrupts */
??? void enable_interrupts (void)
??? {
???? unsigned long temp;
???? __asm__ __volatile__("mrs %0,cpsrn"
??????????? "bic %0,%0,#0x80n"
??????????? "msr cpsr_c,%0"
??????????? : "=r" (temp)
??????????? :
??????????? : "memory");
??? }
    #else
??????? void enable_interrupts (void)
??? {?
??? }?
? 19)设置CS8900的MAC地址。
? cs8900_get_enetaddr (gd->bd->bi_enetaddr);?
? 20)初始化以太网。
? eth_initialize(gd->bd);//bd中已经IP,MAC已经初始化
? 21)main_loop ();定义于common/main.c
? 至此所有初始化工作已经完毕。main_loop在标准转入设备中接受命令行,然后分析,查找,执行。
关于U-boot中命令相关的编程:
1、命令相关的函数和定义
? @main_loop:这个函数里有太多编译选项,对于smdk2410,去掉所有选项后等效下面的程序
? void main_loop()
?? {
??? static char lastcommand[CFG_CBSIZE] = { 0,};
??? int len;
??? int rc = 1;
??? int flag;
???? char *s;
??? int bootdelay;
??? s = getenv ("bootdelay");?? //自动启动内核等待延时
??? bootdelay = s ? (int)simple_strtol(s,10) : CONFIG_BOOTDELAY;
??
??? debug ("### main_loop entered: bootdelay=%dnn",bootdelay);
??? s = getenv ("bootcmd");? //取得环境中设置的启动命令行
??? debug ("### main_loop: bootcmd="%s"n",s ? s : "");
??
??? if (bootdelay >= 0 && s && !abortboot (bootdelay))
??? {
???? run_command (s,0);//执行启动命令行,smdk2410.h中没有定义CONFIG_BOOTCOMMAND,所以没有命令执行。
??? }
???
??? for (;;) {
??? len = readline(CFG_PROMPT);//读取键入的命令行到console_buffer
??
???? flag = 0; /* assume no special flags for now */
???? if (len > 0)
????? strcpy (lastcommand,console_buffer);//拷贝命令行到lastcommand.
???? else if (len == 0)
????? flag |= CMD_FLAG_REPEAT;
????? if (len == -1)
????? puts ("n");
???? else
????? rc = run_command (lastcommand,flag); //执行这个命令行。
??
???? if (rc flash_id = FLASH_MAN_SST;
???? else
????? {
?????? panic("NOT expected FLASH FOUND!n");return 0;
????? }
???? value=READ_ADDR1;?? //read device ID
??
???? if(value==(CFG_FLASH_WORD_SIZE)SST_ID_xF1601)
????? {
?????? info->flash_id += FLASH_SST1601;
???????? info->sector_count = 32;?? //32 block
???????? info->size = 0x00200000; // 2M=32*64K
????? }
???? else
????? {
?????? panic("NOT expected FLASH FOUND!n");return 0;?
????? }
?????
???? //建立sector起始地址表。
??? if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST )
??? {
????? for (i = 0; i sector_count; i++)
???? info->start = CFG_FLASH_BASE + (i * 0x00010000);
??? }
???
???? //设置sector保护信息,对于SST生产的FLASH,全部设为0。
??? for (i = 0; i sector_count; i++)
??? {
???? if((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST)
?????? info->protect = 0;
??? }
???
???? //结束读ID状态:
??? *((CFG_FLASH_WORD_SIZE *)&info->start[0])= (CFG_FLASH_WORD_SIZE)0x00F0;
???
??? //设置保护,将u-boot镜像和环境参数所在的block的proctect标志置1
??? flash_protect (FLAG_PROTECT_SET,
??????????? CFG_FLASH_BASE,
??????????? CFG_FLASH_BASE + monitor_flash_len - 1,
??????????? &flash_info[0]);
??
??? flash_protect (FLAG_PROTECT_SET,
??????????? CFG_ENV_ADDR,
??????????? CFG_ENV_ADDR + CFG_ENV_SIZE - 1,&flash_info[0]);
??? return info->size;
?? }
   
//flash_erase实现
??  这里给出修改的部分,s_first,s_last是要擦除的block的起始和终止block号.对于protect[]置位的block不进行擦除。
擦除一个block命令时序按照上面图示的Block-Erase进行。
for (sect = s_first; sectprotect[sect] == 0)
??? { /* not protected */
??????? addr = (CFG_FLASH_WORD_SIZE *)(info->start[sect]);
??????? if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST)
???????? {
??????? MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00AA;
??????? MEM_FLASH_ADDR2 = (CFG_FLASH_WORD_SIZE)0x0055;
??????? MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x0080;
??????? MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00AA;
??????? MEM_FLASH_ADDR2 = (CFG_FLASH_WORD_SIZE)0x0055;
??????? addr[0] = (CFG_FLASH_WORD_SIZE)0x0050;? /* block erase */
??????? for (i=0; istart[l_sect]);//查询DQ7是否为1,DQ7=1表明擦除完毕
? while ((addr[0] & (CFG_FLASH_WORD_SIZE)0x0080) != (CFG_FLASH_WORD_SIZE)0x0080) {
?? if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) {
??? printf ("Timeoutn");
??? return 1;
? }
? ................
?
//write_word操作,这个函数由write_buff一调用,完成写入一个word的操作,其操作命令序列由上图中Word-Program指定。
? static int write_word (flash_info_t *info,ulong dest,ulong data)
? {
?? volatile CFG_FLASH_WORD_SIZE *dest2 = (CFG_FLASH_WORD_SIZE *)dest;
?? volatile CFG_FLASH_WORD_SIZE *data2 = (CFG_FLASH_WORD_SIZE *)&data;
?? ulong start;
?? int flag;
?? int i;
?
?? /* Check if Flash is (sufficiently) erased */
?? if ((*((volatile ulong *)dest) & data) != data) {
??? return (2);
?? }
?? /* Disable interrupts which might cause a timeout here */
?? flag = disable_interrupts();
?
?? for (i=0; i CFG_FLASH_WRITE_TOUT) {
??? return (1);
???????? }
?????? }
???? }
?? return (0);
? }
?
? 这些代码在与nor flash相关的命令中都会间接被调用。所以u-boot可移植性的另一个方面就是规定一些函数调用接口和全局变量,这些函数的实现是硬件相关的,移植时只需要实现这些函数。
? 而全局变量是具体硬件无关的。u-boot在通用目录中实现其余与硬件无关的函数,这些函数就只与全局变量和函数接口打交道了。 通过编译选项设置来灵活控制是否需要编译通用部分。
?
?
6、增加从Nand 启动的代码:
FS2410板有跳线,跳线短路时从NAND启动,否则从NOR启动。根据FS2410 BIOS源码,我修改了start.s加入了可以从两种FLASH中启动u-boot的
代码。原理在于:在重定位之前先读BWSCON寄存器,判断OM0位是0(有跳线,NAND启动)还是1(无跳线,NOR启动),采取不同的重定位代码
分别从nand或nor中拷贝u-boot镜像到RAM中。这里面也有问题,比如从Nand启动后,nor flash的初始化代码和与它相关的命令都是不能使用的。
这里我采用比较简单的方法,定义一个全局变量标志_boot_flash保存当前启动FLASH标志,_boot_flash=0则表明是NOR启动,否则是从NAND。
在每个与nor flash 相关的命令执行函数一开始就判断这个变量,如果为1立即返回。flash_init()也必须放在这个if(!_boot_flash)条件中。
这里方法比较笨,主要是为了能在跳线处于任意状态时都能启动u-boot。
修改后的start.s如下。
.......
? //修改1
? .globl _boot_flash
? _boot_flash:?? //定义全局标志变量,0:NOR FLASH启动,1:NAND FLASH启动。
? .word 0x00000000??
.........
? ///修改2:
? ldr r0,=BWSCON
? ldr r0,[r0]
? ands r0,#6
? beq nand_boot?? //OM0=0,有跳线,从Nand启动。nand_boot在后面定义。
? ............
?
? //修改4,这里在全局变量_boot_flash中设置当前启动flash设备是NOR还是NAND
? //这里已经完成搬运到RAM的工作,即将跳转到RAM中_start_armboot函数中执行。
? adr r1,_boot_flash //取_boot_flash的当前地址,这时还在NOR FLASH或者NAND 4KB缓冲中。
? ldr r2,_TEXT_BASE
? add r1,r1,r2?? //得到_boot_flash重定位后的地址,这个地址在RAM中。
? ldr r0,#6?? //
? mov r2,#0x00000001
? streq r2,[r1]?? //如果当前是从NAND启动,置_boot_flash为1
?
? ldr pc,_start_armboot

_start_armboot: .word start_armboot

........

//////// 修改4,从NAND拷贝U-boot镜像(最大128KB),这段代码由fs2410 BIOS修改得来。
nand_boot:
?? mov r5,#NFCONF
?? ldr r0,=(1>8)
?? strb???? r1,[r5,#8]
?? cmp????? r6,#0???? //if(NandAddr)?
?? movne??? r0,lsr #16 //WrNFAddr(addr>>16)
?? strneb?? r0,#8]
??
?? bl? WaitNandBusy //WaitNFBusy()
?
?? ldrb r0,#0xc] //RdNFDat()
?? sub? r0,#0xff
??
?? mov????? r1,#0?? //WrNFCmd(READCMD0)
?? strb???? r1,#4]
??
?? ldr????? r1,#0]? //NFChipDs()
?? orr????? r1,#0x800
?? str????? r1,#0]
??
?? mov? pc,r7
?
? ReadNandPage:
?? mov???? r7,lr
?? mov????? r4,r1
?? mov????? r5,#NFCONF
?
?? ldr????? r1,#0]? //NFChipEn()
?? bic????? r1,#0]
?
?? mov????? r1,#4]
?? strb???? r1,#8]? //WrNFAddr(0)
?? strb???? r0,#8]? //WrNFAddr(addr)
?? mov????? r1,lsr #8 //WrNFAddr(addr>>8)
?? strb???? r1,#0?? //if(NandAddr)?
?? movne??? r0,#8]
??
?? ldr????? r0,#0]? //InitEcc()
?? orr????? r0,#0x1000
?? str????? r0,#0]
??
?? bl?????? WaitNandBusy //WaitNFBusy()
??
?? mov????? r0,#0?? //for(i=0; i<512; i++)
? r1:
?? ldrb???? r1,#0xc] //buf[i] = RdNFDat()
?? strb???? r1,[r4,r0]
?? add????? r0,#1
?? bic????? r0,#0x10000
?? cmp????? r0,#0x200
?? bcc????? r1
??
?? ldr????? r0,#0]? //NFChipDs()
?? orr????? r0,#0x800
?? str????? r0,#0]
???
?? mov?? pc,r7
?
关于nand命令,我尝试打开CFG_CMD_NAND选项,并定义
??? #define CFG_MAX_NAND_DEVICE 1
?? #define MAX_NAND_CHIPS 1
?? #define CFG_NAND_BASE 0x4e000000
?? 添加boar_nand_init()定义(空实现)。但是连接时出现问题,原因是u-boot使用的是软浮点,而我的交叉编译arm-linux-gcc是硬件浮点。
?? 看过一些解决方法,比较麻烦,还没有解决这个问题,希望好心的高手指点。不过我比较纳闷,u-boot在nand部分哪里会用到浮点运算呢?
??
? 7、添加网络命令。
? 我尝试使用ping命令,其余的命令暂时不考虑。
? 在common/cmd_net中,首先有条件编译 #if (CONFIG_COMMANDS & CFG_CMD_NET),然后在命令函数do_ping(...)定义之前有条件编译判断
? #if (CONFIG_COMMANDS & CFG_CMD_PING) 。所以在include/cofig/fs2410.h中必须打开这两个命令选项。
?? #define CONFIG_COMMANDS
??? (CONFIG_CMD_DFL? |
??? CFG_CMD_CACHE? |
??? CFG_CMD_REGINFO? |
??? CFG_CMD_DATE? |
??? CFG_CMD_NET | ? //
??? CFG_CMD_PING | //
??? CFG_CMD_ELF)
? 并且设定IP:192.168.0.12。
?
?? 至此,整个移植过程已经完成。编译连接生成u-boot.bin,烧到nand 和nor上都能顺利启动u-boot,使用ping命令时出现问题,
?? 发现ping自己的主机竟然超时,还以为是程序出了问题,后来才发现是windows防火墙的问题。关闭防火墙就能PING通了。
??
?? 总体来说,u-boot是一个很特殊的程序,代码庞大,功能强大,自成体系。为了在不同的CPU,ARCH,BOARD上移植进行了很多灵活的设计。
在u-boot的移植过程中学到很多东西,尤其是程序设计方法方面真的是大开了眼界。u-boot在代码级可移植性和底层程序开发技术上给人很好的启发。
很多东西没有搞明白,尤其是u-boot最重要的功能--引导OS这部分还没有涉及。linux内核还没入门呢,路漫漫其修远兮,吾将上下而求索。
?没有IDE环境看u-boot这种makefile工程很费劲,我用UltraEdit干了这件事,后来才发现可以使用source insight 这个软件。。。。。。。。这些工作都是自己学习过程的总结,谬误之处在所难免,请高手不吝指正。。

?


下面通过一个简单的例子来说明怎么使用do_run()函数,yaffs2ram的作用是实现从yaffs文件系统中拷贝logo.bmp到指定的RAM地址上:
?


int yaffs2ram(void)

?


{

?


? char *argv[5];?? //定义5个指针数组,用来存放各个字符命令的指针

?


? char buf[100];

?


? char *name_buf =

"/logo/logo.bmp";

?


? int ret;
?


?? if(!yaffs_file_exist(name_buf))//检查文件系统中是否存在logo.bmp
??? {
??????? dprintf_line("%s NOT Exist!",name_buf);
??????? return -1;
??? }
?

?

?

?

?

//把

?

?

yrdm? /logo/logo.bmp? 0x8000_0000命令


传给buf(loadaddr为环境变量)


?


??? sprintf(buf,"yrdm %s ${loadaddr}",name_buf);

??? setenv("upgradetemp",buf);//设置临时环境变量upgradetemp的值
??? argv[0] = "run";
??? argv[1] = "upgradetemp";
??? argv[2] = NULL;
??? ret = do_run(NULL,2,argv);

?

?? //运行yrdm? /logo/logo.bmp? 0x8000_0000命令,把logo.bmp拷贝到0x8000_000地址上


?


??? if ( ret == 1)
?


?????? {
?


???????? printf("do_run error!")
?


??????? return -1;
?


???????? }

?


??? setenv("upgradetemp",NULL); //清空临时环境变量的值

?


return 0;

?

?

?

}

?

?

?

注:由于do_

yrdm

函数规定输入命令的个数必须为3(如下所示),所以我们加了一个argv[2] = NULL命令,该命令什么都不做。

?


U_BOOT_CMD(
??? yrdm,?? 3,? 0,? do_yrdm,
??? "read file to memory from yaffs",
??? "filename offset"
);

本篇文章来源于 Linux公社网站(www.linuxidc.com)? 原文链接:http://www.linuxidc.com/Linux/2012-02/55138p2.htm

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读