模型理解"/>
【转】uboot中DM驱动模型理解
title: uboot中DM驱动模型理解
date: 2022-03-01 18:46:46
categories:
tags:
- 驱动
- uboot
1、uboot驱动模型(DM)
Uboot引入驱动模型(driver model),这种驱动模型为驱动的定义和访问接口提供了统一的方法,提高了驱动之间的兼容性以及访问的标准型,Uboot驱动模型和linux kernel的设备驱动模型相类似,但是又有所区别。
要打开DM模型,最后反映在几个配置信息上:
CONFIG_DM=y
,全局DM模型打开CONFIG_DM_XXX=y
,某个驱动的DM模型的打开- 可以通过
Kconifg
、Makefile
来查看对应宏的编译情况
2、DM模型重点数据结构
2.1 global_data
typedef struct global_data {
...
#ifdef CONFIG_DMstruct udevice *dm_root; /* Root instance for Driver Model */struct udevice *dm_root_f; /* Pre-relocation root instance */struct list_head uclass_root; /* Head of core tree */
#endif
...
}
global_data,管理着整个Uboot的全局变量,其中dm_root,dm_root_f,uclass_root用来管理整个DM模型。这几个变量含义如下:
dm_root:DM模型的根设备
dm_root_f:重定向前的根设备
uclass_root:uclass链表的头
这几个变量,最终要的作用就是:管理整个模型中的udevice设备信息和uclass驱动类。
2.2 uclass
struct uclass {void *priv; //uclass的私有数据struct uclass_driver *uc_drv; //uclass类的操作函数集合struct list_head dev_head; //该uclass的所有设备struct list_head sibling_node; //下一个uclass的节点
};
其中,uclass
相当于队长,管理着对应某一个类别下的所有的udevice
,并且有对应的uclass_driver
驱动。
uclass
是uboot
自动生成的,并且不是所有uclass
都会生成,有对应uclass_driver
并且有被udevice
匹配到的uclass
才会生成。
所有生成的uclass
都会被挂载gd->uclass_root
链表上。
直接遍历链表gd->uclass_root
链表并且根据uclass_id
来获取到相应的uclass
。
int uclass_get(enum uclass_id key, struct uclass **ucp);
// 从gd->uclass_root链表获取对应的uclass
2.3 uclass_driver
正如上面,我们看到了uclass
类所包含uclass_driver
结构体,uclass_driver
正如其名,它就是uclass
的驱动程序。其主要作用是:为uclass
提供统一管理的接口,结构体如下:
/*** struct uclass_driver - Driver for the uclass** A uclass_driver provides a consistent interface to a set of related* drivers.*/
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
主要通过UCLASS_DRIVER
来定义,以mailbox为例:
/* Declare a new 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)))
通过上面的定义,替换掉宏之后,最终得到的定义如下:
struct uclass_driver _u_boot_list_2_uclass_2_mbox_test = {.name = "mbox_test",.id = UCLASS_MISC,.of_match = win2030_umbox_test_ids,.priv_auto_alloc_size = sizeof(struct win2030_umbox_test),}
//同时存放在段._u_boot_list_2_uclass_2_mbox_test中,也就是section段的内容
由上面结构体可得,其定义之后都被存放在了段._u_boot_list_2_uclass_2_umbox_test
中,那么去哪里可以看到呢?
在u-boot.map
文件中搜索,._u_boot_list_2_uclass_2_umbox_test
,就可以查到程序中定义的所有驱动程序。
为什么是uclass_2
呢?我们大概看一下,也会看到uclass_1
和uclass_3
,这两个代表什么呢?
想要获取uclass_driver需要先获取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的长度struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 从uclass_driver table中获取uclass id为id的uclass_driver。
上文中提到的uclass_1
和uclass_3
起到定位作用,用于计算uclass_2
的长度!
上述的API,主要用于根据uclass_id
来查找到对应的uclass_driver
,进而操作对应的uclass
下的udevice
。
2.4 uclass_id
uclass
代表驱动的一个类别,uclass_driver
是uclass
的驱动程序,为uclass
提供统一操作接口。而对于不同类型的驱动,就需要uclass_id
来区分了!
事实上,每一种类型的设备``uclass都有唯一对应的
uclass_id,贯穿设备模型,也是
udevice与
uclass`相关联的关键之处。
enum uclass_id {/* These are used internally by driver model */UCLASS_ROOT = 0,UCLASS_DEMO,UCLASS_TEST,UCLASS_TEST_FDT,UCLASS_TEST_BUS,UCLASS_TEST_PROBE,
....../* U-Boot uclasses start here - in alphabetical order */UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */UCLASS_ADC, /* Analog-to-digital converter */UCLASS_AHCI, /* SATA disk controller */UCLASS_AUDIO_CODEC, /* Audio codec with control and data path */UCLASS_AXI, /* AXI bus */UCLASS_BLK, /* Block device */UCLASS_BOARD, /* Device information from hardware */
......
};
这个uclass_id在一个uclass-id.h文件中,描述了各种所支持的设备id
2.5 udevice
/*** struct udevice - An instance of a driver** This holds information about a device, which is a driver bound to a* particular port or peripheral (essentially a driver instance).**/
struct udevice {const struct driver *driver; //device 对应的driverconst char *name; //device 的名称void *platdata;void *parent_platdata;void *uclass_platdata;ofnode node; //设备树节点ulong driver_data;struct udevice *parent; //父设备void *priv; // 私有数据的指针struct uclass *uclass; //驱动所属的uclassvoid *uclass_priv;void *parent_priv;struct list_head uclass_node;struct list_head child_head;struct list_head sibling_node;uint32_t flags;int req_seq;int seq;
#ifdef CONFIG_DEVRESstruct list_head devres_head;
#endif
};
udevice是最基础的一个设备单元,我们把它作为一个独立的个体,上层所有的操作,最终都与该结构体有关。
我们创建一个设备后,为了服从统一的管理,该结构体会被连接到DM模型下,并入到机制中。那么udevice会被连接到哪里呢?
将udevice连接到对应的uclass中,uclass主要用来管理着同一类的驱动.
除此之外,有父子关系的udevice,还会连接到udevice->child_head链表下,方便调用.
相关API:
这些相关的API,主要作用就是根据uclass_id
,查找对应的uclass
,然后根据索引值或者名称,来查找到对应的udevice
#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中获取udevicestruct 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);
2.6 driver
struct driver {char *name; //驱动名称enum uclass_id id; //驱动所对应的uclass_id const struct udevice_id *of_match; //匹配函数int (*bind)(struct udevice *dev); //绑定函数int (*probe)(struct udevice *dev); //注册函数int (*remove)(struct udevice *dev);int (*unbind)(struct udevice *dev);int (*ofdata_to_platdata)(struct udevice *dev);int (*child_post_bind)(struct udevice *dev);int (*child_pre_probe)(struct udevice *dev);int (*child_post_remove)(struct udevice *dev);int priv_auto_alloc_size;int platdata_auto_alloc_size;int per_child_auto_alloc_size;int per_child_platdata_auto_alloc_size;const void *ops; /* driver-specific operations */uint32_t flags;
#if CONFIG_IS_ENABLED(ACPIGEN)struct acpi_ops *acpi_ops;
#endif
};
driver
对象,主要通过U_BOOT_DRIVER
来定义,以mailbox来举例:
/* Declare a new 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)))
通过上面的定义,最终我们定义的结构体如下:
struct uclass_driver _u_boot_list_2_uclass_2_mbox_test = {.name = "mbox_test",.id = UCLASS_MISC,.of_match = win2030_umbox_test_ids,.priv_auto_alloc_size = sizeof(struct win2030_umbox_test),}
//同时存放在段._u_boot_list_2_uclass_2_mbox_test中,也就是section段的内容
由上面结构体可得,其定义之后都被存放在了段._u_boot_list_2_driver_2_xxx
中,那么去哪里可以看到呢?
相关API:
/*先获取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); // 通过.u_boot_list_2_driver_3的段地址 减去 .u_boot_list_2_driver_1的段地址 获得driver table的长度/*遍历所有的driver*/
struct driver *lists_driver_lookup_name(const char *name) // 从driver table中获取名字为name的driver。
3、DM驱动模型之上帝视角
如何实现driver
和udevice
的绑定、uclass
、uclass_driver
的绑定呢?
从对象设计的角度来看,Uboot的驱动模型可以分为静态形式和动态形式。
- 静态模式:对象是离散的,和其他对象分隔开,减小对象复杂度,利于模块化设计。
- **动态模式:*运行态表达形式的对象是*把所有的静态对象组合成层次视图,有清晰的数据关联视图
在静态模式下,驱动模型主要将对象分为udevice
和driver
,即设备和驱动程序,两个就像火车的两条轨道,永远也不会产生交集,驱动和设备可以想注册多少就注册多少。
udevice是driver的一个实例,两个不相交的铁轨,那么如何让其产生交集呢?这就是动态模式需要做的工作了!
**在动态模式下,**引入了uclass和uclass_driver两个数据结构,实现了对udevice和driver的管理。
看一下uclass和uclass_driver两个结构体的说明:
- uclass:设备组公共属性对象,作为
udevice
的一个属性,主要用来管理某个驱动类的所有的设备。 - uclass_driver:设备组公共行为对象,
uclass
的驱动程序,主要将uclass
管理的设备和驱动实现绑定、注册,移除等操作。
通过这两个结构体的引入,可以将毫不相关的udevice
是driver
关联起来!
udevice
与driver
的绑定:通过驱动的of_match
和compatible
属性来配对,绑定。
udevice与uclass的绑定:udevice内的driver下的uclass_id,来与uclass对应的uclass_driver的uclass_id进行匹配。
uclass与uclass_driver的绑定:已知udevice内的driver下的uclass_id,创建uclass的同时,通过``uclass_id找到对应的uclass_driver对象,然后将uclass_driver绑定到uclass`上!
4、DM模型-udevice与driver绑定
DM的初始化分为两个部分,一个是在relocate
重定向之前的初始化:initf_dm
,一个是在relocate
重定向之后的初始化:initr_dm
。
下面是两个函数的对比:
static int initf_dm(void)
{
#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)int ret;bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");ret = dm_init_and_scan(true); //这里为truebootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);if (ret)return ret;
#endif
#ifdef CONFIG_TIMER_EARLYret = dm_timer_init();if (ret)return ret;
#endifreturn 0;
}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;gd->dm_root = NULL;
#ifdef CONFIG_TIMERgd->timer = NULL;
#endifbootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r");ret = dm_init_and_scan(false); //这里为falsebootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);if (ret)return ret;return 0;
}
两个均调用了dm_init_and_scan这个接口,这两个的关键区别在于参数的不同。
首先说明一下dts节点中的“u-boot,dm-pre-reloc”属性,当设置了这个属性时,则表示这个设备在relocate之前就需要使用。
当dm_init_and_scan的参数为true时,只会对带有“u-boot,dm-pre-reloc”属性的节点进行解析。而当参数为false的时候,则会对所有节点都进行解析。
4.1 dm_init
int dm_init(bool of_live)
{int ret;if (gd->dm_root) {dm_warn("Virtual root driver already exists!\n");return -EINVAL;}INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);#if defined(CONFIG_NEEDS_MANUAL_RELOC)fix_drivers();fix_uclass();fix_devices();
#endifret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST); //查找root_driver驱动,并绑定if (ret)return ret;
#if CONFIG_IS_ENABLED(OF_CONTROL)
# if CONFIG_IS_ENABLED(OF_LIVE)if (of_live)DM_ROOT_NON_CONST->node = np_to_ofnode(gd->of_root);else
#endifDM_ROOT_NON_CONST->node = offset_to_ofnode(0);
#endifret = device_probe(DM_ROOT_NON_CONST); //probe激活root_driver驱动if (ret)return ret;return 0;
}
dm_init
这个函数,名字起的容易让人误导,这个函数主要做的就是初始化了根设备root_driver
,根据这个跟设备,初始化了global_data
中的dm_root
、uclass_root
。
4.2 list_bind_fdt
这个函数主要用来查找子设备,并且根据查找到的子设备,进而查找对应驱动进行绑定!即:实现了driver
和device
的绑定。
int lists_bind_fdt(struct udevice *parent, ofnode node, struct udevice **devp,bool pre_reloc_only)
{struct driver *driver = ll_entry_start(struct driver, driver); //获得驱动列表的起始地址const int n_ents = ll_entry_count(struct driver, driver); //获得驱动列表的总数量const struct udevice_id *id;struct driver *entry;struct udevice *dev;bool found = false;const char *name, *compat_list, *compat;int compat_length, i;int result = 0;int ret = 0;if (devp)*devp = NULL;name = ofnode_get_name(node);log_debug("bind node %s\n", name);compat_list = ofnode_get_property(node, "compatible", &compat_length); //得到compatible属性,用于匹配driver驱动if (!compat_list) {if (compat_length == -FDT_ERR_NOTFOUND) {log_debug("Device '%s' has no compatible string\n",name);return 0;}dm_warn("Device tree error at node '%s'\n", name);return compat_length;}/** Walk through the compatible string list, attempting to match each* compatible string in order such that we match in order of priority* from the first string to the last.*/for (i = 0; i < compat_length; i += strlen(compat) + 1) {compat = compat_list + i;log_debug(" - attempt to match compatible string '%s'\n",compat);for (entry = driver; entry != driver + n_ents; entry++) { //循环判断所有驱动是否匹配 ret = driver_check_compatible(entry->of_match, &id,compat);if (!ret)break;}if (entry == driver + n_ents)continue;if (pre_reloc_only) {if (!ofnode_pre_reloc(node) &&!(entry->flags & DM_FLAG_PRE_RELOC)) {log_debug("Skipping device pre-relocation\n");return 0;}}log_debug(" - found match at '%s': '%s' matches '%s'\n",entry->name, entry->of_match->compatible,id->compatible);ret = device_bind_with_driver_data(parent, entry, name,id->data, node, &dev); //该函数,用于创建udevice对象,并与查找到的driver绑定if (ret == -ENODEV) {log_debug("Driver '%s' refuses to bind\n", entry->name);continue;}if (ret) {dm_warn("Error binding driver '%s': %d\n", entry->name,ret);return ret;} else {found = true;if (devp)*devp = dev;}break;}if (!found && !result && ret != -ENODEV)log_debug("No match for node '%s'\n", name);return result;
}
ists_bind_fdt这个函数,主要用来扫描设备树中的各个节点;
根据扫描到的udevice设备信息,通过compatible来匹配compatible相同的driver,匹配成功后,就会创建对应的struct udevice结构体,它会同时指向设备资源和driver,这样设备资源和driver就绑定在一起了。
5、DM模型—probe探测函数的执行
上述,完成了DM模型的初始化,但是我们只是建立了driver
和udevice
的绑定关系,那么何时调用到我们驱动中的probe
探测函数呢?uclass
与driver
又何时匹配的呢?
dm_init
只是负责初始化并绑定了udevice
和driver
,那么probe
探测函数是在该驱动初始化的时候执行。
以mmc驱动为例!其初始化流程如下:
在MMC驱动初始化后,有没有注意到mmc_probe这个函数,该函数就是间接调用了我们驱动编写的probe函数。
执行流程在上面已经很清楚了:根据uclass_id,调用``uclass_get_device_by_seq来得到udevice,进而调用device_probe来找到对应驱动的probe`。
主要工作归纳如下:
- 根据
udevice
获取driver
- 然后判断是否父设备被
probe
- 对父设备进行probe
- 调用driver的probe函数
6、DM模型——uclass与uclass_driver绑定
上述完成了driver
的probe
函数调用,基本底层都已经准备好了,uclass
何时与uclass_driver
绑定,给上层提供统一的API呢?
uclass
与uclass_driver
绑定,也是在驱动probe
之后,确保该驱动存在,设备存在,最后为该驱动绑定uclass
与uclass_driver
,为上层提供统一接口。
上文的驱动流程图中mmc_do_preinit
这个里面调用了ret = uclass_get(UCLASS_MMC, &uc);
,该函数才是真正的将uclass
与uclass_driver
绑定。
int uclass_get(enum uclass_id id, struct uclass **ucp)
{struct uclass *uc;*ucp = NULL;uc = uclass_find(id);if (!uc)return uclass_add(id, ucp);*ucp = uc;return 0;
}
uclass_get主要实现了:根据uclass_id查找对应的uclass是否被添加到global_data->uclass_root链表中,如果没有添加到,就调用uclass_add函数,实现uclass与uclass_driver的绑定,并将其添加到global_data->uclass_root链表中。
static int uclass_add(enum uclass_id id, struct uclass **ucp)
{struct uclass_driver *uc_drv;struct uclass *uc;int ret;*ucp = NULL;uc_drv = lists_uclass_lookup(id); //根据uclass_id查找到对应的driverif (!uc_drv) {debug("Cannot find uclass for id %d: please add the UCLASS_DRIVER() declaration for this UCLASS_... id\n",id);/** Use a strange error to make this case easier to find. When* a uclass is not available it can prevent driver model from* starting up and this failure is otherwise hard to debug.*/return -EPFNOSUPPORT;}uc = calloc(1, sizeof(*uc));if (!uc)return -ENOMEM;if (uc_drv->priv_auto_alloc_size) {uc->priv = calloc(1, uc_drv->priv_auto_alloc_size);if (!uc->priv) {ret = -ENOMEM;goto fail_mem;}}uc->uc_drv = uc_drv; //uclass与uclass_driver绑定INIT_LIST_HEAD(&uc->sibling_node);INIT_LIST_HEAD(&uc->dev_head);list_add(&uc->sibling_node, &DM_UCLASS_ROOT_NON_CONST); //添加到global_data->uclass_root链表中if (uc_drv->init) {ret = uc_drv->init(uc);if (ret)goto fail;}*ucp = uc;return 0;
fail:if (uc_drv->priv_auto_alloc_size) {free(uc->priv);uc->priv = NULL;}list_del(&uc->sibling_node);
fail_mem:free(uc);return ret;
}
更多推荐
【转】uboot中DM驱动模型理解
发布评论