uboot 设备驱动模型
一、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是否可用
- gpio_request_by_name
-
老接口:
这些接口是为了兼容老版本的接口,注意,但是最终还是会调用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的值
- gpio_request
例子:
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/