College of Computer Science & Technology 第五章 BootLoader开发 College of Computer Science & Technology
第五章 BootLoader开发 BootLoader的作用 BootLoader的启动流程 BootLoader之vivi 鲁东大学 LUDONG UNIVERSITY BootLoader的作用 BootLoader的启动流程 BootLoader之vivi BootLoader之Uboot (附)
包含依赖于CPU体系结构的硬件初始化代码 汇编语言编写 第二阶段 初始化操作系统内核运行环境 C语言完成 BootLoader结构 鲁东大学 LUDONG UNIVERSITY BootLoader一般分为两个阶段 第一阶段 包含依赖于CPU体系结构的硬件初始化代码 汇编语言编写 第二阶段 初始化操作系统内核运行环境 C语言完成 分段:提高BootLoader的可移植性和可读性
第一阶段 基本硬件设备初始化 BootLoader启动流程 为第二阶段准备RAM空间 复制BootLoader第二阶段代码到RAM 堆栈设置 鲁东大学 LUDONG UNIVERSITY 第一阶段 基本硬件设备初始化 为第二阶段准备RAM空间 复制BootLoader第二阶段代码到RAM 堆栈设置 跳转至第二阶段的C程序入口
BootLoader vivi第二阶段 vivi第二阶段 vivi第一阶段 head.s —C语言编写的一个main()函数 共包括8个步骤 鲁东大学 LUDONG UNIVERSITY vivi第二阶段 —C语言编写的一个main()函数 vivi第一阶段 head.s … bl main 共包括8个步骤 vivi第二阶段 main.c 完成后,将根据输入,进入操作系统或者vivi-shell step1 … step8 boot_or_vivi boot embeddOS vivi-shell
vivi第二阶段代码(1) Next int main(int argc, char *argv[]) { int ret; 鲁东大学 LUDONG UNIVERSITY int main(int argc, char *argv[]) { int ret; /*Step 1*/ putstr("\r\n"); putstr(vivi_banner); reset_handler(); /*Step 2*/ ret = board_init(); if (ret) { putstr("Failed a board_init() procedure\r\n"); error(); } /*Step 3*/ mem_map_init(); mmu_init(); putstr("Succeed memory mapping.\r\n"); Step1 打印vivi版本 判断是否为硬件复位 Step2 开发板初始化 Reset_handler()是无效代码。 Step3 内存映射初始化 mmu初始化 Next
vivi第二阶段代码(2) /*Step 4*/ ret = heap_init(); Step4 堆初始化 if (ret) { 鲁东大学 LUDONG UNIVERSITY /*Step 4*/ ret = heap_init(); if (ret) { putstr("Failed initailizing heap region\r\n"); error(); } /*Step 5*/ ret = mtd_dev_init(); /*Step 6*/ init_priv_data(); /*Step 7*/ misc(); init_builtin_vivi_cmds(); / /*Step 8*/ boot_or_vivi(); return 0; Step4 堆初始化 Step5 mtd设备初始化 Step6 私有数据初始化 Step7 vivi命令初始化 Step8 启动os 或者 vivi-shell Back
vivi第二阶段分析 步骤1 Step1 ①putstr(vivi_banner) 由串口打印一个字符串vivi_banner 鲁东大学 LUDONG UNIVERSITY Step1 ①putstr(vivi_banner) 由串口打印一个字符串vivi_banner 该字符串为vivi版本信息 定义位置-version.c Vivi_banner 可以修改
hard_reset_handler() soft_reset_handler() clear_mem(base,length) vivi第二阶段 步骤1 鲁东大学 LUDONG UNIVERSITY ②reset_handler() main() reset_handler() 判断如果是硬件复位,则将用户使用的DRAM(内存)清零。 判断复位原因 clear_mem参数 base-user_ram_base length-user_ram_size hard_reset_handler() 条件编译 CONFIG_RESET_HANDLING 是无效的,所以实际上使用的reset_handler()是个空函数。 soft_reset_handler() clear_mem(base,length) Back
vivi第二阶段 步骤2 step2 board_init() main() board_init() 调用init_time() 鲁东大学 LUDONG UNIVERSITY step2 board_init() main() board_init() 调用init_time() 初始化定时器 vivi中未用 init_time() 设置IO作用 设置口上拉 设置EXINT 调用set_gpios() 初始化通用IO口 set_gpios() 这一部分与开发板电路有关,移植过程中需要根据实际电路进行修改 return 0 Back
mem_mapping_linear() cache_clean_invalidate vivi第二阶段 步骤3 鲁东大学 LUDONG UNIVERSITY ①mem_map_init();内存映射初始化-建立页表 ②mmu_init();存储器管理单元MMU初始化 main() mem_map_init() mem_map_nand_boot() mem_map_nor() mem_mapping_linear() 源码 mmu.c cache_clean_invalidate tlb_invalidate
mem_mapping_linear()-初始化MMU主页表 vivi第二阶段 步骤3 鲁东大学 LUDONG UNIVERSITY mem_mapping_linear()-初始化MMU主页表 基址12bit Description 20bit 4095 0<<20 1<<20 1 4095<<20 … 1MB/页 4GB 4096页 for (sectionNumber = 0; sectionNumber < 4096; sectionNumber++) { pageoffset = (sectionNumber << 20); *(mmu_tlb_base + (pageoffset >> 20)) = pageoffset | MMU_SECDESC; }
MMU只使用一级页表地址转换 虚地址 访问页表 基址 1 [1:0] 00-error 01-二级粗表 (4,64K) 10-一级页表 鲁东大学 LUDONG UNIVERSITY 虚地址 12bit 20bit偏移量 访问页表 只使用一级页表 4095 基址 1 … [1:0] 00-error 01-二级粗表 (4,64K) 10-一级页表 (1024K) 11-二级细表 (1,4,64K) 物理地址 基址 20bit偏移量 ARM920T内集成MMU支持2级页表,根据页表项的最低两位进行虚地址-物理地址的转换
mmu_init();存储器管理单元MMU初始化-MMU配置 vivi第二阶段 步骤3 鲁东大学 LUDONG UNIVERSITY mmu_init();存储器管理单元MMU初始化-MMU配置 arm920_setup()采用内嵌汇编__asm 操作CP15相关寄存器对MMU进行控制 main() mmu_init() arm920_setup() 禁用I_cache和 D_cache "mcr p15, 0, r0, c7, c7, 0" 耗写并禁用WB "mcr p15, 0, r0, c7, c10, 4 " r0=0 禁用数据/指令TLB "mcr p15, 0, r0, c8, c7, 0 " r4=0x33dfc000 将页表地址写入CP15:C2 "mcr p15, 0, r4, c2, c0, 0" …
vivi第二阶段 步骤3 mcr p15, 0, r0, c3, c0, 0 位 符号 使能功能 1 2 3 7 8 9 12 13 M A 鲁东大学 LUDONG UNIVERSITY mcr p15, 0, r0, c3, c0, 0 位 符号 使能功能 1 2 3 7 8 9 12 13 M A C W B S R I V MMU 对齐检测 数据Cache 写缓冲器 大端模式 系统保护位 ROM保护位 指令Cache 高地址向量表 mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x3000 bic r0, r0, #0x0300 bic r0, r0, #0x0087 orr r0, r0, #0x0002 orr r0, r0, #0x0004 orr r0, r0, #0x1000 orr r0, r0, #0x0001 mcr p15, 0, r0, c1, c0, 0
MMU页表项说明 主页表项 基地址 SBZ AP 域 1 C B 共16种域 页表项权限控制 每个域控制位权限控制位2位,共32位 鲁东大学 LUDONG UNIVERSITY 主页表项 基地址 SBZ AP 域 1 C B 31 20 19 12 11-10 9 8-5 4 3 2 共16种域 页表项权限控制 每个域控制位权限控制位2位,共32位 CP15的C3寄存器 域-主控权限 页表项权限控制-次控制 域值 访问 说明 11 10 01 00 管理者 保留 客户 不可访问 访问不受控制 不可预料 访问受页表项权限控制 产生域错误 Back
vivi第二阶段 步骤4 step4 heap_init() heap—堆,用于动态分配的内存空间 链表形式组织的内存空间 main() 鲁东大学 LUDONG UNIVERSITY step4 heap_init() heap—堆,用于动态分配的内存空间 链表形式组织的内存空间 函数mmalloc从堆中分配空间 相应地,mfree释放空间到堆中 main() heap_init() mmalloc_init() heap_init()调用函数mmalloc_init()对堆内存块链表头指针进行初始化。 根据返回值判断初始化是否成功。
vivi堆结构 堆数据结构blockhead 内存块 标识该块是否已分配,0-No,1-Yes 鲁东大学 LUDONG UNIVERSITY 堆数据结构blockhead 标识该块是否已分配,0-No,1-Yes typedef struct blockhead_t { Int32 signature; Bool allocated; unsigned long size; struct blockhead_t *next; struct blockhead_t *prev; } blockhead; 标识该内存块大小 内存块 signature allocated 指向下一块内存块 size of(Blockhead) blockhead size *next 指向上一块内存块 *prev block size
vivi堆组织 … static blockhead *gHeapBase ;全局变量,heap链表头指针 鲁东大学 LUDONG UNIVERSITY static blockhead *gHeapBase ;全局变量,heap链表头指针 gHeapBase NULL NULL … mmalloc_init():初始化gHeapBase指向的链表第一项 mmalloc_init((unsign char*)(HEAP_BASE),HEAP_SIZE) 调用后,整个堆初始化为一个大小为HEAP_SIZE的数据块 源码 lib/heap.c
vivi内存动态分配 I. mmalloc()-void *mmalloc(unsign long size) 1 N 鲁东大学 LUDONG UNIVERSITY 1 I. mmalloc()-void *mmalloc(unsign long size) N block->size>size? blockptr=gHeapbase Y newblock=blockptr+sizeof(blockhead)+size blockptr==null? Y N newblock->size=blockptr->size -sizeof(blockhead)-size Y blockptr->allocated==yes? newblock->allocated=false newblock->next=blockptr->next newblock->prev=blockptr N return null Y block->size>=size? N blockptr->next=nextblock block->size=size 1 blockptr=block->next return (blockptr+sizeof(blockhead))
vivi内存动态释放 vivi内存动态释放 鲁东大学 LUDONG UNIVERSITY II.mfree()-void free(void *block) Y block==null? 回朔,加上头结构 N blckptr=block-sizeof(blockhead) 后一个块是空块? 放回堆中 Y blockptr->allocated=false 与后一块合并,修改size,prev和next 前一个块是空块? Return Y 与前一块合并,修改size,prev和next Back
vivi第二阶段 步骤5 step5 mtd_dev_init() mtd-memory technology device 存储技术设备 鲁东大学 LUDONG UNIVERSITY step5 mtd_dev_init() mtd-memory technology device 存储技术设备 Linux操作系统用于访问存储设备的系统,主要是为了更加简单的支持Flash这类存储设备。 Flash硬件驱动 MTD原始设备 MTD块设备 MTD字符设备 块设备节点 字符设备节点 文件系统 根文件系统 封装mtd为块设备 封装mtd为字符设备 大量Flash数据/操作函数,匹配ID 通用时序,读取芯片ID信息
mtd_dev_init() vivi中调用mtd_dev_init()函数初始化Flash为MTD设备。 函数调用结构: main() 鲁东大学 LUDONG UNIVERSITY vivi中调用mtd_dev_init()函数初始化Flash为MTD设备。 函数调用结构: main() Step5 mtd_dev_init() mtd_dev_init()函数调用mtd_init() mtd_init()根据autoconfig.h的定义,调用smc_init()函数,初始化sumsungNANDFLASH存储器为MTD设备,构造nand_chip数据结构 mtd_init() smc_init() smc_init()函数构造mfd_info数据结构的过程中,调用smc_scan扫描已知NANDFLASH型号 smc_insert() smc_scan()
通过mtd->priv=nand_chip smc_init() 鲁东大学 LUDONG UNIVERSITY smc_init()初始化了两个数据结构 I.nand_chip II.mtd_info 源文件 s3c2410_flash.c Nand.h MTD.h 将nand_chip结构的各函数 (read,write)指针与smc 定义函数关联 内存开辟空间结构化为 nand_chip和mtd_info 通过函数控制NANDFLASH 控制器进行芯片复位 初始化结构体为全零 通过mtd->priv=nand_chip 进行关联 调用smc_insert()进行 MTD设备初始化 初始化NANDFLASH控制器 (设值NFCONF)
smc_scan() smc_insert()为构造mtd设备数据结构mtd_info 主要操作是调用了smc_scan()查找芯片信息 鲁东大学 LUDONG UNIVERSITY smc_insert()为构造mtd设备数据结构mtd_info 主要操作是调用了smc_scan()查找芯片信息 源码 smc_core.c 读取nandflash ID信息 根据ID匹配数组项 进行MTD参数设置 根据ID查找数组 nand_dev_ids[] 完善nand_chip数据项 nand_smc_info[] 根据ID匹配数组项 进行MTD参数设置 关联mtd_info的各函数 到NAND操作函数 Back
smc_scan()中用到的两个数组 I.nand_dev_ids[ ] 鲁东大学 LUDONG UNIVERSITY I.nand_dev_ids[ ] 该数组中定义了常用的NANDFLASH芯片相关信息,为结构体nand_flash_dev数组 Nand.h Nand_ids.h EX:nand_dev_ids[ ]={ {"Samsung K9D1G08V0M", NAND_MFR_SAMSUNG, 0x79, 27, 0, 3, 0x4000}, // 128Mb {NULL,}} II.nand_smc_info[ ] 该数组为结构体nand_smc_dev数组,共有8项,分别定义了不同容量,从1M~128M的NAND Flash芯片的操作参数 Nand_ids.h
vivi第二阶段 步骤6 step6 init_priv_data() 拷贝参数到内存(SDRAM)定义位置 鲁东大学 LUDONG UNIVERSITY step6 init_priv_data() 拷贝参数到内存(SDRAM)定义位置 init_priv_data() 分别调用两个函数拷贝: I.vivi内置缺省参数 II.用户自定义参数 get_default_priv_data() load_saved_priv_data() get_default_param_tlb() 拷贝的参数分为三类:a.vivi使用参数 b.linux启动命令 c.mtd分区信息 get_default_linux_cmd() get_default_mtd_partition()
vivi参数拷贝-默认 get_default_param_tlb() VIVI 1MB get_default_linux_cmd() 鲁东大学 LUDONG UNIVERSITY get_default_param_tlb() 源码 rw.c VIVI 1MB get_default_linux_cmd() get_default_mtd_partition() HEAP 1MB MMU_TABLE 16KB 三个拷贝函数相似,由VIVI内定义数组(默认参数)拷贝到SDRAM固定位置 16KB Linux_CMD VIVI_PARAM MTD_PART VIVI_PRIV_RAM_BASE 16KB STACK 32KB 参数个数 DRAM BASE 8字符开头
vivi参数拷贝-用户定义 buffer SDRAM 0x30000000 SDRAM FLASH 0x1FC0000 鲁东大学 LUDONG UNIVERSITY VIVI_PRIV_RAM VIVI_PRIV_ROM buffer SDRAM 0x30000000 SDRAM FLASH 0x1FC0000 测试每类头8个字符是否正确 I.首先调用get_default_priv_data 都拷贝到VIVI_PRIV_RAM_BASE II.然后调用load_saved_priv_data 所以,用户定义参数优先级高于默认参数,如果用户定义,则覆盖默认参数,使用用户定义参数。 Back
vivi第二阶段 步骤7 Step7 init_builtin_cmd() 初始化vivi shell支持的用户命令 … 鲁东大学 LUDONG UNIVERSITY Step7 init_builtin_cmd() 初始化vivi shell支持的用户命令 VIVI内使用命令链表数据结构包含vivi shell支持的命令 命令链表中每条命令采用cmd结构体 Init_builtin_cmd()通过调用add_cmd()向链表中加入命令 Init_builtin_cmd() 源码 command.c add_cmd(&load_cmd) add_cmd()多次调用,根据需要将所有命令加入命令链表 … add_cmd(&help_cmd)
"help [{cmds}] \t\t\t-- Help about help?" vivi用户命令 数据结构 鲁东大学 LUDONG UNIVERSITY typedef struct user_command { const char *name; void (*cmdfunc)(int argc, const char **); struct user_command *next_cmd; const char *helpstr; } user_command_t; head_cmd tail_cmd name *cmdfunc next_cmd helpstr name *cmdfunc next_cmd helpstr help command_help null helpstr 命令功能函数 命令功能函数 command_help() … "help [{cmds}] \t\t\t-- Help about help?"
tail_cmd->next_cmd=cmd head_cmd=tail_cmd=cmd vivi添加用户命令 鲁东大学 LUDONG UNIVERSITY void add_cmd(user_command_t *cmd) Y head_cmd==null? N tail_cmd->next_cmd=cmd tail_cmd=cmd head_cmd=tail_cmd=cmd Back
vivi第二阶段 步骤8 step8 boot_or_vivi() vivi启动的最后步骤 等待按键 step2-初始化开发板 鲁东大学 LUDONG UNIVERSITY step8 boot_or_vivi() vivi启动的最后步骤 等待按键 step2-初始化开发板 step3-初始化mmu step4-初始化堆 step5-初始化mtd设备 step6-拷贝参数 为进入嵌入式操作系统做好了准备 其他 回车/无输入 vivi_shell() run_autoboot() 源码 main.c 同时,stage2的step7-初始化用户命令,即为vivi_shell用户交互准备完成。
exec_string()调用parseargs() exec_string()调用exec_cmd() vivi_shell() 鲁东大学 LUDONG UNIVERSITY 当用户按下除回车键外其他按键时,系统调用vivi_shell(),等待通过串口接收的用户命令,对于命令进行相应的处理。 vivi_shell()函数调用了serial_term,通过串口进行交互 serial_term() 显示提示符 vivi> 调用getcmd() 如果有用户输入 调用exec_string() parseargs()将用户输入的string分离为用户命令及其参数argc,argv[] exec_cmd()根据用户命令,查找step7中初始化的命令链表,调用相应的cmdfunc执行 exec_string()调用parseargs() exec_string()调用exec_cmd()
run_autoboot() 当按键输入回车或超时(boot_delay)无输入时,vivi调用run_autoboot()运行。 鲁东大学 LUDONG UNIVERSITY 当按键输入回车或超时(boot_delay)无输入时,vivi调用run_autoboot()运行。 run_autoboot() exec_string(“boot”) run_autoboot()实际执行了无参数的boot用户命令。 parseargs () 用户由vivi_shell输入的boot命令行: 无参数 boot 一参数 boot 设备类型 两参数 boot 设备类型 分区 三参数 boot 设备类型 起始地址 长度 execcmd() command_boot()
command_boot() command_boot() 获得kernel所在的设备类型,并根据参数确定kernel的起始位置及长度 鲁东大学 LUDONG UNIVERSITY command_boot() 获得kernel所在的设备类型,并根据参数确定kernel的起始位置及长度 get_mtd_partition(”kernel”) 将kernel从mtd设备拷贝 到SDRAM指定位置,如果未指定 则拷贝到DRAM开始的空闲区域 boot_kernel(from,size,media_type) call_linux(0,mach_type,to) mov pc,r2; //r2=to=boot_mem_base + LINUX_KERNEL_OFFSET
附:CP15-C1 鲁东大学 LUDONG UNIVERSITY
附:MMU-AP控制 鲁东大学 LUDONG UNIVERSITY
附:nand _flash_ids 鲁东大学 LUDONG UNIVERSITY