[uboot] uboot流程系列:
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)
[project X] tiny210(s5pv210)从存储设备加载代码到DDR
[uboot] (第一章)uboot流程——概述
[uboot] (第二章)uboot流程——uboot-spl编译流程
[uboot] (第三章)uboot流程——uboot-spl代码流程
[uboot] (第四章)uboot流程——uboot编译流程
[uboot] (第五章)uboot流程——uboot启动流程
[uboot] (番外篇)global_data介绍
[uboot] (番外篇)uboot relocation介绍
[uboot] (番外篇)uboot之fdt介绍
建议先看《[uboot] (番外篇)uboot之fdt介绍》,了解一下uboot的fdt的功能,在驱动模型中会使用到。
==============================================================================================================
uboot引入了驱动模型(driver model),这种驱动模型为驱动的定义和访问接口提供了统一的方法。提高了驱动之间的兼容性以及访问的标准型。
uboot驱动模型和kernel中的设备驱动模型类似,但是又有所区别。
在后续我们将驱动模型(driver model)简称为DM,其实在uboot里面也是这样简称的。
具体细节建议参考./doc/driver-model/README.txt
(1)配置CONFIG_DM
在configs/tiny210_defconfig中定义了如下:
CONFIG_DM=y
(2)使能相应的uclass driver的config。
DM和uclass是息息相关的,如果我们希望在某个模块引入DM,那么就需要使用相应模块的uclass driver来代替旧版的通用driver。
关于uclass我们会在后续继续说明。
以serial为例,为了在serial中引入DM,我在configs/tiny210_defconfig0中打开了CONFIG_DM_SERIAL宏,如下
CONFIG_DM_SERIAL=y
看driver/serial/Makefile
ifdef CONFIG_DM_SERIAL
obj-y += serial-uclass.o ## 引入dm的serial core驱动
else
obj-y += serial.o ## 通用的serial core驱动
endif
## 可以发现编译出来的serial core的驱动代码是不一样的。
(3)对应设备驱动也要引入dm的功能
其设备驱动主要是实现和底层交互,为uclass层提供接口。后续再具体说明。
__后续都以serial-uclass进行说明
uboot的DM主要有四个组成部分
Uclass - a group of devices which operate in the same way. A uclass provides
a way of accessing individual devices within the group, but always
using the same interface. For example a GPIO uclass provides
operations for get/set value. An I2C uclass may have 10 I2C ports,
4 with one driver, and 6 with another.
uclass,使用相同方式的操作集的device的组。相当于是一种抽象。uclass为那些使用相同接口的设备提供了统一的接口。
例如,GPIO uclass提供了get/set接口。再例如,一个I2C uclass下可能有10个I2C端口,4个使用一个驱动,另外6个使用另外一个驱动。
结合上图来看:
这里先简单介绍一下:uclass和udevice都是动态生成的。在解析fdt中的设备的时候,会动态生成udevice。
然后找到udevice对应的driver,通过driver中的uclass id得到uclass_driver id。从uclass链表中查找对应的uclass是否已经生成,没有生成的话则动态生成uclass。
typedef struct global_data {
#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
// DM中的根设备,也是uboot中第一个创建的udevice,也就对应了dts里的根节点。
struct udevice *dm_root_f; /* Pre-relocation root instance */
// 在relocation之前DM中的根设备
struct list_head uclass_root; /* Head of core tree */
// uclass链表,所有被udevice匹配的uclass都会被挂载到这个链表上
#endif
} gd_t;
后续以数据结构、如何定义、存放位置、如何获取四个部分进行说明进行说明
每一种uclass都有自己对应的ID号。定义于其uclass_driver中。其附属的udevice的driver中的uclass id必须与其一致。
所有uclass id定义于include/dm/uclass-id.h中
列出部分id如下
enum uclass_id {
/* These are used internally by driver model */
UCLASS_ROOT = 0,
UCLASS_DEMO,
UCLASS_CLK, /* Clock source, e.g. used by peripherals */
UCLASS_PINCTRL, /* Pinctrl (pin muxing/configuration) device */
UCLASS_SERIAL, /* Serial UART */
}
struct uclass {
void *priv; // uclass的私有数据指针
struct uclass_driver *uc_drv; // 对应的uclass driver
struct list_head dev_head; // 链表头,连接所属的所有udevice
struct list_head sibling_node; // 链表节点,用于把uclass连接到uclass_root链表上
};
(2)如何定义
uclass是uboot自动生成。并且不是所有uclass都会生成,有对应uclass driver并且有被udevice匹配到的uclass才会生成。
具体参考后面的uboot DM初始化一节。或者参考uclass_add实现。
(3)存放位置
所有生成的uclass都会被挂载gd->uclass_root链表上。
(4)如何获取、API
直接遍历链表gd->uclass_root链表并且根据uclass id来获取到相应的uclass。
具体uclass_get-》uclass_find实现了这个功能。
有如下API:
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 从gd->uclass_root链表获取对应的uclass
struct uclass_driver {
const char *name; // 该uclass_driver的命令
enum uclass_id id; // 对应的uclass id
/* 以下函数指针主要是调用时机的区别 */
int (*post_bind)(struct udevice *dev); // 在udevice被绑定到该uclass之后调用
int (*pre_unbind)(struct udevice *dev); // 在udevice被解绑出该uclass之前调用
int (*pre_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之前调用
int (*post_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之后调用
int (*pre_remove)(struct udevice *dev);// 在该uclass的一个udevice进行remove之前调用
int (*child_post_bind)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备被绑定到该udevice之后调用
int (*child_pre_probe)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备进行probe之前调用
int (*init)(struct uclass *class); // 安装该uclass的时候调用
int (*destroy)(struct uclass *class); // 销毁该uclass的时候调用
int priv_auto_alloc_size; // 需要为对应的uclass分配多少私有数据
int per_device_auto_alloc_size; //
int per_device_platdata_auto_alloc_size; //
int per_child_auto_alloc_size; //
int per_child_platdata_auto_alloc_size; //
const void *ops; //操作集合
uint32_t flags; // 标识为
};
UCLASS_DRIVER(serial) = {
.id = UCLASS_SERIAL,
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};
UCLASS_DRIVER实现如下:
#define UCLASS_DRIVER(__name) \
ll_entry_declare(struct uclass_driver, __name, uclass)
#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我们在《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》已经介绍过了
最终得到一个如下结构体
struct uclass_driver _u_boot_list_2_uclass_2_serial = {
.id = UCLASS_SERIAL, // 设置对应的uclass id
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
}
并且存放在.u_boot_list_2_uclass_2_serial段中。
.u_boot_list_2_uclass_1
0x23e368e0 0x0 drivers/built-in.o
.u_boot_list_2_uclass_2_gpio
0x23e368e0 0x48 drivers/gpio/built-in.o
0x23e368e0 _u_boot_list_2_uclass_2_gpio // gpio uclass driver的符号
.u_boot_list_2_uclass_2_root
0x23e36928 0x48 drivers/built-in.o
0x23e36928 _u_boot_list_2_uclass_2_root // root uclass drvier的符号
.u_boot_list_2_uclass_2_serial
0x23e36970 0x48 drivers/serial/built-in.o
0x23e36970 _u_boot_list_2_uclass_2_serial // serial uclass driver的符号
.u_boot_list_2_uclass_2_simple_bus
0x23e369b8 0x48 drivers/built-in.o
0x23e369b8 _u_boot_list_2_uclass_2_simple_bus
.u_boot_list_2_uclass_3
0x23e36a00 0x0 drivers/built-in.o
0x23e36a00 . = ALIGN (0x4)
最终,所有uclass driver结构体以列表的形式被放在.u_boot_list_2_uclass_1和.u_boot_list_2_uclass_3的区间中。
这个列表简称uclass_driver table。
struct uclass_driver *uclass =
ll_entry_start(struct uclass_driver, uclass);
// 会根据.u_boot_list_2_uclass_1的段地址来得到uclass_driver table的地址
const int n_ents = ll_entry_count(struct uclass_driver, uclass);
// 获得uclass_driver table的长度
接着通过遍历这个uclass_driver table,得到相应的uclass_driver。
有如下API
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 从uclass_driver table中获取uclass id为id的uclass_driver。
struct udevice {
const struct driver *driver; // 该udevice对应的driver
const char *name; // 设备名
void *platdata; // 该udevice的平台数据
void *parent_platdata; // 提供给父设备使用的平台数据
void *uclass_platdata; // 提供给所属uclass使用的平台数据
int of_offset; // 该udevice的dtb节点偏移,代表了dtb里面的这个节点node
ulong driver_data; // 驱动数据
struct udevice *parent; // 父设备
void *priv; // 私有数据的指针
struct uclass *uclass; // 所属uclass
void *uclass_priv; // 提供给所属uclass使用的私有数据指针
void *parent_priv; // 提供给其父设备使用的私有数据指针
struct list_head uclass_node; // 用于连接到其所属uclass的链表上
struct list_head child_head; // 链表头,连接其子设备
struct list_head sibling_node; // 用于连接到其父设备的链表上
uint32_t flags; // 标识
int req_seq;
int seq;
#ifdef CONFIG_DEVRES
struct list_head devres_head;
#endif
};
(2)如何定义
在dtb存在的情况下,由uboot解析dtb后动态生成,后续在“uboot DM的初始化”一节中具体说明。
(3)存放位置
(4)如何获取、API
#define uclass_foreach_dev(pos, uc) \
list_for_each_entry(pos, &uc->dev_head, uclass_node)
#define uclass_foreach_dev_safe(pos, next, uc) \
list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node)
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // 通过索引从uclass中获取udevice
int uclass_get_device_by_name(enum uclass_id id, const char *name, // 通过设备名从uclass中获取udevice
struct udevice **devp);
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp);
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp);
int uclass_first_device(enum uclass_id id, struct udevice **devp);
int uclass_first_device_err(enum uclass_id id, struct udevice **devp);
int uclass_next_device(struct udevice **devp);
int uclass_resolve_seq(struct udevice *dev);
和uclass_driver方式是相似的。
struct driver {
char *name; // 驱动名
enum uclass_id id; // 对应的uclass id
const struct udevice_id *of_match; // compatible字符串的匹配表,用于和device tree里面的设备节点匹配
int (*bind)(struct udevice *dev); // 用于绑定目标设备到该driver中
int (*probe)(struct udevice *dev); // 用于probe目标设备,激活
int (*remove)(struct udevice *dev); // 用于remove目标设备。禁用
int (*unbind)(struct udevice *dev); // 用于解绑目标设备到该driver中
int (*ofdata_to_platdata)(struct udevice *dev); // 在probe之前,解析对应udevice的dts节点,转化成udevice的平台数据
int (*child_post_bind)(struct udevice *dev); // 如果目标设备的一个子设备被绑定之后,调用
int (*child_pre_probe)(struct udevice *dev); // 在目标设备的一个子设备被probe之前,调用
int (*child_post_remove)(struct udevice *dev); // 在目标设备的一个子设备被remove之后,调用
int priv_auto_alloc_size; //需要分配多少空间作为其udevice的私有数据
int platdata_auto_alloc_size; //需要分配多少空间作为其udevice的平台数据
int per_child_auto_alloc_size; // 对于目标设备的每个子设备需要分配多少空间作为父设备的私有数据
int per_child_platdata_auto_alloc_size; // 对于目标设备的每个子设备需要分配多少空间作为父设备的平台数据
const void *ops; /* driver-specific operations */ // 操作集合的指针,提供给uclass使用,没有规定操作集的格式,由具体uclass决定
uint32_t flags; // 一些标志位
};
U_BOOT_DRIVER(serial_s5p) = {
.name = "serial_s5p",
.id = UCLASS_SERIAL,
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
U_BOOT_DRIVER实现如下:
#define U_BOOT_DRIVER(__name) \
ll_entry_declare(struct driver, __name, driver)
#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我们在《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》已经介绍过了
最终得到如下一个结构体
struct driver _u_boot_list_2_driver_2_serial_s5p= {
.name = "serial_s5p",
.id = UCLASS_SERIAL,
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
并且存放在.u_boot_list_2_driver_2_serial_s5p段中
.u_boot_list_2_driver_1
0x23e36754 0x0 drivers/built-in.o
.u_boot_list_2_driver_2_gpio_exynos
0x23e36754 0x44 drivers/gpio/built-in.o
0x23e36754 _u_boot_list_2_driver_2_gpio_exynos
.u_boot_list_2_driver_2_root_driver
0x23e36798 0x44 drivers/built-in.o
0x23e36798 _u_boot_list_2_driver_2_root_driver
.u_boot_list_2_driver_2_serial_s5p
0x23e367dc 0x44 drivers/serial/built-in.o
0x23e367dc _u_boot_list_2_driver_2_serial_s5p
.u_boot_list_2_driver_2_simple_bus_drv
0x23e36820 0x44 drivers/built-in.o
0x23e36820 _u_boot_list_2_driver_2_simple_bus_drv
.u_boot_list_2_driver_3
0x23e36864 0x0 drivers/built-in.o
最终,所有driver结构体以列表的形式被放在.u_boot_list_2_driver_1和.u_boot_list_2_driver_3的区间中。
这个列表简称driver table。
struct driver *drv =
ll_entry_start(struct driver, driver);
// 会根据.u_boot_list_2_driver_1的段地址来得到uclass_driver table的地址
const int n_ents = ll_entry_count(struct driver, driver);
// 获得driver table的长度
接着通过遍历这个driver table,得到相应的driver。
struct driver *lists_driver_lookup_name(const char *name)
// 从driver table中获取名字为name的driver。
先看一下前面一节理解一下。
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 从gd->uclass_root链表获取对应的uclass
struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 从uclass_driver table中获取uclass id为id的uclass_driver。
#define uclass_foreach_dev(pos, uc) \
list_for_each_entry(pos, &uc->dev_head, uclass_node)
#define uclass_foreach_dev_safe(pos, next, uc) \
list_for_each_entry_safe(pos, next, &uc->dev_head, uclass_node)
int device_bind(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata, int of_offset,
struct udevice **devp)
// 初始化一个udevice,并将其与其uclass、driver绑定。
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
const struct driver_info *info, struct udevice **devp)
// 通过name获取driver并且调用device_bind对udevice初始化,并将其与其uclass、driver绑定。
int uclass_bind_device(struct udevice *dev)
// 绑定udevice到其对应的uclass的设备链表中
{
uc = dev->uclass;
list_add_tail(&dev->uclass_node, &uc->dev_head);
}
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp); // 通过索引从uclass中获取udevice,注意,在获取的过程中就会对设备进行probe
int uclass_get_device_by_name(enum uclass_id id, const char *name, // 通过设备名从uclass中获取udevice
struct udevice **devp);
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp);
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp);
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp);
int uclass_first_device(enum uclass_id id, struct udevice **devp);
int uclass_first_device_err(enum uclass_id id, struct udevice **devp);
int uclass_next_device(struct udevice **devp);
int uclass_resolve_seq(struct udevice *dev);
struct driver *lists_driver_lookup_name(const char *name)
// 从driver table中获取名字为name的driver。
uboot中可以通过两种方法来添加设备
注意:这里只是设备的定义,最终还是会被uboot解析成udevice结构体的。
(1)通过U_BOOT_DEVICE宏来进行定义或者直接定义struct driver_info结构体
(2)U_BOOT_DEVICE宏以ns16550_serial为例
U_BOOT_DEVICE(overo_uart) = {
"ns16550_serial",
&overo_serial
};
U_BOOT_DEVICE实现如下:
和上述的U_BOOT_DRIVER类似,这里不详细说明了
#define U_BOOT_DEVICE(__name) \
ll_entry_declare(struct driver_info, __name, driver_info)
/* Declare a list of devices. The argument is a driver_info[] array */
#define U_BOOT_DEVICES(__name) \
ll_entry_declare_list(struct driver_info, __name, driver_info)
(3)直接定义struct driver_info结构体,以根设备为例
uboot会创建一个根设备root,作为所有设备的祖设备
root的定义如下:
static const struct driver_info root_info = {
.name = "root_driver",
};
在对应的dts文件中添加相应的设备节点和信息,以tiny210的serial为例:
arch/arm/dts/s5pv210-tiny210.dts
/dts-v1/;
#include "skeleton.dtsi"
/{
aliases {
console = "/serial@e2900000";
};
serial@e2900000 {
compatible = "samsung,exynos4210-uart";
reg = <0xe2900000 0x100>;
interrupts = <0 51 0>;
id = <0>;
};
};
dts的内容这里不多说了。
关于下面可能使用到的一些FDT的API可以参考一下《[uboot] (番外篇)uboot之fdt介绍》。
关于下面可能使用到一些DM的API可以参考一下上述第四节。
DM的初始化
DM中udevice和uclass的解析
dm初始化的接口在dm_init_and_scan中。
可以发现在uboot relocate之前的initf_dm和之后的initr_dm都调用了这个函数。
static int initf_dm(void)
{
#if defined(CONFIG_DM) && defined(CONFIG_SYS_MALLOC_F_LEN)
int ret;
ret = dm_init_and_scan(true); // 调用dm_init_and_scan对DM进行初始化和设备的解析
if (ret)
return ret;
#endif
return 0;
}
#ifdef CONFIG_DM
static int initr_dm(void)
{
int ret;
/* Save the pre-reloc driver model and start a new one */
gd->dm_root_f = gd->dm_root; // 存储relocate之前的根设备
gd->dm_root = NULL;
ret = dm_init_and_scan(false); // 调用dm_init_and_scan对DM进行初始化和设备的解析
if (ret)
return ret;
return 0;
}
#endif
主要区别在于参数。
首先说明一下dts节点中的“u-boot,dm-pre-reloc”属性,当设置了这个属性时,则表示这个设备在relocate之前就需要使用。
当dm_init_and_scan的参数为true时,只会对带有“u-boot,dm-pre-reloc”属性的节点进行解析。而当参数为false的时候,则会对所有节点都进行解析。
由于“u-boot,dm-pre-reloc”的情况比较少,所以这里只学习参数为false的情况。也就是initr_dm里面的dm_init_and_scan(false);。
driver/core/root.c
int dm_init_and_scan(bool pre_reloc_only)
{
int ret;
ret = dm_init(); // DM的初始化
if (ret) {
debug("dm_init() failed: %d\n", ret);
return ret;
}
ret = dm_scan_platdata(pre_reloc_only); // 从平台设备中解析udevice和uclass
if (ret) {
debug("dm_scan_platdata() failed: %d\n", ret);
return ret;
}
if (CONFIG_IS_ENABLED(OF_CONTROL)) {
ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); // 从dtb中解析udevice和uclass
if (ret) {
debug("dm_scan_fdt() failed: %d\n", ret);
return ret;
}
}
ret = dm_scan_other(pre_reloc_only);
if (ret)
return ret;
return 0;
}
对应代码如下:
driver/core/root.c
#define DM_ROOT_NON_CONST (((gd_t *)gd)->dm_root) // 宏定义根设备指针gd->dm_root
#define DM_UCLASS_ROOT_NON_CONST (((gd_t *)gd)->uclass_root) // 宏定义gd->uclass_root,uclass的链表
int dm_init(void)
{
int ret;
if (gd->dm_root) {
// 根设备已经存在,说明DM已经初始化过了
dm_warn("Virtual root driver already exists!\n");
return -EINVAL;
}
INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);
// 初始化uclass链表
ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
// DM_ROOT_NON_CONST是指根设备udevice,root_info是表示根设备的设备信息
// device_bind_by_name会查找和设备信息匹配的driver,然后创建对应的udevice和uclass并进行绑定,最后放在DM_ROOT_NON_CONST中。
// device_bind_by_name后续我们会进行说明,这里我们暂时只需要了解root根设备的udevice以及对应的uclass都已经创建完成。
if (ret)
return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
DM_ROOT_NON_CONST->of_offset = 0;
#endif
ret = device_probe(DM_ROOT_NON_CONST);
// 对根设备执行probe操作,
// device_probe后续再进行说明
if (ret)
return ret;
return 0;
}
这里就完成的DM的初始化了
(1)创建根设备root的udevice,存放在gd->dm_root中。
(2)初始化uclass链表gd->uclass_root
跳过。
关于fdt以及一些对应API请参考《[uboot] (番外篇)uboot之fdt介绍》。
对应代码如下(后续我们忽略pre_reloc_only=true的情况):
driver/core/root.c
int dm_scan_fdt(const void *blob, bool pre_reloc_only)
// 此时传进来的参数blob=gd->fdt_blob, pre_reloc_only=0
{
return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
// 直接调用dm_scan_fdt_node
}
int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
bool pre_reloc_only)
// 此时传进来的参数
// parent=gd->dm_root,表示以root设备作为父设备开始解析
// blob=gd->fdt_blob,指定了对应的dtb
// offset=0,从偏移0的节点开始扫描
// pre_reloc_only=0,不只是解析relotion之前的设备
{
int ret = 0, err;
/* 以下步骤相当于是遍历每一个dts节点并且调用lists_bind_fdt对其进行解析 */
for (offset = fdt_first_subnode(blob, offset);
// 获得blob设备树的offset偏移下的节点的第一个子节点
offset > 0;
offset = fdt_next_subnode(blob, offset)) {
// 循环查找下一个子节点
if (!fdtdec_get_is_enabled(blob, offset)) {
// 判断节点状态是否是disable,如果是的话直接忽略
dm_dbg(" - ignoring disabled device\n");
continue;
}
err = lists_bind_fdt(parent, blob, offset, NULL);
// 解析绑定这个节点,dm_scan_fdt的核心,下面具体分析
if (err && !ret) {
ret = err;
debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL),
ret);
}
}
return ret;
}
lists_bind_fdt是从dtb中解析udevice和uclass的核心。
其具体实现如下:
driver/core/lists.c
int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
struct udevice **devp)
// parent指定了父设备,通过blob和offset可以获得对应的设备的dts节点,对应udevice结构通过devp返回
{
struct driver *driver = ll_entry_start(struct driver, driver);
// 获取driver table地址
const int n_ents = ll_entry_count(struct driver, driver);
// 获取driver table长度
const struct udevice_id *id;
struct driver *entry;
struct udevice *dev;
bool found = false;
const char *name;
int result = 0;
int ret = 0;
dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL));
// 打印当前解析的节点的名称
if (devp)
*devp = NULL;
for (entry = driver; entry != driver + n_ents; entry++) {
// 遍历driver table中的所有driver,具体参考三、4一节
ret = driver_check_compatible(blob, offset, entry->of_match,
&id);
// 判断driver中的compatibile字段和dts节点是否匹配
name = fdt_get_name(blob, offset, NULL);
// 获取节点名称
if (ret == -ENOENT) {
continue;
} else if (ret == -ENODEV) {
dm_dbg("Device '%s' has no compatible string\n", name);
break;
} else if (ret) {
dm_warn("Device tree error at offset %d\n", offset);
result = ret;
break;
}
dm_dbg(" - found match at '%s'\n", entry->name);
ret = device_bind(parent, entry, name, NULL, offset, &dev);
// 找到对应的driver,调用device_bind进行绑定,会在这个函数中创建对应udevice和uclass并切进行绑定,后面继续说明
if (ret) {
dm_warn("Error binding driver '%s': %d\n", entry->name,
ret);
return ret;
} else {
dev->driver_data = id->data;
found = true;
if (devp)
*devp = dev;
// 将udevice设置到devp指向的地方中,进行返回
}
break;
}
if (!found && !result && ret != -ENODEV) {
dm_dbg("No match for node '%s'\n",
fdt_get_name(blob, offset, NULL));
}
return result;
}
在device_bind中实现了udevice和uclass的创建和绑定以及一些初始化操作,这里专门学习一下device_bind。
device_bind的实现如下(去除部分代码)
driver/core/device.c
int device_bind(struct udevice *parent, const struct driver *drv,
const char *name, void *platdata, int of_offset,
struct udevice **devp)
// parent:父设备
// drv:设备对应的driver
// name:设备名称
// platdata:设备的平台数据指针
// of_offset:在dtb中的偏移,即代表了其dts节点
// devp:所创建的udevice的指针,用于返回
{
struct udevice *dev;
struct uclass *uc;
int size, ret = 0;
ret = uclass_get(drv->id, &uc);
// 获取driver id对应的uclass,如果uclass原先并不存在,那么会在这里创建uclass并其uclass_driver进行绑定
dev = calloc(1, sizeof(struct udevice));
// 分配一个udevice
dev->platdata = platdata; // 设置udevice的平台数据指针
dev->name = name; // 设置udevice的name
dev->of_offset = of_offset; // 设置udevice的dts节点偏移
dev->parent = parent; // 设置udevice的父设备
dev->driver = drv; // 设置udevice的对应的driver,相当于driver和udevice的绑定
dev->uclass = uc; // 设置udevice的所属uclass
dev->seq = -1;
dev->req_seq = -1;
if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
/*
* Some devices, such as a SPI bus, I2C bus and serial ports
* are numbered using aliases.
*
* This is just a 'requested' sequence, and will be
* resolved (and ->seq updated) when the device is probed.
*/
if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
if (uc->uc_drv->name && of_offset != -1) {
fdtdec_get_alias_seq(gd->fdt_blob,
uc->uc_drv->name, of_offset,
&dev->req_seq);
}
// 设置udevice的alias请求序号
}
}
if (!dev->platdata && drv->platdata_auto_alloc_size) {
dev->flags |= DM_FLAG_ALLOC_PDATA;
dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
// 为udevice分配平台数据的空间,由driver中的platdata_auto_alloc_size决定
}
size = uc->uc_drv->per_device_platdata_auto_alloc_size;
if (size) {
dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
dev->uclass_platdata = calloc(1, size);
// 为udevice分配给其所属uclass使用的平台数据的空间,由所属uclass的driver中的per_device_platdata_auto_alloc_size决定
}
/* put dev into parent's successor list */
if (parent)
list_add_tail(&dev->sibling_node, &parent->child_head);
// 添加到父设备的子设备链表中
ret = uclass_bind_device(dev);
// uclass和udevice进行绑定,主要是实现了将udevice链接到uclass的设备链表中
/* if we fail to bind we remove device from successors and free it */
if (drv->bind) {
ret = drv->bind(dev);
// 执行udevice对应driver的bind函数
}
if (parent && parent->driver->child_post_bind) {
ret = parent->driver->child_post_bind(dev);
// 执行父设备的driver的child_post_bind函数
}
if (uc->uc_drv->post_bind) {
ret = uc->uc_drv->post_bind(dev);
if (ret)
goto fail_uclass_post_bind;
// 执行所属uclass的post_bind函数
}
if (devp)
*devp = dev;
// 将udevice进行返回
dev->flags |= DM_FLAG_BOUND;
// 设置已经绑定的标志
// 后续可以通过dev->flags & DM_FLAG_ACTIVATED或者device_active宏来判断设备是否已经被激活
return 0;
上述就完成了dtb的解析,udevice和uclass的创建,以及各个组成部分的绑定关系。
注意,这里只是绑定,即调用了driver的bind函数,但是设备还没有真正激活,也就是还没有执行设备的probe函数。
经过前面的DM初始化以及设备解析之后,我们只是建立了udevice和uclass之间的绑定关系。但是此时udevice还没有被probe,其对应设备还没有被激活。
激活一个设备主要是通过device_probe函数,所以在介绍DM的工作流程前,先说明device_probe函数。
driver/core/device.c
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int size = 0;
int ret;
int seq;
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
// 表示这个设备已经被激活了
drv = dev->driver;
assert(drv);
// 获取这个设备对应的driver
/* Allocate private data if requested and not reentered */
if (drv->priv_auto_alloc_size && !dev->priv) {
dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
// 为设备分配私有数据
}
/* Allocate private data if requested and not reentered */
size = dev->uclass->uc_drv->per_device_auto_alloc_size;
if (size && !dev->uclass_priv) {
dev->uclass_priv = calloc(1, size);
// 为设备所属uclass分配私有数据
}
// 这里过滤父设备的probe
seq = uclass_resolve_seq(dev);
if (seq < 0) {
ret = seq;
goto fail;
}
dev->seq = seq;
dev->flags |= DM_FLAG_ACTIVATED;
// 设置udevice的激活标志
ret = uclass_pre_probe_device(dev);
// uclass在probe device之前的一些函数的调用
if (drv->ofdata_to_platdata && dev->of_offset >= 0) {
ret = drv->ofdata_to_platdata(dev);
// 调用driver中的ofdata_to_platdata将dts信息转化为设备的平台数据
}
if (drv->probe) {
ret = drv->probe(dev);
// 调用driver的probe函数,到这里设备才真正激活了
}
ret = uclass_post_probe_device(dev);
return ret;
}
主要工作归纳如下:
通过uclass来获取一个udevice并且进行probe有如下接口
driver/core/uclass.c
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp) //通过索引从uclass的设备链表中获取udevice,并且进行probe
int uclass_get_device_by_name(enum uclass_id id, const char *name,
struct udevice **devp) //通过设备名从uclass的设备链表中获取udevice,并且进行probe
int uclass_get_device_by_seq(enum uclass_id id, int seq, struct udevice **devp) //通过序号从uclass的设备链表中获取udevice,并且进行probe
int uclass_get_device_by_of_offset(enum uclass_id id, int node,
struct udevice **devp) //通过dts节点的偏移从uclass的设备链表中获取udevice,并且进行probe
int uclass_get_device_by_phandle(enum uclass_id id, struct udevice *parent,
const char *name, struct udevice **devp) //通过设备的“phandle”属性从uclass的设备链表中获取udevice,并且进行probe
int uclass_first_device(enum uclass_id id, struct udevice **devp) //从uclass的设备链表中获取第一个udevice,并且进行probe
int uclass_next_device(struct udevice **devp) //从uclass的设备链表中获取下一个udevice,并且进行probe
这些接口主要是获取设备的方法上有所区别,但是probe设备的方法都是一样的,都是通过调用uclass_get_device_tail->device_probe来probe设备的。
以uclass_get_device为例
int uclass_get_device(enum uclass_id id, int index, struct udevice **devp)
{
struct udevice *dev;
int ret;
*devp = NULL;
ret = uclass_find_device(id, index, &dev); //通过索引从uclass的设备链表中获取对应的udevice
return uclass_get_device_tail(dev, ret, devp); // 调用uclass_get_device_tail进行设备的get,最终会调用device_probe来对设备进行probe
}
int uclass_get_device_tail(struct udevice *dev, int ret,
struct udevice **devp)
{
ret = device_probe(dev);
// 调用device_probe对设备进行probe,这个函数在前面说明过了
if (ret)
return ret;
*devp = dev;
return 0;
}
serial-uclass较为简单,我们以serial-uclass为例
UCLASS_DRIVER(serial) = {
.id = UCLASS_SERIAL, //注意这里的uclass id
.name = "serial",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.post_probe = serial_post_probe,
.pre_remove = serial_pre_remove,
.per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};
< 2 > 定义s5pv210的serial的dts节点
serial@e2900000 {
compatible = "samsung,exynos4210-uart"; //注意这里的compatible
reg = <0xe2900000 0x100>;
interrupts = <0 51 0>;
id = <0>;
};
< 3 > 定义设备驱动
U_BOOT_DRIVER(serial_s5p) = {
.name = "serial_s5p",
.id = UCLASS_SERIAL, //注意这里的uclass id
.of_match = s5p_serial_ids,
.ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
.probe = s5p_serial_probe,
.ops = &s5p_serial_ops,
.flags = DM_FLAG_PRE_RELOC,
};
static const struct udevice_id s5p_serial_ids[] = {
{ .compatible = "samsung,exynos4210-uart" }, //注意这里的compatible
{ }
};
(1)udevice和对应uclass的创建
在DM初始化的过程中uboot自己创建对应的udevice和uclass。
具体参考“六、uboot DM的初始化”
(2)udevice和对应uclass的绑定
在DM初始化的过程中uboot自己实现将udevice绑定到对应的uclass中。
具体参考“六、uboot DM的初始化”
(3)对应udevice的probe
由模块自己实现。例如serial则需要在serial的初始化过程中,选择需要的udevice进行probe。
serial-uclass只是操作作为console的serial,并不具有通用性,这里简单的了解下。
代码如下,过滤掉无关代码
driver/serial/serial-uclass.c
int serial_init(void)
{
serial_find_console_or_panic(); // 调用serial_find_console_or_panic进行作为console的serial的初始化
gd->flags |= GD_FLG_SERIAL_READY;
return 0;
}
static void serial_find_console_or_panic(void)
{
const void *blob = gd->fdt_blob;
struct udevice *dev;
int node;
if (CONFIG_IS_ENABLED(OF_CONTROL) && blob) {
/* Check for a chosen console */
// 这里过滤掉获取指定的serial的dts节点的代码
if (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node,
&dev)) {
// 这里调用uclass_get_device_by_of_offset,通过dts节点的偏移从uclass的设备链表中获取udevice,并且进行probe。
// 注意,是在这里完成设备的probe的!!!
gd->cur_serial_dev = dev;
// 将udevice存储在gd->cur_serial_dev,后续uclass中可以直接通过gd->cur_serial_dev获取到对应的设备并且进行操作
// 但是注意,这种并不是通用做法!!!
return;
}
}
}
void serial_putc(char ch)
{
if (gd->cur_serial_dev)
_serial_putc(gd->cur_serial_dev, ch);// 将console对应的serial的udevice作为参数传入
}
static void _serial_putc(struct udevice *dev, char ch)
{
struct dm_serial_ops *ops = serial_get_ops(dev);// 获取设备对应的driver函数的ops操作集
int err;
do {
err = ops->putc(dev, ch); // 以udevice为参数,调用ops中对应的操作函数
} while (err == -EAGAIN);
}
到此整个流程简单介绍到这。
这里几乎都是纸上谈兵,后续会来一篇gpio-uclass的使用实战。