uboot代码解析5:命令集、自定义uboot命令,命令处理框架

https://ke.qq.com/course/4032547?flowToken=1042705

目录

一 uboot的命令集

二 help命令的原理

三 创建一个自定义uboot命令

写一个命令文件hello.c

配置文件到Makefile中

编译并测试结果

cmd_tbl_s 结构体

下面的可以不用看了。

四 命令交互

五 命令是存放在哪里的

1.存储段简介

2.宏实现

3.段存储

六 查找一个命令

七 运行一个命令


一 uboot的命令集

启动并进入uboot,输入help,并按回车键,得到本uboot支持的命令集

=> help
?       - alias for 'help'
base    - print or set address offset
bdinfo  - print Board Info structure
bmode   - sd1|sd2|qspi1|normal|usb|sata|ecspi1:0|ecspi1:1|ecspi1:2|ecspi1:3|esdhc1|esdhc2|esdhc3|esdhc4 [noreset]
boot    - boot default, i.e., run 'bootcmd'
bootd   - boot default, i.e., run 'bootcmd'
bootelf - Boot from an ELF image in memory
bootm   - boot application image from memory
bootp   - boot image via network using BOOTP/TFTP protocol
bootvx  - Boot vxWorks from an ELF image
bootz   - boot Linux zImage image from memory
clocks  - display clocks
cmp     - memory compare
coninfo - print console devices and information
cp      - memory copy
crc32   - checksum calculation
dcache  - enable or disable data cache
dhcp    - boot image via network using DHCP/TFTP protocol
dm      - Driver model low level access
echo    - echo args to console
editenv - edit environment variable
env     - environment handling commands
erase   - erase FLASH memory
exit    - exit script
ext2load- load binary file from a Ext2 filesystem
ext2ls  - list files in a directory (default /)
ext4load- load binary file from a Ext4 filesystem
ext4ls  - list files in a directory (default /)
ext4size- determine a file's size
ext4write- create a file in the root directory
false   - do nothing, unsuccessfully
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls   - list files in a directory (default /)
fatsize - determine a file's size
fatwrite- write file into a dos filesystem
fdt     - flattened device tree utility commands
flinfo  - print FLASH memory information
fstype  - Look up a filesystem type
fuse    - Fuse sub-system
go      - start application at address 'addr'
gpio    - query and control gpio pins
help    - print command description/usage
icache  - enable or disable instruction cache
iminfo  - print header information for application image
imxtract- extract a part of a multi-image
itest   - return true/false on integer compare
load    - load binary file from a filesystem
loadb   - load binary file over serial line (kermit mode)
loads   - load S-Record file over serial line
loadx   - load binary file over serial line (xmodem mode)
loady   - load binary file over serial line (ymodem mode)
loop    - infinite loop on address range
ls      - list files in a directory (default /)
md      - memory display
mdio    - MDIO utility commands
mii     - MII utility commands
mm      - memory modify (auto-incrementing address)
mmc     - MMC sub system
mmcinfo - display MMC info
mtest   - simple RAM read/write test
mw      - memory write (fill)
nfs     - boot image via network using NFS protocol
nm      - memory modify (constant address)
ping    - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
protect - enable or disable FLASH write protection
reset   - Perform RESET of the CPU
run     - run commands in an environment variable
save    - save file to a filesystem
saveenv - save environment variables to persistent storage
setenv  - set environment variables
setexpr - set environment variable as the result of eval expression
sf      - SPI flash sub-system
showvar - print local hushshell variables
size    - determine a file's size
sleep   - delay execution for some time
source  - run script from memory
test    - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
true    - do nothing, successfully
version - print monitor, compiler and linker version

cmd文件夹下有很多的cmd_xxx.c文件,他们就是命令相关的具体实现。

二 help命令的原理

help命令对应的处理函数是d_help,源码如下所示:

static int do_help(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
	const int len = ll_entry_count(cmd_tbl_t, cmd);
	return _do_help(start, len, cmdtp, flag, argc, argv);
}

U_BOOT_CMD(
	help,	CONFIG_SYS_MAXARGS,	1,	do_help,
	"print command description/usage",
	"\n"
	"	- print brief description of all commands\n"
	"help command ...\n"
	"	- print detailed usage of 'command'"
);

/* This does not use the U_BOOT_CMD macro as ? can't be used in symbol names */
ll_entry_declare(cmd_tbl_t, question_mark, cmd) = {
	"?",	CONFIG_SYS_MAXARGS,	1,	do_help,
	"alias for 'help'",
#ifdef  CONFIG_SYS_LONGHELP
	""
#endif /* CONFIG_SYS_LONGHELP */
};

它调用了_do_help在common/command.c文件中:

int _do_help(cmd_tbl_t *cmd_start, int cmd_items, cmd_tbl_t *cmdtp, int flag,
         int argc, char * const argv[])

help命令就是调用这个函数实现的。

1.同理,每一个命令也对应一个执行的函数。

2.由_do_help函数参数和代码分析可知,命令是以argc 、argv形式传递给处理函数的。

3.命令集全局变量static FDC_COMMAND_STRUCT cmd; /* global command struct */

4.宏U_BOOT_CMD用户注册命令,注册内容包括命令名字、帮助信息、执行函数等。

5.ll_entry_declare宏用于创建一个help命令的别名符号'?'

三 创建一个自定义uboot命令

我先自己添加一个命令,然后再分析它的执行流程,其实我只想让它工作,不想知道它是如何工作的。

操作步骤如下,进入uboot源码的cmd文件夹,复制help.c为一个新的文件hello.c,修改内容如下所示:

写一个命令文件hello.c

#ifdef debug
#undef debug
#endif
#define debug(format,...) printf("%s,%s,line=%d:"format"\n",__FILE__,__func__,__LINE__,##__VA_ARGS__)

static int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int i = 0;
	debug("argc = %d",argc);
	debug("cmdtp->name = %s",cmdtp->name);
	for(i = 0;i < argc;i++){
		debug("argv[%d] = %s",i,argv[i]);
	}
	return 0;
}

U_BOOT_CMD(
	hello,	CONFIG_SYS_MAXARGS,	1,	do_hello,
	"print hello world/usage",
	"\n"
	"	- print hello world for test\n"
	"hello command ...\n"
	"	- print a valueable message for lkmao"
);

/* This does not use the U_BOOT_CMD macro as ? can't be used in symbol names */
ll_entry_declare(cmd_tbl_t, hello_mark, cmd) = {
	"h",	CONFIG_SYS_MAXARGS,	1,	do_hello,
	"alias for 'help'",
#ifdef  CONFIG_SYS_LONGHELP
	""
#endif /* CONFIG_SYS_LONGHELP */
};

配置文件到Makefile中

然后修改Makefile文件,使它能够被编译和执行

在cmd目录的Makefile文件中添加如下内容

obj-y += hello.o

编译并测试结果

添加完毕,重新编译uboot,下载并启动,然后使用help命令测试

 看,这里不仅创建了一个hello命令,还创建了一个hello命令的别名h,测试一下这两个命令

=> help hello
hello - print hello world/usage

Usage:
hello
        - print hello world for test
hello command ...
        - print a valueable message for lkmao
=> help h
h - alias for 'help'

Usage:
h

完美输出了帮助信息

=> hello
cmd/hello.c,do_hello,line=19:argc = 1
cmd/hello.c,do_hello,line=20:cmdtp->name = hello
cmd/hello.c,do_hello,line=22:argv[0] = hello

完美执行了自定义的hello命令,且它的传参原理和c语言main相同。

使用h别名


=> h
cmd/hello.c,do_hello,line=19:argc = 1
cmd/hello.c,do_hello,line=20:cmdtp->name = h
cmd/hello.c,do_hello,line=22:argv[0] = h

完美执行了自定义的hello命令的别名h,且它的传参原理和c语言main相同。

传递参数a b c,如下所示,参数完美的传递给了hello命令。

=> hello a b c
cmd/hello.c,do_hello,line=19:argc = 4
cmd/hello.c,do_hello,line=20:cmdtp->name = hello
cmd/hello.c,do_hello,line=22:argv[0] = hello
cmd/hello.c,do_hello,line=22:argv[1] = a
cmd/hello.c,do_hello,line=22:argv[2] = b
cmd/hello.c,do_hello,line=22:argv[3] = c

自此自建命令,测试完毕,结果完美。

cmd_tbl_s 结构体

U_BOOT_CMD宏本质上就是在构造一个cmd_tbl_s 结构体。

struct cmd_tbl_s {
	char		*name;		/* Command Name		名字	*/
	int		maxargs;	/* maximum number of arguments 命令最多接收参数个数	*/
	int		repeatable;	/* autorepeat allowed?		按回车键时,是否重复执行,直接按回车执行上一条命令*/
					/* 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
};

下面的可以不用看了。

四 命令交互

位置:Main.c (common)

函数:void main_loop(void)

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifndef CONFIG_SYS_GENERIC_BOARD
	puts("Warning: Your board does not use generic board. Please read\n");
	puts("doc/README.generic-board and take action. Boards not\n");
	puts("upgraded by the late 2014 may break or be removed.\n");
#endif

#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();

	run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
	update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();
}

void cli_loop(void)
{
#ifdef CONFIG_SYS_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#else
	cli_simple_loop();
#endif /*CONFIG_SYS_HUSH_PARSER*/
}

这个函数有句注释/* This point is never reached */,有点不可思议。继续向前分析。

五 命令是存放在哪里的

1.存储段简介

由前面的分析可知,每个命令对应一个cmd_tbl_t结构体。命令不是存放在数组和链表中,而是存放在程序段中,链接时,链接到代码区的用户自定义的段中。定义在u-boot.lds中,在该链接脚本中定义了命令集段的开始地址和结束地址,uboot启动时,该代码段被加载到内存中,然后就可以通过find_cmd函数找到了。

2.宏实现

common/command.h文件中。

#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
				_usage, _help, _comp)			\
		{ #_name, _maxargs, _rep, _cmd, _usage,			\
			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help)	\
	U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
					_usage, _help, NULL)

#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);

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

先看几个简单的,

1.如果CONFIG_AUTO_COMPLETE是未定义的的,则_CMD_COMPLETE是空的,就是不支持自动补全

2. 如果CONFIG_SYS_LONGHELP是未定义的,则_CMD_HELP(x)是空的,就不支持长help功能。

3.这里还没有看到段属性信息,但是这里调用了宏ll_entry_declare,估计段属性就在ll_entry_declare宏里

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)))

这里section括号里面的就是段属性啦。

3.段存储

在uboot文件夹根目录搜索

grep -irn u_boot_list_2_

下面只截取了部分结果。

/*略*/
u-boot.sym:1853:87844640 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_mmc
u-boot.sym:1873:87844940 g     O .u_boot_list   00000008 _u_boot_list_2_env_clbk_2_baudrate
u-boot.sym:1891:87844250 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_ext2load
u-boot.sym:1897:878444f0 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_itest
u-boot.sym:1906:87844678 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_mtest
u-boot.sym:1916:878445d0 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_md
u-boot.sym:1921:87844138 g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_cp
u-boot.sym:1924:878447ac g     O .u_boot_list   0000001c _u_boot_list_2_cmd_2_saveenv
/*略*/

搜索到的信息就是命令存储的段信息啦。

六 查找一个命令

find_cmd位于 commmon/command.c中

cmd_tbl_t *find_cmd(const char *cmd)
{
	cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
	const int len = ll_entry_count(cmd_tbl_t, cmd);
	return find_cmd_tbl(cmd, start, len);
}

看下两个宏ll_entry_start和ll_entry_end

/**
 * ll_entry_start() - Point to first entry of linker-generated array
 * @_type:	Data type of the entry
 * @_list:	Name of the list in which this entry is placed
 *
 * This function returns (_type *) pointer to the very first entry of a
 * linker-generated array placed into subsection of .u_boot_list section
 * specified by _list argument.
 *
 * Since this macro defines an array start symbol, its leftmost index
 * must be 2 and its rightmost index must be 1.
 *
 * Example:
 * struct my_sub_cmd *msc = ll_entry_start(struct my_sub_cmd, cmd_sub);
 */
#define ll_entry_start(_type, _list)					\
({									\
	static char start[0] __aligned(4) __attribute__((unused,	\
		section(".u_boot_list_2_"#_list"_1")));			\
	(_type *)&start;						\
})

/**
 * ll_entry_end() - Point after last entry of linker-generated array
 * @_type:	Data type of the entry
 * @_list:	Name of the list in which this entry is placed
 *		(with underscores instead of dots)
 *
 * This function returns (_type *) pointer after the very last entry of
 * a linker-generated array placed into subsection of .u_boot_list
 * section specified by _list argument.
 *
 * Since this macro defines an array end symbol, its leftmost index
 * must be 2 and its rightmost index must be 3.
 *
 * Example:
 * struct my_sub_cmd *msc = ll_entry_end(struct my_sub_cmd, cmd_sub);
 */
#define ll_entry_end(_type, _list)					\
({									\
	static char end[0] __aligned(4) __attribute__((unused,		\
		section(".u_boot_list_2_"#_list"_3")));			\
	(_type *)&end;							\
})

宏ll_entry_count()用于返回命令数量

/**
 * ll_entry_count() - Return the number of elements in linker-generated array
 * @_type:	Data type of the entry
 * @_list:	Name of the list of which the number of elements is computed
 *
 * This function returns the number of elements of a linker-generated array
 * placed into subsection of .u_boot_list section specified by _list
 * argument. The result is of an unsigned int type.
 *
 * Example:
 * int i;
 * const unsigned int count = ll_entry_count(struct my_sub_cmd, cmd_sub);
 * struct my_sub_cmd *msc = ll_entry_start(struct my_sub_cmd, cmd_sub);
 * for (i = 0; i < count; i++, msc++)
 *         printf("Entry %i, x=%i y=%i\n", i, msc->x, msc->y);
 */
#define ll_entry_count(_type, _list)					\
	({								\
		_type *start = ll_entry_start(_type, _list);		\
		_type *end = ll_entry_end(_type, _list);		\
		unsigned int _ll_result = end - start;			\
		_ll_result;						\
	})

它最终就是在以u_boot_list_2_xxx命名的段中查找命令的,并返回该命令对应的cmd_tbl_t结构体

七 运行一个命令

Cli.c (common)文件

run_command函数用于运行一个命令,它最终调用的就是cmd_tbl_t结构体的函数指针cmd

int        (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
/*
 * Run a command using the selected parser.
 *
 * @param cmd	Command to run
 * @param flag	Execution flags (CMD_FLAG_...)
 * @return 0 on success, or != 0 on error.
 */
int run_command(const char *cmd, int flag)
{
#ifndef CONFIG_SYS_HUSH_PARSER
	/*
	 * cli_run_command can return 0 or 1 for success, so clean up
	 * its result.
	 */
	if (cli_simple_run_command(cmd, flag) == -1)
		return 1;

	return 0;
#else
	int hush_flags = FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP;

	if (flag & CMD_FLAG_ENV)
		hush_flags |= FLAG_CONT_ON_NEWLINE;
	return parse_string_outer(cmd, hush_flags);
#endif
}

<think>我们正在讨论U-Boot的设备模型(DriverModel,简称DM)驱动模块框架。根据用户引用内容,用户已经对DM有一些了解,现在需要完整的框架代码示例。我们将构建一个简单的虚拟设备驱动示例,展示U-BootDM驱动的基本结构。这个示例将包括:1.定义UCLASS(设备类)2.定义U_BOOT_DRIVER(设备驱动)3.定义U_BOOT_DEVICE(设备实例,可选,因为设备树也可以提供设备实例)注意:在U-Boot中,设备可以通过设备树(DTB)描述,也可以通过U_BOOT_DEVICE宏静态定义。这里我们将展示静态定义方式。步骤:1.在include/dm/uclass-id.h中添加自定义的UCLASS枚举值(假设我们创建一个名为UCLASS_MYDEV的类)2.创建驱动文件,例如mydev.c,在其中实现驱动操作函数、定义UCLASS_DRIVER和U_BOOT_DRIVER3.定义设备实例(如果需要静态设备)注意:在实际开发中,通常不需要修改uclass-id.h,因为U-Boot允许动态分配ID,但为了清晰,我们假设需要添加一个新的UCLASS。然而,根据用户之前的引用,UCLASS_DEMO是一个枚举常量。因此,我们首先需要添加一个新的枚举常量。但是,由于用户引用[3]中提到了UCLASS_GPIO,我们可以参考其结构。由于修改核心头文件(uclass-id.h)通常不是推荐的做法(除非确实需要一个新的uclass),因此这里我们将使用一个假设:我们已经为自定义设备类添加了一个枚举值UCLASS_MYDEV。然而,在U-Boot中,通常建议使用现有的UCLASS,或者如果确实需要新的,则按照标准流程添加。这里为了示例,我们假设已经添加。完整示例代码:第一步:在uclass-id.h中添加枚举值(模拟)在enumuclass_id中添加:UCLASS_MYDEV,/*自定义设备类*/第二步:编写驱动文件mydev.c</think>### U-Boot DM驱动模块完整框架代码示例 以下是一个完整的U-Boot设备模型(DM)驱动实现框架,包含UCLASS定义、设备驱动注册、操作函数实现等核心模块。代码基于U-Boot v2023.01架构设计,严格遵循DM规范[^1][^3]。 ```c /* 1. 头文件包含 */ #include <common.h> #include <dm.h> #include <errno.h> #include <log.h> /* 2. 定义设备私有数据结构 */ struct mydev_priv { uint32_t reg_base; // 设备寄存器基地址 uint32_t clock_rate; // 设备时钟频率 char label[20]; // 设备标签 }; /* 3. 定义设备操作函数集 */ static int mydev_enable(struct udevice *dev) { struct mydev_priv *priv = dev_get_priv(dev); log_info("Enabling device at 0x%x\n", priv->reg_base); /* 实际硬件操作代码 */ return 0; } static int mydev_disable(struct udevice *dev) { /* 设备禁用逻辑 */ return 0; } static const struct udevice_id mydev_ids[] = { { .compatible = "vendor,my-device-1.0" }, // 设备树匹配字符串 { } }; /* 4. 定义设备驱动操作函数集 */ static struct dm_mydev_ops mydev_ops = { .enable = mydev_enable, .disable = mydev_disable, }; /* 5. 实现U_BOOT_DRIVER驱动主体 */ U_BOOT_DRIVER(my_device) = { .name = "my_device", // 驱动名称 .id = UCLASS_MYDEV, // 关联的UCLASS ID .of_match = mydev_ids, // 设备树匹配表 .ops = &mydev_ops, // 操作函数集 .probe = mydev_probe, // 设备探测函数 .priv_auto = sizeof(struct mydev_priv), // 自动分配私有数据 }; /* 6. 实现UCLASS驱动 */ UCLASS_DRIVER(mydev) = { .id = UCLASS_MYDEV, // UCLASS唯一标识 .name = "mydev", // UCLASS名称 .flags = DM_UC_FLAG_SEQ_ALIAS, .post_probe = mydev_post_probe, // 设备probe后回调 .pre_remove = mydev_pre_remove, // 设备移除前回调 .per_device_auto_alloc_size = sizeof(struct uclass_priv), // UCLASS私有数据 }; /* 7. 设备探测函数实现 */ static int mydev_probe(struct udevice *dev) { struct mydev_priv *priv = dev_get_priv(dev); const void *fdt = gd->fdt_blob; int node = dev_of_offset(dev); /* 解析设备树参数 */ priv->reg_base = dev_read_addr(dev); priv->clock_rate = dev_read_u32_default(dev, "clock-frequency", 1000000); /* 初始化硬件 */ writel(0x01, priv->reg_base + CTRL_REG); log_info("Device probed at 0x%x\n", priv->reg_base); return 0; } /* 8. UCLASS回调函数 */ static int mydev_post_probe(struct udevice *dev) { /* 设备初始化后处理 */ return 0; } ``` #### 关键组件说明: 1. **设备私有数据** (`struct mydev_priv`) - 存储设备特定状态信息 - 通过`priv_auto`自动分配内存 2. **操作函数集** (`struct dm_mydev_ops`) - 实现标准设备操作接口 - 确保驱动通用性[^3] 3. **设备树匹配** (`mydev_ids`) ```dts // 设备树节点示例 my_device: mydev@0x10000000 { compatible = "vendor,my-device-1.0"; reg = <0x10000000 0x1000>; clock-frequency = <50000000>; }; ``` 4. **驱动注册宏** (`U_BOOT_DRIVER`) - 声明驱动名称、ID和回调函数 - 自动注册到DM核心[^1] 5. **UCLASS注册宏** (`UCLASS_DRIVER`) - 管理同类设备的公共行为 - 提供生命周期回调 #### 编译配置要求: ```makefile # Kconfig配置 config DM_MYDEV bool "My Custom Device Support" depends on DM default y help Support for Vendor MyDev hardware # Makefile添加 obj-$(CONFIG_DM_MYDEV) += mydev.o ``` > **最佳实践**: > 1. 使用`dev_read_*()`系列函数访问设备树参数 > 2. 在`probe()`中完成硬件初始化而非构造函数 > 3. 通过`log_*()`输出调试信息替代`printf` > 4. 私有数据命名遵循`<driver>_priv`规范[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千册

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值