uboot编译运行应用程序笔记
前言
uboot运行的应用程序通常是自己编写的裸机程序,最大的缺点是加载到内存运行后无法返回至uboot命令行。笔者最近在学习uboot时无意间发现uboot带有应用程序的相关例子。下图为编译uboot时的输出信息:
笔者随即在网络上寻找相关资料,但资料很少。故笔者将自己的调试uboot应用程序的方法整理总结下来,远远谈不上教程,想直接看结果的可以移步至总结章节。
环境
-
硬件平台:IMX6ULL
-
uboot版本:uboot-2016.03
-
gcc 版本:4.9.4 (Linaro GCC 4.9-2017.01)
-
开发板内存:起始地址-0x80000000,大小-0x20000000
-
zImage加载地址:0x80800000
注:本文的目录的相对地址均以uboot源码的根目录为起始。
过程
在前言中了解到既然编译出了hello_wold.bin
应用程序,那么我们将其加载到板子上的内存上运行一下观察效果。hello_wold.c
程序如下:
/*
* (C) Copyright 2000
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <exports.h>
int hello_world (int argc, char * const argv[])
{
int i;
/* Print the ABI version */
app_startup(argv);
printf ("Example expects ABI version %d\n", XF_VERSION);
printf ("Actual U-Boot ABI version %d\n", (int)get_version());
printf ("Hello World\n");
printf ("argc = %d\n", argc);
for (i=0; i<=argc; ++i) {
printf ("argv[%d] = \"%s\"\n",
i,
argv[i] ? argv[i] : "<NULL>");
}
printf ("Hit any key to exit ... ");
while (!tstc())
;
/* consume input */
(void) getc();
printf ("\n\n");
return (0);
}
通过代码可以了解到,此程序主要是打印一些调试信息。
启动板子,进入命令行模式,配置好网络,输入以下命令:
tftp 80000000 hello_world.bin
go 80000000
输出如下:
可以观察到没有任何调试信息输出,但应用程序可以正常返回。据此可以推断程序是可以运行的,但print函数没效果,很有可能是链接地址和加载地址不一致所导致的,在程序运行时去print函数所在的位置找不到该函数。
为了验证这个想法,在上面的hello_world.c
中添加如下代码:
......
printf ("Hello World\n");
/* 新添加的代码 */
volatile unsigned int *pRegs;
pRegs = (volatile unsigned int *)(0x020AC000 + 0x0);
*pRegs &= ~(1 << 3);
*pRegs |= (1 << 3);
此代码的作用是直接操作GPIO的数据寄存器关闭开发板上一个led灯,在先前的uboot初始化中我开启了这个led。假如关闭了这个led,则验证了上面的想法是正确的。
在uboot根目录下使用make
编译,观察输出信息:
可以观察到确实重新生成了hello_world.c
,再次将hello_world.bin
加载到内存运行,结果led灯确实关闭了,猜想正确。
接下来需要考虑的是如何修改hello_world.bin
的链接地址。
为了了解到hello_world.c
是如何编译的,需要打开完整的uboot编译信息,在在uboot根目录下输入以下命令:
make distclean
make sky_board_defconfig
make -j6 V=1 | grep hello_world
make distclean
:清理工程make sky_board_defconfig
:生成默认的.config
文件,根据自己的工程环境修改即可make -j6 V=1 | grep hello_world
:-j6 多线程编译,V=1 打开全部的调试信息,grep hello_world 只输出带有 hello_world 字样的调试信息。
调试信息输出如下:
与hello_world
相关的只有4条信息,从第二条信息可以了解到hello_world
的代码段链接在0xc100000地址。
于是在Makefile里寻找编译该语句的命令,打开hello_world.c
所在文件夹下的Makefile,即相对路径为./examples/standalone/Makefile
。观察到有下面的命令:
在上图的第64行就是将代码段连接在CONFIG_STANDALONE_LOAD_ADDR
宏定义所在的地址下。在uboot根目录下输入下面的命令来寻找与之相关的代码段:
grep -nR CONFIG_STANDALONE_LOAD_ADDR
输出如下:
在./arch/arm/config.mk
目录下发现了0xc100000地址,使用vim查看该代码段
vim ./arch/arm/config.mk +12
代码段如下:
此代码段就是控制uboot应用程序的链接地址的,可以直接修改0xc100000的值。本着程序设计的原则,可以在开发板的头文件中修改,笔者的开发板头文件目录为./include/configs/sky_board.h
,添加以下代码:
#define CONFIG_STANDALONE_LOAD_ADDR 0x80800000
将链接地址修改为zImage镜像的加载地址,这样程序在运行时绝对不会干扰到uboot的正常运行。
在命令行中使用make -j6 V=1 | grep hello_world
重新编译uboot,观察输出信息:
链接地址修改为0x80800000,将程序加载到内存地址为0x80800000处中运行,输出如下:
成功打印应用程序的调试信息!!!
总结
目的:实现uboot应用程序正常返回,并且可以使用相关库函数和uboot提供的函数。
实现:
- 在开发板头文件中添加应用程序链接地址。如在笔者的环境下,在
./include/configs/sky_board.h
中添加#define CONFIG_STANDALONE_LOAD_ADDR 0x80800000
代码段。 - 在
./examples/standalone
目录下添加自己的.c
文件。如在./examples/standalone
目录下添加test.c
文件 - 编写
test.c
文件。
#include <common.h>
#include <exports.h>
// 函数名称须与文件名一致,否则会弹出警告
int test(int argc, char *const argv[])
{
int i;
app_startup(argv); // 必须调用此函数
for (i = 0;i <= argc; ++i) { // 打印所有命令行参数
printf ("argv[%d] = \"%s\"\n", i, argv[i] ? argv[i] : "<NULL>");
}
printf ("\n\n");
return (0);
}
-
修改
./examples/standalone
目录下的Makefile
。如果添加的c文件名为test.c
,则在适当位置添加如下代码段extra-y += test
-
使用
make
或者make examples
命令编译即可。上传至开发板运行如下:
注:应用程序链接地址必须和内存加载地址保持一致
举个栗子
要求:在Linux启动之前,让LED灯闪烁三次,周期为500ms。
思路:编写led闪烁的应用程序,先将应用程序加载到内存中,再通过fatwrite
命令将应用程序写入到系统分区中,修改bootcmd
使每次启动时都从系统分区加载应用程序并运行。
步骤:
- 编写
led.c
应用程序
#include <common.h>
#include <exports.h>
int led(int argc, char *const argv[])
{
int i;
volatile unsigned int *pRegs;
pRegs = (volatile unsigned int *)(0x020AC000 + 0x0);
app_startup(argv);
for (i = 0; i < 3; i++) {
*pRegs &= ~(1 << 3);
printf("LED ON \r\n");
mdelay(500);
*pRegs |= (1 << 3);
printf("LED OFF \r\n");
mdelay(500);
}
return (0);
}
-
修改Makefile,添加合适位置
extra-y += led
。 -
使用
make examples
编译应用程序 -
修改
bootcmd
。在bootcmd中添加如下代码:(使用setenv
修改,或者直接修改代码)
"fatload mmc 0:1 80800000 led.bin;"
"go 80800000;"
- 在uboot命令行中使用
tftp 80800000 led.bin
将应用程序加载到内存0x80800000处。
- 使用
fatwrite mmc 0:1 80800000 led.bin 1a5
命令将内存起始位置为0x80800000,大小为0x01a5的数据拷贝到mmc0(SD卡)的1分区(系统分区)处,文件名为led.bin
。
- 使用
fatls mmc 0:1
查看分区文件内容,观察到出现led.bin
,重启开发板即可
打印输出如下