Uboot 设备驱动模型

此文是转载,原文: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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值