此文是转载,原文:https://www.cnblogs.com/YYFaGe/p/16672483.html
一、uboot驱动模型概述
在linux中采用device、bus、driver来管理设备和驱动,在uboot中引入了驱动模型(driver model)简称为DM,这种驱动模型为驱动的定义和访问接口提供了统一的方法。提高了驱动之间的兼容性以及访问的标准型。它主要包含以下4个成员:
udevice:它就是指设备对象,一个driver的实例。
driver:udevice的驱动,硬件外设的driver。
uclass:一个uboot驱动类,收集类似的驱动程序。
uclass_driver:uclass对应的driver
uboot中可以使用dm tree、dm uclass、dm devres命令来打印设备和驱动的相关信息。
global_data,管理着整个Uboot的全局变量,其中dm_root,dm_root_f,uclass_root用来管理整个DM模型。这几个变量代表什么意思呢?
dm_root:DM模型的根设备
dm_root_f:重定向前的根设备
uclass_root:uclass链表的头
typedef struct global_data {
...
#ifdef CONFIG_DM
struct 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
...
}
1.1 uclass
uclass对驱动进行了归类处理,他把具有相似操作的设备归到一个uclass下面,而不管它们的具体形式。比如对于GPIO它们会具有读取管脚和设置管脚输出的方法,对于serial它们会具有输出串行数据、读取串行数据和设置波特率等方法,这就是uclass要描述的东西,而它不会在乎GPIO或者serial是片内soc上的外设还是外部专用芯片扩展的。
struct uclass {
void *priv; //uclass的私有数据
struct uclass_driver *uc_drv; //uclass类的操作函数集合
struct list_head dev_head; //该uclass的所有设备
struct list_head sibling_node; //连接到gd->
uclass_root 上
};
1.2 uclass_driver
uclass_driver 就是uclass的驱动程序。其主要作用是:为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之前调用,device_probe-->uclass_pre_probe_device-->pre_probe
int (*post_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之后调用,device_probe-->uclass_post_probe_device-->post_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来定义,下面以mmc的uclass_driver为例说明:
mmc的uclass_driver定义在uboot/driver/mmc/mmc-uclass.c中
/* 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)))
UCLASS_DRIVER(mmc) = {
.id = UCLASS_MMC,
.name = "mmc",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.per_device_auto_alloc_size = sizeof(struct mmc_uclass_priv),
};
这样定义之后,mmc的uclass_driver就定定义了在数据段的"u_boot_list_2_uclass_2_mmc "字段中,打开u-boot.map文件mmc的数据段如下所示:
其他i2c、mtd等uclass_driver定义方法类似。
1.3 udevice
一个udevice 代表一个设备,uboot会从设备树中读取设备树节点信息,每个节点都生成一个udevice。也可以用宏U_BOOT_DEVICE来定义一个设备。
struct udevice {
const struct driver *driver; //device 对应的driver
const char *name; //device 的名称
void *platdata;
void *parent_platdata;
void *uclass_platdata;
ofnode node; //设备树节点
ulong driver_data;
struct udevice *parent; //父设备
void *priv; // 私有数据的指针
struct uclass *uclass; //驱动所属的uclass
void *uclass_priv;
void *parent_priv;
struct list_head uclass_node; //连接在struct uclass的dev_head后面
struct list_head child_head; //子设备连接在这里
struct list_head sibling_node; //连接到父设备的child_head后面
uint32_t flags;
int req_seq;
int seq;
#ifdef CONFIG_DEVRES
struct list_head devres_head;
#endif
};
1.4 driver
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宏定义,xilinx zynq系列的soc的mmc driver定义在uboot/driver/mmc/zynq_sdhci.c中,如下所示;
#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)))
/* Declare a new U-Boot driver */
#define U_BOOT_DRIVER(__name) \
ll_entry_declare(struct driver, __name, driver)
U_BOOT_DRIVER(arasan_sdhci_drv) = {
.name = "arasan_sdhci",
.id = UCLASS_MMC,
.of_match = arasan_sdhci_ids,
.ofdata_to_platdata = arasan_sdhci_ofdata_to_platdata,
.ops = &sdhci_ops,
.bind = arasan_sdhci_bind,
.probe = arasan_sdhci_probe,
.priv_auto_alloc_size = sizeof(struct arasan_sdhci_priv),
.platdata_auto_alloc_size = sizeof(struct arasan_sdhci_plat),
};
这样定义后,mmc的driver将定义在数据段中的“”“u_boot_list_2_driver_2_arasan_sdhci_drv”字段中,在u-boot.map中可以看到
二、dm模型初始化
dm模型初始化在uboot/common/board_r.c中
-->initr_dm
-->dm_init_and_scan
-->dm_init //注册一个gd->dm_root 根设备,后面所有设备都是这个根设备的子设备
-->dm_scan_platdata
-->dm_extended_scan_fdt
-->dm_scan_fdt
-->dm_scan_fdt_live //xilinx zynq 没有定义OF_LIVE宏,这个函数没有执行
-->dm_scan_fdt_node //从设备树中扫描出所有设备节点
-->lists_bind_fdt //为每个设备节点的compatible与数据段中的driver列表的compatible匹配,为匹配上的driver和设备节点执行device_bind_with_driver_data函数
-->device_bind_with_driver_data
-->device_bind_common //为设备树中的每个设备节点创建一个udevice,并查找有没有对应的uclass,如果有则取出,没有则创建一个uclass,关联udevice和uclass。
-->dm_scan_other //这里不执行,直接返回0
dm模型初始化完成后,uclass、udevice、globle_date关系如下:
uclass 和udevice的关系如下所示,例如一个soc中有三个i2c控制器,这三个i2c控制器同归于一个i2c class管理
globle_data中管理这所有的udevice和uclass
三、uboot GPIO驱动
下面以xilinx 的zynqmp soc的gpio驱动说明uboot的GPIO驱动结构,zynqmp在ps端已经有一个gpio控制器,在pl端再放两个axi gpio控制器,这样就有三个gpio控制器。经过第二节的dm初始化后,gpio的uclass、udevice和drive的对应关系如下:
gpio提供的通用接口在uboot/driver/gpio/gpio-uclass.c文件中,分别如下:
DM框架下的接口
注意,外部通过gpio_desc 来描述一个GPIO,所以这些接口都是以gpio_desc作为参数
gpio_request_by_name
int gpio_request_by_name(struct udevice *dev, const char *list_name, int index, struct gpio_desc *desc, int flags)
通过对应的udevice找到其dtsi节点中属性名为list_name的GPIO属性并转化为gpio_desc,并且request。
gpio_request_by_name_nodev
int gpio_request_by_name_nodev(const void *blob, int node, const char *list_name, int index, struct gpio_desc *desc, int flags)
通过对应的dtsi节点中属性名为list_name的GPIO属性并转化为gpio_desc,并且request。
dm_gpio_request
int dm_gpio_request(struct gpio_desc *desc, const char *label)
申请gpio_desc描述的GPIO
dm_gpio_get_value
int dm_gpio_get_value(const struct gpio_desc *desc)
获取gpio_desc描述的GPIO的值
dm_gpio_set_value
int dm_gpio_set_value(const struct gpio_desc *desc, int value)
设置gpio_desc描述的GPIO的值
dm_gpio_set_dir_flags
int dm_gpio_set_dir_flags(struct gpio_desc *desc, ulong flags)
设置gpio_desc描述的GPIO的输入输出方向,带标志
dm_gpio_set_dir
int dm_gpio_set_dir(struct gpio_desc *desc)
设置gpio_desc描述的GPIO的输入输出方向
dm_gpio_is_valid
static inline bool dm_gpio_is_valid(const struct gpio_desc *desc)
判断gpio_desc是否可用
老接口:
这些接口是为了兼容老版本的接口,注意,但是最终还是会调用DM框架下的接口
gpio_request
int gpio_request(unsigned gpio, const char *label)
申请一个GPIO
gpio_direction_input
int gpio_direction_input(unsigned gpio)
设置某个GPIO为输入
gpio_direction_output
int gpio_direction_output(unsigned gpio, int value)
设置某个GPIO为输出
gpio_get_value
int gpio_get_value(unsigned gpio)
获取某个GPIO上的值
gpio_set_value
int gpio_set_value(unsigned gpio, int value)
设置GPIO的值
例子:
int gpio = 10;
gpio_request(gpio, "camera_power"); //获取10号gpio端口
ret = gpio_direction_output(gpio, 0); //设置输出,输出低电平
if (ret != 0)
{
printf("gpio set direction error\n");
return -1;
}
ret = gpio_set_value(gpio, 1); //输出高电平
if (ret != 0)
{
printf("gpio set value error\n");
return -1;
}
在调用gpio_set_value时会从全局变量gd中找到uclass_id为UCLASS_GPIO的uclass,这个就是gpio的uclass。然后从这个gpio uclass中找到对应的udevice,最后从udevice中找到driver并调用driver的opt函数设置gpio的电平。
四、uboot驱动总结
从第三节的gpio驱动中可以得出一个结论,uboot驱动采用了分层和分离的思想,分离的思想是让soc的控制器驱动和设备驱动分离,分层的思想是驱动分为driver、device、class三层。还是以gpio驱动为例,比如用gpio去控制led灯,这个灯的效果是要一闪一灭还是流水灯还是其他什么效果,这个可以看做是led灯这个设备的驱动,是应用层的东西。soc的gpio控制器驱动是属于uboot驱动层的东西。通过uclsaa-gpio.c提供的gpio控制接口来实现应用层的设备驱动和驱动层的控制器驱动分离,这样的好处是驱动层的改变不会影响到应用层,比如从xilinx 的zynq平台换到hisi平台,gpio驱动肯定发送变化,只要uclass-gpio.c中提供的接口不变,应该层的led灯控制程序就可以不变,这就是分离带来的好处。
参考链接:https://zhuanlan.zhihu.com/p/460754843
https://blog.csdn.net/ZHONGCAI0901/article/details/117781158
https://www.freesion.com/article/368464004/