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_rootdm_root_fuclass_root用来管理整个DM模型。这几个变量代表什么意思呢?

  • dm_root:DM模型的根设备
  • dm_root_f:重定向前的根设备
  • uclass_rootuclass链表的头
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/

 

posted @ 2022-09-14 11:43  YYFaGe  阅读(2702)  评论(0编辑  收藏  举报