这一篇我们学习uboot中的驱动模型的初始化,在uboot中,驱动模型被称为Driver Model,简称DM。这种驱动模型为uboot中的各类驱动提供了统一的接口。
1. 数据结构及概念
DM模型主要依赖于下面四种数据结构:
- udevice,具有硬件设备的抽象, 和driver实例相关
- driver,特定udevice的硬件驱动,包含了驱动的绑定、初始化、probe和卸载等函数。使用
U_BOOT_DRIVER
来注册。需要声明所属的uclass。 - uclass,维护一类驱动,例如显示部分有lcdif驱动,display controller驱动,他们都在驱动中声明自己属于
UCLASS_VIDEO
类。又例如所有的MIPI驱动都属于UCLASS_DSI_HOST
类。 - uclass_driver,在我们写的驱动中,我们会使用
UCLASS_DRIVER
来注册一个uclass_driver
对象。这个uclass驱动维护了这一类硬件驱动的接口,为上层的调用提供了统一的接口。
我这里以GPIO为例,绘制了这四种数据结构的依赖关系。首先是DM框架中所定义的关于GPIO的UCLASS DRIVER,这个driver只有三个统一的接口,gpio_post_probe
,gpio_post_bind
和gpio_pre_remove
。在以spi中的gpio操作为例,gpio_request->gpio_to_device
拿着传入的gpio number遍历UCLASS_GPIO
下所有GPIO udevice,获取GPIO的描述符(包含udevice信息和GPIO的偏移量),此时已经找到了这个GPIO所对应的udevice,也就找到了驱动函数gpio_mxc,然后设置输入输出方向函数会调用这个驱动的相应函数来实现指定的输入输出方向。
2. DM模型的初始化:dm_init
在全局数据global_data定义了DM根节点,dm初始化的接口在dm_init_and_scan(bool pre_reloc_only)
中,初始化流程主要有两次,入口函数分别是static int initf_dm(void)
和static int initr_dm(void)
。第一次是在重定位之前,调用的是initf_dm
函数。第二次是在重定位之后,调用的是initr_dm
函数。
typedef struct global_data {
// dts中的根节点,第一个创建的udevice
struct udevice *dm_root;
// relocation之前的根设备
struct udevice *dm_root_f;
// uclass的链表, 挂的是有udevice的uclass
struct list_head uclass_root;
} gd_
DM root初始化函数dm_init。
int dm_init(bool of_live)
{
gd->uclass_root = &DM_UCLASS_ROOT_S_NON_CONST;
//初始化uclass_root链表头
INIT_LIST_HEAD(DM_UCLASS_ROOT_NON_CONST);
//创建一个device dm_root并将其绑定到driver name “root_driver”。
device_bind_by_name(NULL, false, &root_info,
&DM_ROOT_NON_CONST);
//探测设备udevice dm_root并激活它
ret = device_probe(DM_ROOT_NON_CONST);
}
2.1 device_bind_by_name分析
lists_driver_lookup_name
通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址。
device_bind_common
创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。
int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
const struct driver_info *info, struct udevice **devp)
{
struct driver *drv;
uint plat_size = 0;
int ret;
//通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址
drv = lists_driver_lookup_name(info->name);
//创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。
ret = device_bind_common(parent, drv, info->name, (void *)info->plat, 0,
ofnode_null(), plat_size, devp);
return ret;
}
2.2 device_probe分析
DM_ROOT_NON_CONST
代表gd中得dm_root:(((gd_t *)gd)->dm_root)
。device_probe
是一个通用的函数, 其大概步骤如下:
- 检测该device是否已经激活,已激活就直接返回。
- 获取该设备对应的driver。
- 读取设备的平台数据。
- 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
- 标记该设备处于激活状态。
- 处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
- 如果配置了IOMMU,则需要先打开IOMMU。
- 预使能设备,包括uclass的
pre-probe()
和父uclass的child_pre_probe()
方法。 - 处理
{clocks/clock-parents/clock-rates}
属性配置时钟。 - 执行该设备的driver的probe函数,激活该设备。
uclass_post_probe_device()
处理一个刚刚被probed过的设备。- 最后处理IOMUX驱动,如果是IOMUX驱动则需要设置pinctl的默认状态。
int device_probe(struct udevice *dev)
{
const struct driver *drv;
int ret;
// 检测该device是否已经激活,已激活就直接返回。
if (dev->flags & DM_FLAG_ACTIVATED)
return 0;
drv = dev->driver; // 获取该设备对应的driver
//device_ofdata_to_platdata() - 设备读取平台数据
ret = device_ofdata_to_platdata(dev);
// 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
if (dev->parent) {
ret = device_probe(dev->parent);
}
// 标记该设备处于激活状态。
dev_or_flags(dev, DM_FLAG_ACTIVATED);
//处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
//如果配置了IOMMU,则需要先打开IOMMU
if (CONFIG_IS_ENABLED(IOMMU) && dev->parent &&
(device_get_uclass_id(dev) != UCLASS_IOMMU)) {
ret = dev_iommu_enable(dev);
}
//预使能设备,包括uclass的pre-probe()和父uclass的child_pre_probe()方法。
ret = uclass_pre_probe_device(dev);
if (dev->parent && dev->parent->driver->child_pre_probe) {
ret = dev->parent->driver->child_pre_probe(dev);
}
//只处理具有有效ofnode的设备
if (dev_of_valid(dev) && !(dev->driver->flags & DM_FLAG_IGNORE_DEFAULT_CLKS)) {
// 处理{clocks/clock-parents/clock-rates}属性配置时钟
ret = clk_set_defaults(dev, CLK_DEFAULTS_PRE);
}
// 执行该设备的driver的probe函数,激活该设备。
if (drv->probe) {
ret = drv->probe(dev);
}
//uclass_post_probe_device() - 处理一个刚刚被probed过的设备
ret = uclass_post_probe_device(dev);
//设置pinctl的默认状态
if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
pinctrl_select_state(dev, "default");
return 0;
}
3. DM模型Device Tree节点的设备初始化:dm_scan(pre_reloc_only)
dm_scan
函数是在其他地方(设备树)搜索设备并进行驱动匹配,然后bind。主要分为三步:
dm_scan_plat
,搜索并绑定所有的驱动到根节点(((gd_t *)gd)->dm_root)
上;dm_extended_scan
,扫描dtb文件;dm_scan_other
,留给厂商自定义覆盖的弱函数。
本节主要展开分析dm_extended_scan
函数,下面是这个函数的流程:
dm_extended_scan(bool pre_reloc_only)
-->dm_scan_fdt(pre_reloc_only)
-->dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only)
-->lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only)
-->for : dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only)
-->dm_scan_fdt_node(gd->dm_root, node, pre_reloc_only)
首先这个函数定义了需要扫描的三个父节点,以下的扫描和绑定都是基于这三个节点展开的。
const char * const nodes[] = {
"/chosen",
"/clocks",
"/firmware"
};
Device Tree设备初始化的核心函数是dm_scan_fdt_node->lists_bind_fdt
,扫描设备树遍历父节点,并且将驱动绑定和设备树节点绑定。
步骤:
ofnode_get_property()
,根据node节点获取compatible
属性字符串list;- 遍历兼容字符串列表,
driver_check_compatible
尝试匹配每个字符串兼容字符串,以便我们按优先级顺序匹配从第一个字符串到最后一个; - 找到匹配的驱动,
device_bind_with_driver_data()
创建一个设备并且绑定到driver。
4. device_bind_common
device_bind_common
用于绑定设备树节点和驱动,主要有三处调用,分别是device_bind_by_name
,device_bind_with_driver_data
和device_bind
。device_bind_by_name
在dm模型初始化的时候来初始化根设备global_data->dm_root
;device_bind_with_driver_data
在扫描设备树并绑定驱动的时候调用;device_bind
在可以选择其余驱动的bind函数中调用,手动绑定想要的设备树节点和驱动。例如下图中apple笔记本的pinctl驱动:
绑定的主要步骤:
-
uclass_get()
根据driver->id
获取一个uclass(.id = UCLASS_PINCTRL),如果它不存在就创建它。每个类都由一个ID标识,一个从0到n-1的数字,其中n是类的数量。这个函数允许根据类的ID查找类。uclass_get(drv->id, &uc)
-
创建一个新的device,申请一个struct udevice空间
dev = calloc(1, sizeof(struct udevice))
-
初始化新device的相关列表头
INIT_LIST_HEAD(&dev->sibling_node); INIT_LIST_HEAD(&dev->child_head); INIT_LIST_HEAD(&dev->uclass_node); INIT_LIST_HEAD(&dev->devres_head);
-
初始化新udevice相关数据,将新udevice和传入的driver、uclass进行绑定。
dev->platdata = platdata;-------->传入 dev->driver_data = driver_data;-->传入 dev->name = name;------>传入 dev->node = node; dev->parent = parent; dev->driver = drv;----->传入 dev->uclass = uc;
-
dev->seq为该设备分配的序列号(-1 = none)。这是在设备probe时设置的,并且在设备的uclass中是唯一的。dev->req_seq为此设备请求的序列号(-1 = any)
dev->seq = -1; dev->req_seq = -1; if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) { if (uc->uc_drv->name && ofnode_valid(node)) dev_read_alias_seq(dev, &dev->req_seq); } else { dev->req_seq = uclass_find_next_free_req_seq(drv->id); }
-
sibling_node对应该设备,并将它添加到parent的child_head设备列表中
if (parent) list_add_tail(&dev->sibling_node, &parent->child_head);
-
uclass_bind_device()
将udevice与uclass进行关联uclass_bind_device(dev);
-
device绑定成功后,就会调用
drv->bind
if (drv->bind) { ret = drv->bind(dev); }
-
在一个新的child被绑定后,就会调用parent的
parent->driver->child_post_bind(dev)
if (parent && parent->driver->child_post_bind) { ret = parent->driver->child_post_bind(dev); }
-
在一个新设备绑定到这个uclass后被调用
if (uc->uc_drv->post_bind) { ret = uc->uc_drv->post_bind(dev); }
5. 总结
对于DM模型初始化来说,uboot会在启动序列中使用dm_init
创建一个dm_root(udevice)并将其绑定到“root_driver”(driver),然后device_probe
来激活这个设备。第二步使用dm_scan来绑定设备树中的设备和驱动到dm_root下面。