jephen

翻译:U-Boot programming: A tutorial Part I

0
阅读(1649)

英文原文:http://xillybus.com/tutorials/uboot-hacking-howto-1


介绍:

用户通常需要对U-Boot进行细微更改,以使其适应定制硬件。 例如,支持定制板的功能或添加一些输出信息,使最终用户能够确认设备已启动,并且了解启动过程中发生了哪些事情。

本简短教程侧重于ARM的U-Boot,但其他架构上使用的技术类似且通常完全相同。 本教程假设读者熟悉U-Boot命令行的使用以及编译和部署。

强烈建议首先阅读U-boot根目录中的README文件。 它包括以下内容:

  • 源文件树结构

  • CONFIG的含义定义

  • 构建U-Boot的说明。

  • 如何将U-Boot移植到新平台

  • Hush shell的简要说明

  • 如何构建Linux映像(mkimage)

  • 常见环境变量列表

  • “Hello world”示例以及如何使用它

boards.cfg文件包含了支持的板卡列表。 这个文件也是值得一看的。

本教程是针对U-Boot版本v2013.07编写的,但这些方法适用于各种版本。


明智的裁剪

遇到大量软件源时的直接本能是寻找进行裁剪的第一个地方,并将必要的功能进行硬编码。但这不仅会导致将来出现令人畏惧的重新裁剪和重新编译,而且这是不必要的:U-Boot实际上是为了方便添加自定义功能而设计的。

我们可以将u-boot可能的修改分为三类:

1.U-Boot初始化过程中的修改,以便尽早启动定制板卡上的特定硬件

2.通过添加或修改底层驱动程序,添加对特定硬件的支持

3.扩展命令界面以支持所需的功能,将uboot作为调用新硬件的前端。

在板卡的初始化过程中裁剪代码以执行特定操作可能很诱人,也很可能会奏效,但正如刚刚提到的,硬编码有其缺点。 如果硬件的设置可以推迟到命令执行阶段,那么编写一个小的自定义驱动程序和命令支持来支持硬件会更加优雅和可重用。

本教程分为三个部分:U-Boot的一般介绍(本部分),关于如何添加功能的实际说明(第二部分)以及U-Boot启动过程的一些背景,适合那些需要在早期初始化一些硬件的工程师(第三部分)。


软件组织

Linux内核裁剪者会对U-Boot感到相对满意,因为其大部分编码风格和组织都受到Linux内核的启发。 然而,其结构更简单,但成本更低,灵活性更低。 驱动程序和用户前端之间没有中间层。

例如,要获取GPIO引脚的值,只需从代码中的任何位置使用GPIO的引脚编号调用gpio_get_value(gpio)。 不会有多个GPIO驱动程序编译到系统中,只有一个定义此功能的源文件被编译,否则链接将失败。 当然,如果在某处使用gpio_get_value(),则必须编译这个源文件。

因此,U-Boot中的“硬件驱动程序”只是一段代码,它实现了一组链接到全局名称空间的函数。 这对于需要占用少的资源的实用程序来说十分有意义:不会编译没有意义的东西,并且大多数时候都是一组固定的硬件,并且每种硬件只有一个实例。

当然,一个驱动可能依赖于另一个驱动。 例如,SOFT_I2C驱动程序依赖于连接到I2C设备的两个GPIO引脚。 可以使用GPIO的API函数访问这些引脚。 任何其他软件也可以通过GPIO API访问引脚(希望不是相同的引脚)。

运行makeXXX_config的背后

任何构建U-Boot的人都会输入类似下面的内容:

$ make zynq_zed_config

在编译uboot之前。 许多人会发现有一个/include/configs/目录,目录中有相应的文件,例如zynq_zed.h,文件的内容如下(此处未显示顶部的GPLv2标头):

#ifndef __CONFIG_ZYNQ_ZED_H
#define __CONFIG_ZYNQ_ZED_H
#define PHYS_SDRAM_1_SIZE (512 * 1024 * 1024)
#define CONFIG_ZYNQ_SERIAL_UART1
#define CONFIG_ZYNQ_GEM0
#define CONFIG_ZYNQ_GEM_PHY_ADDR0
#define CONFIG_SYS_NO_FLASH
#define CONFIG_ZYNQ_SDHCI0
#define CONFIG_ZYNQ_QSPI
#define CONFIG_ZYNQ_BOOT_FREEBSD
#include <configs/zynq_common.h>
#endif /* __CONFIG_ZYNQ_ZED_H */

这实际上只是冰山一角。 运行“make zynq_zed_config”命令会生成include / config.h。 如下:

/* Automatically generated - do not edit */
#define CONFIG_SYS_ARCH  "arm"
#define CONFIG_SYS_CPU   "armv7"
#define CONFIG_SYS_BOARD "zynq"
#define CONFIG_SYS_VENDOR "xilinx"
#define CONFIG_SYS_SOC    "zynq"
#define CONFIG_BOARDDIR board/xilinx/zynq
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/zynq_zed.h>
#include <asm/config.h>
#include <config_fallbacks.h>
#include <config_uncmd_spl.h>

配置文件包含其他#define(可能还有#undef)语句,其中是大部分用于带有CONFIG_ *前缀的变量。

在这一点上,很明显有一些定义了的CONFIG_ *编译变量(和其他变量)的头文件。 这些头文件会以两种方式影响代码的编译方式:

1.定义的所有CONFIG_ *变量都转换为include/autoconf.mk中的Makefile变量,定义的每个变量(与变量对应的赋值)获得值“y”。 这个值在每个源目录的Makefile中使用,以选择编译和链接到主可执行文件的文件。

2.这些编译变量直接被编译好的C源码调用,并且可能包含特定目标板卡的属性。

与Linux内核不同,uboot没有KConfig应用程序,因此这些定义是通过特定板卡的h文件实现。要添加新的“make config”目标板卡,需要在include/configs/中创建一个新的配置文件(或者复制一个类似的配置文件),并在boards.cfg中添加一行。


U-Boot代码的三个功能

可以将代码分为三种类型(与上面提到的三种修改对应):

1.纯初始化代码:此代码始终在U-Boot自身启动期间运行。 此阶段的更多信息可参看本教程第三部分。

2.“驱动程序”:实现一组功能的代码,可以访问某个硬件。 其中很多都可以在drivers/,fs/和其他目录中找到

3.命令:通常根据对驱动程序API的调用,向U-Boot shell添加命令并实现其功能。 这些命令在common / cmd _ *.c文件中。

所有三种代码类型都受到CONFIG定义的强烈影响。 例如,一个能够编译某个驱动程序的CONFIG也可能用#ifdef选择一段初始化代码。

典型的向U-Boot添加新功能的方法是编写驱动程序代码,为其编写调用命令,并使用CONFIG标志启用它们。 在某些情况下,可以在初始化序列中添加一个代码段,以便在执行任何命令之前准备硬件。