• 系统移植2:bootloader的选择(u-boot)和移植


    Boot Loader 概念

    就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,他就是所谓的引导加载程序(Boot Loader)。

    为什么系统移植之前要先移植BootLoader?

    BootLoader的任务是引导操作系统,所谓引导操作系统,就是启动内核,让内核运行就是把内核加载到内存RAM中去运行,那先问两个问题:第一个问题,是谁把内核搬到内存中去运行?第二个问题:我们说的内存是SDRAM,大家都知道,这种内存和SRAM不同,最大的不同就是SRAM只要系统上电就可以运行,而SDRAM需要软件进行初始化才能运行,那么在把内核搬运到内存运行之前必须要先初始化内存吧,那么内存是由谁来初始化的呢?其实这两件事情都是由bootloader来干的,目的是为内核的运行准备好软硬件环境,没有bootloadr我们的系统当然不能跑起来。

    bootloader的分类

    首先更正一个错误的说法,很多人说bootloader就是U-boot,这种说法是错误的,确切来说是u-boot是bootloader的一种。也就是说bootloader具有很多种类,不同的bootloader具有不同的使用范围,其中最令人瞩目的就是有一个叫U-Boot的bootloader,是一个通用的引导程序,而且同时支持X86、ARM和PowerPC等多种处理器架构。U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目,是由德国DENX小组开发的用于多种嵌入式CPU的bootloader程序,对于Linux的开发,德国的u-boot做出了巨大的贡献,而且是开源的。

    u-boot的特点

    ① 开放源码;代码结构清晰,易于移植,命令丰富有监控功能

    ② 支持多种嵌入式操作系统内核,如Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS;

    ③ 支持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;

    ④ 较高的可靠性和稳定性;

    ⑤ 高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;

    ⑥ 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;

    ⑦ 较为丰富的开发调试文档与强大的网络技术支持;

    u-boot的工作模式

    U-Boot的工作模式有启动加载模式和下载模式。启动加载模式是Bootloader的正常工作模式,嵌入式产品发布时,Bootloader必须工作在这种模式下,Bootloader将嵌入式操作系统从FLASH中加载到SDRAM中运行,整个过程是自动的。下载模式就是Bootloader通过某些通信手段将内核映像或根文件系统映像等从PC机中下载到目标板的SDRAM中运行,用户可以利用Bootloader提供的一些令接口来完成自己想要的操作,这种模式主要用于测试和开发。

    u-boot的启动过程

    大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。

    1、 stage1(start.s代码结构)

    U-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:

    (1) 定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。

    (2)设置异常向量(exception vector)。

    (3)设置CPU的速度、时钟频率及中断控制寄存器。

    (4)初始化内存控制器 。

    (5)将rom中的程序复制到ram中。

    (6)初始化堆栈 。

    (7)转到ram中执行,该工作可使用指令ldrpc来完成。

    2、 stage2(C语言代码部分)

    lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:

    (1)调用一系列的初始化函数。

    (2)初始化flash设备。

    (3)初始化系统内存分配函数。

    (4)如果目标系统拥有nand设备,则初始化nand设备。

    (5)如果目标系统有显示设备,则初始化该类设备。

    (6)初始化相关网络设备,填写ip,c地址等。

    (7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。

    u-boot移植(基于cortex_a9的fs4412(exynos4412芯片)为例)

    1.建立自己的平台

    (1).下载u-boot源码包2010.03版本,比较稳定

    (2).解压后添加我们自己的平台信息,我们先在board.cfg中去找对应的开发板型号,如果没有找到对应的开发板型号,那我们就去找使用相同型号芯片的开发板,对他的源码进行copy,如果没有找到相同型号的芯片,那我们可以去找更新版本的,将其移植到旧版中进行使用,或者联系厂家。

    (3).随后修改相应目录的文件名,比如我们使用的开发板为fs4412而找到相同芯片的板子为orgine,我们可以将其复制一份修改为我们开发板的型号,和相应目录的Makefile,指定交叉工具链,由于我们是基于ARM体系的,所以可以进入Makefile文件,搜索CROSS,将其固定为arm-linux-。

    (4).编译,生成bin文件,随后便可以进行烧写。

    (5)烧写

    1)SD卡启动
    2)tftp下载  u-boot.bin(tftp 41000000 u-boot.bin)
    3)烧写:movi write uboot  41000000
    4)断电、拨码开关EMMC启动,上电。

    现象:

    但是启动之后我们发现超级终端没有任何界面输出,这表示我们的程序可能没有运行起来,于是我们在uboot的入口函数 start.s汇编文件中,加入一段点灯代码来进行调试,在嵌入式开发中我们可能经常会使用点灯来进行调试,最后发现灯并没有亮起来,那么毫无疑问,我们的开办板成了一块板砖,程序并没有执行。

    现在我们的移植出现了一些问题,要如何解决呢,首先我们需要知道它的启动原理,以及引导顺序,我们可以去查看芯片手册中的相关章节:

    通过阅读手册我们发现芯片自己的引导加载程序包括第一和第二引导加载器。这些引导加载程序的特点是:

    1.iROM: It is a small and simple code to initiate SOC. It is implemented on internal ROM of SOC.(这是一个启动SOC的小而简单的代码。它在SOC的内部ROM上实现了实现。)

    2.First boot loader (BL1): It is chip-specific and stored in external memory device.

    (第一个引导加载程序(BL1):它是特定于芯片的,并存储在外部存储器设备中。)

    3.Second boot loader (BL2): It is platform-specific and stored in external memory device. User should build and store this in an external memory device. It is not provided by Samsung.

    第二个引导加载程序(BL2):它是特定于平台的,并存储在外部内存设备中。用户应该将其构建并存储在外部内存设备中。它不是由三星提供的。

     上图就是我们启动时间操作的方框图

    iROM被放置在内部64 KB的ROM中。它初始化基本的系统功能,如时钟和堆栈

    iROM将BL1映像从一个特定的引导设备加载到内部的256 KB的SRAM。通过操作模式(OM)引脚选择引导设备。根据安全引导键值,iROM可以对BL1映像进行完整性检查,检查完毕将Bl1加载到SRAM初始化系统时钟,随后对bl2进行校验,校验完成后加载到SRAM中,初始化系统时钟和DRAM控制器后,将内存DARM初始化,随后bl2将OS映像(uboot)从引导设备加载到DRAM。根据安全引导密钥值,BL2可以对OS映像进行完整性检查。

    可以发现我们的emmc中只有uboot,而没有移植bl1,bl2,这将导致校验失败,所以无法运行我们的uboot程序,看来我们似乎找到问题关键所在。

    在完善了我们的bin文件后,我们发现灯亮了,但是有一个现象,灯会熄灭之后再亮,好吧似乎我们还需要再思考一下!!在我们排除了发生异常产生复位信号,看门狗等问题后,我们发现好像我们的程序是没有问题的,好吧我们似乎走进了死路,这个时候该怎么办呢??

    既然找不到原因,那不如我们直接将产生这个现象的源头杀死!!!既然是产生了复位信号,那么肯定是电源管理模块,那我们直接将电源模块的复位功能给干掉!让他不处理复位信号即可!实际开发中总会出现各种问题,大家要学会随机应变哦!

    随后我们解决了这个问题之后,发现超级终端还是没有界面输出,但灯没有问题了,那么我们的程序应该是在正常运行,哎又是哪里出了问题呢?????

    思考:我们界面的输出都是通过串口来实现的,那么是不是说明我们的串口初始化出了问题呢??好吧让我们回到start.s文件查看我们的串口初始化代码,最后我们发现波特率的设置来源于串口时装而串口时钟来源于我们系统时钟的分频,我们看一下这段源码:

    1. /*
    2. 84 * If U-boot is already running in ram, no need to relocate U-Boot.
    3. 85 * Memory controller must be configured before relocating U-Boot
    4. 86 * in ram.
    5. 87 */
    6. 88 ldr r0, =0x0ffffff /* r0 <- Mask Bits*/
    7. 89 bic r1, pc, r0 /* pc <- current addr of code */
    8. 90 /* r1 <- unmasked bits of pc */
    9. 91 ldr r2, _TEXT_BASE /* r2 <- original base addr in ram */
    10. 92 bic r2, r2, r0 /* r2 <- unmasked bits of r2*/
    11. 93 cmp r1, r2 /* compare r1, r2 */
    12. 94 beq 1f /* r0 == r1 then skip sdram init */
    13. /* init system clock */
    14. 97 bl system_clock_init
    15. 98
    16. 99 /* Memory initialize */
    17. 100 bl mem_ctrl_asm_init
    18. 101
    19. 102 1:
    20. 103 /* for UART */
    21. 104 bl uart_asm_init

    通过将pc的值与0x0ffffff进行位清除操作,以保留当前pc值的高字节bit[31:24],根据该值判断当前程序是否运行于ram中,因为ram的基地址分别是0x4000_0000、0xA000_0000,如果位于sram中则pc的高地址必不为0,如果位于sram中则跳转到局部标号1,否则顺序执行97、100行代码 97-100 初始化系统时钟和uart,看来我们系统时钟的初始化可能会有问题,于是我们将系统时钟和uart时钟初始化放到串口初始化代码中,解决了这个问题!!!!!!

    在串口初始化函数中将串口时钟和系统时钟分频加上会发现超级终端有界面输出,此时我们进行一些指令操作发现像ping和tftp等网络命令不可以使用

    思考原因:
    1.因为我们使用的是相同芯片但其他型号的开发板源码,这个开发板可能并不支持网络功能而uboot的指令是非常多的,在常用命令common文件夹的cmd_net.c文件中我们发现命令由宏开关把控,ping和tftp命令是关闭了的,所以我们在我们开发板的头文件fs4412.h中将它打开 

    undef--->define

    2.修改之后我们进行烧写,随后使用ping命令发现,一直阻塞

    思考:为什么会一直阻塞呢,在终端我们可以看到在进行网络初始化时有 “Net initialization skipped No ethernet found”
    这表明我们网络设备未初始化,所以我们需要去找我们的网卡设备,在原理图中我们可以发现我们的网卡设备是在开发板上的dm9000
    所以我们需要去我们的c文件中添加网络设备初始化函数

    3.增加了网络设备初始化模块后,ping命令不再阻塞了,但是超级终端告诉我们网卡设备的地址有问题

    思考:我们的地址是在手册上查找的没有问题,那么有可能是我们地址的设备在进行初始化的时候出现了问题,于是我们参考其他相似开发板的初始化过程就行修改。


    解决了上述问题之后我们的uboot移植基本完成,可以看到过程很艰辛,遇到问题我们需要去发现问题出在哪,随后参考其他开发板的源码对我们自己的代码进行修改。

  • 相关阅读:
    java创建本地文件并读取文件
    【开源】基于JAVA的社区买菜系统
    Java面试题:mysql执行速度慢的原因和优化
    ‘Tensor‘ object has no attribute ‘np‘
    shell脚本学习笔记07
    centos7搭建git服务器
    [MAUI]写一个跨平台富文本编辑器
    常见JVM面试题及答案整理
    C++ 代码改变人生
    LeetCode 算法:最大子数组和c++
  • 原文地址:https://blog.csdn.net/m0_70983574/article/details/126435406