您尚未登录。

楼主 #1 2018-06-08 16:52:29

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

u-boot 源码分析 第二阶段源码分析

《单片机小白转嵌入式Linux学习记录,基于S3C2440----目录》

牢记 u-boot 的本质作用

  1. 将内核从Flash读出内核到SDRAM

  2. 启动内核
    其它都是附加功能,分析第二阶段代码之前我们要明确目标,由于代码量比较庞大,不要花费很多精力去分析不是太重要的代码。

第二阶段代码从 start_armboot 开始

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); // gd 是一个结构体指针,指向内存 CFG_GBL_DATA_SIZE 128 字节(韦老师书本 P260) 全局变量,保存各种参数,比如 内存大小、内存起始地址

flash_init (); // Flash 初始化
nand_init(); / go init the NAND /

....... / 其它一系列的初始化 /

for (;;) {//进入死循环
main_loop ();
}

分析 main_loop()

  1. s = getenv ("bootdelay"); // 读取环境变量,bootdelay u-boot 启动延时时间

  2. 倒计时 倒计时没有按下空格
    a. s = getenv ("bootcmd"); 读出环境变量 bootcmd = nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0
    b. run_command (s, 0);//启动内核s = getenv ("bootcmd")

  3. 如果倒计时按下空格
    a. len = readline (CFG_PROMPT);//读取串口的命令
    b. run_command (lastcommand, flag);

所以u-boot 的核心是 run_command() 各种命令的集合
关于怎么自定义命令,请看我的另一个帖子,这里不重要,可跳过。
------------------------------------------------------------------
自定义u-boot命令 及u-boot 链接脚本 .u_boot_cmd 段的理解
https://whycan.cn/t_1295.html
------------------------------------------------------------------

我们继续分析代码
怎么将内核拷贝到SDRAM
s = getenv ("bootcmd")//获取环境变量 bootcmd
run_command (s, 0) //执行环境变量对应的命令 nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0

相当于执行了nand read.jffs2 0x30007FC0 kernel 和 bootm 0x30007FC0 两条命令

分析第一条命令前先分析一下什么是环境变量?

在u-boot 命令行下 输入 print 打印出来的就是一些常用的环境变量

pc 机每个硬盘上都有一个分区表 C 盘 D 盘 ...
嵌入式Linux 没有分区表,需要我们自己约定每个分区,一般NAND Flash 上的划分为:

bootloader||环境变量(参数)||kernel||文件系统(root)

由于没有分区表,所以这些分区在源码里写死的。在配置文件 100ask24x0.h 中

#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \ // 从0开始的256K是bootloader
"128k(params)," \ // 128K 是 params
"2m(kernel)," \ // 2M 是内核
"-(root)" // 剩下的实际 root 文件系统
可以输入 u-boot 命令 mtd 查看
#: name size offset mask_flags
0: boot_loader 0x00040000 0x00000000 0
1: params 0x00020000 0x00040000 0
2: kernel 0x00200000 0x00060000 0
3: root 0x0fda0000 0x00260000 0
active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000

defaults:
mtdids : nand0=nandflash0
mtdparts: mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root

在命令 nand read.jffs2 0x30007FC0 kernel 中 kernel也是一个环境变量 0x00200000 0x00060000
所以
nand read.jffs2 0x30007FC0 kernel
等价于
nand read.jffs2 0x30007FC0 0x00200000 0x00060000
意思是:从NAND Flash 的 0x00060000 位置读取 0x00200000(2M) 的数据到 SDRAM 的 0x30007FC0

名字kernel 不重要 重要的是他对应的地址
nand read.jffs2 0x30007FC0 kernel
nand read.jffs2 0x30007FC0 0x00200000 0x00060000
执行,是一样的效果。

由命令 nand read.jffs2 0x30007FC0 kernel 可以知道 是 nand 命令实现的烧写,所以我们分析 nand 命令对应的函数 do_nand

do_nand 函数里面的 !strcmp(s, ".jffs2") || !strcmp(s, ".e") 进而调用 ret = nand_read_opts(nand, &opts); 拷贝 内核到 SDRAM

jffs2 是一种文件格式,这里跟文件格式没有关系,只是用这个命令,对页对齐 块对齐没有限制

怎么调用内核?
bootm 0x30007FC0
分析 do_bootm

先来说明一下内核的格式

uimage: 头 + 真正的内核

typedef struct image_header {
uint32_t ih_magic; / Image Header Magic Number /
uint32_t ih_hcrc; / Image Header CRC Checksum /
uint32_t ih_time; / Image Creation Timestamp /
uint32_t ih_size; / Image Data Size /
uint32_t ih_load; / Data Load Address 数据加载地址,如果当前地址不等于加载地址,则把内核搬运到加载地址 /
uint32_t ih_ep; / Entry Point Address 入口地址,搬运完毕内核后,跳到入口地址启动内核 /
uint32_t ih_dcrc; / Image Data CRC Checksum /
uint8_t ih_os; / Operating System /
uint8_t ih_arch; / CPU architecture /
uint8_t ih_type; / Image Type /
uint8_t ih_comp; / Compression Type /
uint8_t ih_name[IH_NMLEN]; / Image Name /
} image_header_t;

bootm 启动内核分析内核当前地址是否等于加载地址,如果不等于加载地址,则把内核移动到加载地址,然后跳到内核入口去启动内核。
我们所用内核的加载地址是0x30008000
0x30008000 - 0x30007FC0 = 64 字节
64 字节刚好等于 内核头接结构 image_header_t 的长度,所以内核不用从新搬运,从而提高启动速度。

if(ntohl(hdr->ih_load) == data) ,位置相同,不搬运

继续讲怎么启动内核

  1. uboot 通过特定数据格式(tag)告诉内核一些参数
    setup_serial_tag (&params);//起始标签
    setup_memory_tags (bd);//内存标签 告诉内核可用内存个数和大小
    setup_commandline_tag (bd, commandline);//命令标签
    setup_end_tag (bd);//结束标签

  2. 跳转到内核入口地址
    void (*theKernel)(int zero, int arch, uint params);//定义函数指针
    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//获取内核入口
    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//传递参数 启动内核

一些分析过程和对应的文件
----------------------------------------------------------
lib_arm.c
调用 do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify);

查看 Armlinux.c 下的 do_bootm_linux 函数
设置启动参数:
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
#endif

启动内核

void (*theKernel)(int zero, int arch, uint params);//定义函数指针

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//获取内核入口

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//传递参数 启动内核

uboot 启动完内核就退出了,怎么传递参数呢?

  1. 在某个地址(开发版:0x30000100)按照双方约定的某种格式tag保存数据。

setup_start_tag (bd); //起始标签
setup_memory_tags (bd); // 内存的块数起始地址和大小
setup_commandline_tag (bd, commandline); //传递命令行
setup_end_tag (bd); //结束标签

setup_start_tag lib_arm.c
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; // 100ask24x0.c (board\100ask24x0): gd->bd->bi_boot_params = 0x30000100;

params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);

params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;

params = tag_next (params); // #define tag_next(t) ((struct tag )((u32 )(t) + (t)->hdr.size))
}
tag结构
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;

/*

  • Acorn specific
    */
    struct tag_acorn acorn;

/*

  • DC21285 specific
    */
    struct tag_memclk memclk;
    } u;
    };

struct tag_core {
u32 flags; / bit 0 = read-only /
u32 pagesize;
u32 rootdev;
};

struct tag_header {
u32 size;
u32 tag;
};

联合体与结构体的区别:
联合体内的成员共用同一个内存,占用内存空间为最大成员所占用的空间。同一时间联合体内部只能存放其中一个成员。
结构体每个成员都占用一定的内存空间。同一时间结构体内部可以存放多个成员。

typedef struct bd_info {
int bi_baudrate; / serial console baudrate /
unsigned long bi_ip_addr; / IP Address /
unsigned char bi_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];
#ifdef CONFIG_HAS_ETH1
/ second onboard ethernet port /
unsigned char bi_enet1addr[6];
#endif
} bd_t;

关于好多地方用到的gd 全局变量 搜索 gd_t *gd
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")

typedef struct global_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;

setup_start_tag 在内存中的排列

U32 u.core.rootdev = 0 0x30000110
U32 u.core.pagesize = 0 0x3000010C
U32 u.core.flags=0 0x30000108
U32 hdr.size = tag_size (tag_core) = 5 0x30000104 #define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2) // 右移2 等于 /4
U32 hdr.tag = ATAG_CORE=(0x54410001) 0x30000100

setup_memory_tags (bd);
获取内存块数、起始地址、大小

setup_commandline_tag (bd, commandline)
char *commandline = getenv ("bootargs");// 来自于环境变量 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

----------------------------------------------------------

最近编辑记录 xinxiaoci (2018-06-12 19:34:43)

离线

#2 2020-05-21 16:15:12

tianyun923
会员
注册时间: 2020-05-21
已发帖子: 7
积分: 7

Re: u-boot 源码分析 第二阶段源码分析

uboot,源码我开了好久都卡不懂了,看韦东山了直接把我看懵逼了,

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn


东莞哇酷科技有限公司开发