LV05-02-U-Boot-03-uboot下GPIO的控制

本文主要是uboot——uboot下GPIO控制的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
其他网站 kernel - Linux source code (v4.15) - Bootlin linux内核源码在线查看
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

这一节,来学习一下,怎么在uboot下新增一个命令来控制gpio。

一、硬件原理图

我们先看底板原理图(”阿尔法Linux开发板(A盘)-基础资料/02、开发板原理图/IMX6ULL_ALPHA_V2.2(底板原理图).pdf”

image-20230716170503614

可以看到LED0 接到了 GPIO_3 上, 这个GPIO_3是哪个引脚?感觉这里标的有问题,我们搜索一下,会发现这里接在一个BTB连接器母座上,所以我们再去搜一些核心板原理图 “阿尔法Linux开发板(A盘)-基础资料/02、开发板原理图/IMX6ULL_CORE_V1.6(核心板原理图).pdf”:

image-20241107075112786

就会发现GPIO_3 就是 GPIO1_IO03,根据底板原理图,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭。

二、uboot的gpio控制命令

1. 命令格式

我看在我使用的uboot 2019.04中是自带一个gpio命令的:

1
2
3
4
5
6
7
=> gpio
gpio - query and control gpio pins

Usage:
gpio <input|set|clear|toggle> <pin>
- input/set/clear/toggle the specified pin
gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs

这里可以控制某一个GPIO为输入模式、输出模式,为输出的时候可以使用set命令配置引脚输出高电平,使用clear命令配置引脚输出低电平,使用toggle命令来反转GPIO的输出电平。还可以通过status来查看GPIO的引脚状态。

2. 使用实例

2.1 配置GPIO输出高

1
2
=> gpio set GPIO1_3
gpio: pin GPIO1_3 (gpio 3) value is 1

2.2 配置GPIO输出低

1
2
=> gpio clear GPIO1_3
gpio: pin GPIO1_3 (gpio 3) value is 0

2.3 翻转GPIO电平

1
2
3
4
=> gpio toggle GPIO1_3
gpio: pin GPIO1_3 (gpio 3) value is 0
=> gpio toggle GPIO1_3
gpio: pin GPIO1_3 (gpio 3) value is 1

2.4 查看GPIO状态 

1
2
3
4
5
6
7
8
9
10
11
12
13
=> gpio status -a
Bank GPIO1_:
GPIO1_0: input: 1 [ ]
# ......
GPIO1_3: output: 1 [ ]
# ......
GPIO1_7: input: 0 [ ]
GPIO1_8: output: 1 [x] backlight
GPIO1_9: output: 1 [x] regulator@1.gpio
GPIO1_10: input: 0 [ ]
# ......
GPIO1_19: input: 0 [x] usdhc@02190000.cd-gpios

从这里可以看到这个GPIO是输入还是释出模式以及是否使用了复用功能。

三、gpio命令的定义

这里需要先回顾一下C语言宏定义中的#和##:

(1)#的作用是转换为字符串。

(2)##的作用是连接前后的内容。

1. gpio命令变量的定义

我们先看一下gpio命令的定义,定义在gpio.c - cmd/gpio.c 中:

1
2
3
4
5
U_BOOT_CMD(gpio, 4, 0, do_gpio,
"query and control gpio pins",
"<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs");

毫无疑问,下一步就是把这个U_BOOT_CMD宏展开,看看这段代码到底干了什么。

2. U_BOOT_CMD

2.1 相关的几个宏定义

2.1.1 U_BOOT_CMD

先来看一下U_BOOT_CMD这个宏,它定义在 command.h - include/command.h 中:

1
2
3
4
5
6
7
8
9
10
/*
* name :命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
* _maxargs :命令的最大参数个数
* _rep :是否自动重复(按Enter键是否会重复执行)
* _cmd :该命令对应的响应函数指针
* _usage :简短的使用说明(字符串)
* _help :较详细的使用说明(字符串)
*/
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

这里下面还嵌套了一个宏U_BOOT_CMD_COMPLETE。

2.1.2 U_BOOT_CMD_COMPLETE

我们再找一下这个宏的位置,在uboot 2019.14的版本中,这个宏有两个定义,都定义在command.h - include/command.h文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifdef CONFIG_CMDLINE
//......
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
//......
#else
//......
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, \
_comp) \
_CMD_REMOVE(sub_ ## _name, _cmd)
//......
#endif /* CONFIG_CMDLINE */

那么用的是哪一个?我们需要去搜一下CONFIG_CMDLINE这个宏定义,这个宏我们需要将uboot编译一遍,编译命令嘛,我们就用之前NXP的哪个评估板的配置文件的编译命令吧:

1
2
3
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

编译之后在源码顶层目录下会生成.config文件,在这个文件中有它的定义:

1
CONFIG_CMDLINE=y

所以U_BOOT_CMD_COMPLETE使用的是上面的定义,也就是:

1
2
3
4
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);

可以看到里面还有一层U_BOOT_CMD_MKENT_COMPLETE。我们继续找。

2.1.3 U_BOOT_CMD_MKENT_COMPLETE

它也是根据CONFIG_CMDLINE有不同的定义,这里的定义是command.h - include/command.h

1
2
3
4
5
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
_usage, _help, _comp) \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

然后就发现这里还有一层_CMD_HELP和_CMD_COMPLETE。

2.1.4 _CMD_HELP

这个宏也定义在command.h - include/command.h

1
2
3
4
5
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif

可以看到具体的定义是CONFIG_SYS_LONGHELP控制,还是去.config文件找,会发现这个配置项为y,所以这里对应的宏为:

1
# define _CMD_HELP(x) x,

2.1.5 _CMD_COMPLETE

这个同样是定义在command.h - include/command.h

1
2
3
4
5
#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif

这里一样的方式,最后确定CONFIG_AUTO_COMPLETE=y,所以这里就是:

1
# define _CMD_COMPLETE(x) x,

2.1.6 ll_entry_declare

接下来看一下ll_entry_declare是什么,它也是一个宏,它定义在linker_lists.h - include/linker_lists.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* ll_entry_declare() - Declare linker-generated array entry
* @_type: Data type of the entry
* @_name: Name of the entry
* @_list: name of the list. Should contain only characters allowed
* in a C variable name!
*
* This macro declares a variable that is placed into a linker-generated
* array. This is a basic building block for more advanced use of linker-
* generated arrays. The user is expected to build their own macro wrapper
* around this one.
*
* A variable declared using this macro must be compile-time initialized.
*
* Special precaution must be made when using this macro:
*
* 1) The _type must not contain the "static" keyword, otherwise the
* entry is generated and can be iterated but is listed in the map
* file and cannot be retrieved by name.
*
* 2) In case a section is declared that contains some array elements AND
* a subsection of this section is declared and contains some elements,
* it is imperative that the elements are of the same type.
*
* 3) In case an outer section is declared that contains some array elements
* AND an inner subsection of this section is declared and contains some
* elements, then when traversing the outer section, even the elements of
* the inner sections are present in the array.
*
* Example:
*
* ::
*
* ll_entry_declare(struct my_sub_cmd, my_sub_cmd, cmd_sub) = {
* .x = 3,
* .y = 4,
* };
*/
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))

这里是在做什么?我们可以看上面的注释,翻译一下就是:该宏声明了一个变量,该变量放置在链接器生成的数组中。这是更高级地使用链接器生成的数组的基本构建块。用户需要围绕这个宏包装器构建自己的宏包装器。使用此宏声明的变量必须在编译时初始化。好像没怎么看懂,其实就是声明一个_type类型的变量是这样的:

1
2
3
4
5
6
_type                              // 变量类型
_u_boot_list_2_##_list##_2_##_name // 变量名
__aligned(4) // 这个变量四字节对齐
__attribute__((unused, section(".u_boot_list_2_"#_list"_2_"#_name))) // 这个变量有两个属性
// unused属性:这个变量如果没有被使用,编译器不会报“变量从未使用“的警告
// section属性:会将这个变量放置到可执行程序的 ".u_boot_list_2_"#_list"_2_"#_name 字段下,与.text段或.data等其他段独立出来

使用此宏声明的变量必须在编译时初始化。使用此宏时必须特别注意:

(1)_type不能包含“static”关键字,否则生成的条目可以被迭代,但会列在map文件中,不能通过名称检索。

(2)如果声明一个section包含一些数组元素,并且声明该section的一个子节包含一些元素,则这些元素必须具有相同的类型。

(3)如果声明了包含一些数组元素的外部分段,并且声明了该分段的内部分段并包含一些元素,则遍历外部分段时,甚至内部分段的元素都存在于数组中。

2.2 展开这几个宏

我们一层一层向上展开。

2.2.1 U_BOOT_CMD_MKENT_COMPLETE

有这两个宏:

1
2
# define _CMD_HELP(x) x,
# define _CMD_COMPLETE(x) x,

我们可以展开U_BOOT_CMD_MKENT_COMPLETE:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* U_BOOT_CMD_MKENT_COMPLETE */
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

// U_BOOT_CMD_MKENT_COMPLETE展开之后是
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, _comp,}

2.2.2 U_BOOT_CMD_COMPLETE

接下来是U_BOOT_CMD_COMPLETE,有上面的U_BOOT_CMD_MKENT_COMPLETE可以得到:

1
2
3
4
5
6
7
8
9
10
11
/* U_BOOT_CMD_COMPLETE */
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
// U_BOOT_CMD_COMPLETE展开之后就是
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, _comp,}

2.2.3 ll_entry_declare

我们吧这个ll_entry_declare也展开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ll_entry_declare
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
// 通过ll_entry_declare(cmd_tbl_t, _name, cmd)传入的三个参数
// _type = cmd_tbl_t
// _name = _name
// _list = cmd 注意这里cmd不是_cmd,cmd就只是在下面被替换成字符串,_cmd依赖上层传入
// 将ll_entry_declare内部的变量替换一下可以得到
#define ll_entry_declare(cmd_tbl_t, _name, cmd) \
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_""cmd""_2_"#_name)))

// 这里可以对 U_BOOT_CMD_COMPLETE 进一步展开之后就是
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_""cmd""_2_"#_name))) = \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, _comp,}

2.2.4 U_BOOT_CMD

最后一步就是把U_BOOT_CMD展开了,根据前面的U_BOOT_CMD_MKENT_COMPLETE展开形式,可以得到:

1
2
3
4
5
6
7
8
9
10
11
/* U_BOOT_CMD */
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
// U_BOOT_CMD最终展开后就是
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_""cmd""_2_"#_name))) = \
{ #_name, _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, NULL,}

我们知道#就是转换为字符串,这个时候可以进一步把#替换掉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_""cmd""_2_""_name"))) = \
{ "_name", _maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, NULL,}

// 整理一下就是
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_""cmd""_2_""_name"))) = \
{ \
"_name", \
_maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, \
_usage, \
_help, \
NULL,}

2.3 gpio命令变量

2.3.1 变量定义展开

上面已经把U_BOOT_CMD展开了,我们现在根据gpio命令变量的定义,对变量进行替换,前面已经看到了gpio命令的定义,定义在gpio.c - cmd/gpio.c 中:

1
2
3
4
5
U_BOOT_CMD(gpio, 4, 0, do_gpio,
"query and control gpio pins",
"<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs");

里面的各个变量对应关系是这样的:

1
2
3
4
5
6
7
8
9
10
/*
* name=gpio, 命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
* _maxargs=4, 命令的最大参数个数
* _rep=0, 是否自动重复(按Enter键是否会重复执行)
* _cmd=do_gpio, 该命令对应的响应函数指针
* _usage="query and control gpio pins", 简短的使用说明(字符串)
* _help="<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs",较详细的使用说明(字符串)
*/

然后我们完全展开,可以得到gpio命令变量的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//      U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)
#define U_BOOT_CMD(gpio, 4, 0, do_gpio, "usage", "help") \
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_""cmd""_2_""_name"))) = \
{ \
"_name", \
_maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, _usage, _help, NULL,}
// 替换后就是
#define U_BOOT_CMD(gpio, 4, 0, do_gpio, "usage", "help") \
cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_""cmd""_2_""gpio"))) = \
{ \
"gpio", \
4, \
0 ? cmd_always_repeatable : cmd_never_repeatable, \
do_gpio, \
"query and control gpio pins", \
"<input|set|clear|toggle> <pin>\n" \
" - input/set/clear/toggle the specified pin\n" \
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs", \
NULL,}

把宏的标识去掉,就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这里的".u_boot_list_2_""cmd""_2_""gpio"其实可以把中间的"去掉的
// 那个条件判断的运算符也可以去掉
// 最后整理一下那就是
cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4)
__attribute__((unused, section(".u_boot_list_2_cmd_2_gpio"))) = {
"gpio",
4,
cmd_never_repeatable,
do_gpio,
"query and control gpio pins",
"<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs",
NULL,};

我们可以去看一下map文件(01_uboot/01_gpio_cmd/u-boot.map · 苏木/imx6ull-driver-demo - 码云 - 开源中国 (gitee.com)),搜索关键字就会发现里面有这个命令在:

image-20241114222950493

这也可以证明前面宏展开是正确的。

2.3.2 cmd_tbl_t类型

可以看到上面的变量类型是cmd_tbl_t,它是一个结构体类型,定义在command.h - include/command.h

1
typedef struct cmd_tbl_s	cmd_tbl_t;

最终的定义是在这里command.h - include/command.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
/*
* Same as ->cmd() except the command
* tells us if it can be repeated.
* Replaces the old ->repeatable field
* which was not able to make
* repeatable property different for
* the main command and sub-commands.
*/
int (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc,
char * const argv[], int *repeatable);
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

前面分析过,CONFIG_SYS_LONGHELP和CONFIG_AUTO_COMPLETE都有定义,所以这里就是:

1
2
3
4
5
6
7
8
9
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc, char * const argv[], int *repeatable); /* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
char *help; /* Help message (long) */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);/* do auto completion on the arguments */
};

可以看到这里有三个函数指针,其中complete指针由于U_BOOT_CMD_COMPLETE宏中固定为NULL,这里可以不用关心。还有两个函数指针cmd_rep和cmd,我们接着看一下。

2.4 两个函数指针

2.4.1 cmd_rep

2.4.1.1 函数指针定义

这个函数指针定义为:

1
int  (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc, char * const argv[], int *repeatable); /* Implementation function	*/

这个函数指针含有5个形参,返回值为int类型。它主要控制是否自动重复(按Enter键是否会重复执行)命令。在命令定义的时候这里主要是根据_rep的值来决定调用哪个函数。

1
2
3
4
5
6
7
8
9
10
11
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_""cmd""_2_""_name"))) = \
{ \
"_name", \
_maxargs, \
_rep ? cmd_always_repeatable : cmd_never_repeatable, \
_cmd, \
_usage, \
_help, \
NULL,}
2.4.1.2 cmd_always_repeatable()

看一下cmd_always_repeatable,它定义在command.c - common/command.c

1
2
3
4
5
6
7
8
int cmd_always_repeatable(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], int *repeatable)
{
*repeatable = 1;

return cmdtp->cmd(cmdtp, flag, argc, argv);
}

2.4.1.3 cmd_never_repeatable()

cmd_never_repeatable定义在command.c - common/command.c

1
2
3
4
5
6
7
int cmd_never_repeatable(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], int *repeatable)
{
*repeatable = 0;

return cmdtp->cmd(cmdtp, flag, argc, argv);
}

从gpio的命令变量定义来看,这里调用的就是这个函数了。

2.4.2 cmd

2.4.2.1 函数指针定义

这个函数指针定义为:

1
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);

它有四个参数,返回值为int类型。这个函数指针就是命令的实现过程,我么敲了这个命令之后要执行什么,就在这个函数指针指向的函数中实现。

2.4.2.2 do_gpio()

前面展开的时候可以看到这个cmd函数指针指向的是do_gpio函数,它定义在gpio.c - cmd/gpio.c

1
static int do_gpio(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);

3. 总结一下吧

这里总结一下吧,总的来说上面那么多就是分析了一条语句:

1
2
3
4
5
U_BOOT_CMD(gpio, 4, 0, do_gpio,
"query and control gpio pins",
"<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs");

这条语句经过层层展开之后,实际上是定义了一个结构体变量:

1
2
3
4
5
6
7
8
9
10
11
cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4)
__attribute__((unused, section(".u_boot_list_2_cmd_2_gpio"))) = {
"gpio",
4,
cmd_never_repeatable,
do_gpio,
"query and control gpio pins",
"<input|set|clear|toggle> <pin>\n"
" - input/set/clear/toggle the specified pin\n"
"gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs",
NULL,};

这个结构体变量名是_u_boot_list_2_cmd_2_gpio,类型是cmd_tbl_t,这个变量在内存中是四字节对齐的,通过__attribute__给这个结构体变量设置了两个属性:

  • unused属性:这个变量如果没有被使用,编译器不会报“变量从未使用“的警告
  • section属性:".u_boot_list_2_cmd_2_gpio"属性会将这个变量放置到可执行程序的".u_boot_list_2_cmd_2_gpio"字段下,与.text段或.data等其他段独立出来

参考资料:

petalinux - u-boot中操作gpio_uboot gpio操作-CSDN博客

6. Uboot的GPIO控制 — [野火]嵌入式Linux镜像构建与部署——基于LubanCat-i.MX6ULL开发板 文档 (embedfire.com)

u-boot的命令实现(添加自定义命令)_uboot 自定义 函数-CSDN博客

Linux下uboot添加自定义命令(详细)实例及原理解析_怎么修改uboot指令-CSDN博客

链接脚本.lds(详细)总结附实例快速掌握-CSDN博客

9.uboot命令体系 源码解读并从中添加命令_uboot让参数等于命令返回值-CSDN博客

u-boot的linker list源码分析_uboot env callback-CSDN博客

uboot源码分析uboot启动流程,uboot-CMD命令调用关系_u-boot源码分析-CSDN博客