单片机
返回首页

OK6410A 开发板 (三) 19 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 driver model

2022-09-21 来源:csdn

总体简述

在 u-boot 中, DM 是 uclass device driver 以及三者相关函数的总体


uclass device driver 相关结构体

driver 在定义的时候就根据 其 自身的id成员被 分为了 XXX uclass

device 在定义的时候就根据 其 自身的name成员 暗含了 与 driver 绑定的条件


函数

初始化

在(initf_dm/initr_dm)的时候,为每一个设备(设备树中的节点/U_BOOT_DEVICE声明的结构体)做以下动作

1. 初始化了 device 结构体

2. device_bind_common 实现driver 、uclass 与该 device 绑定(即三者绑定)

3. 调用了 driver 中的 bind 函数 为该设备类做初始化

在 例如 serial_init/mmc_initialize的时候

1. 调用 driver 中的 probe 函数 为设备做初始化

使用设备

1. 通过 uclass_get_device_xxx 获取 device 句柄

2. 通过 uclass 提供的函数 yyy 来控制设备


对driver mode 来一次感官认识

文档查阅

doc/driver-model/design.rst


u-boot 将 驱动和 设备 做了分离 ,分成了三部分(这里没涉及 uclass 的core 部分) 

Uclass Driver Device


假设 我用 cmdline 去访问 ,那么涉及的代码就是 4部分(这里没涉及 uclass 的core 部分) 

cmd

cmd/demo.c

Uclass

drivers/demo/demo_uclass.c

include/dm-demo.h

Driver

demo-shape.c  demo-simple.c(一个文件对应一个驱动)

Device

demo-pdata.c(虽然是一个文件,但是这里有5个设备)


运行感知

make sandbox_defconfig

make

./u-boot -d u-boot.dtb

=> demo list  // 查看 已经注册的 且 属于 UCLASS_DEMO 类的 设备有多少个 (5个)

Demo uclass entries:

entry 0 - instance 05292d40, ops 08000001, platdata 08000000

entry 1 - instance 05292e00, ops 08000002, platdata 08000000

entry 2 - instance 05292ec0, ops 08000001, platdata 08000003

entry 3 - instance 05292f80, ops 08000002, platdata 08000004

entry 4 - instance 05293040, ops 08000001, platdata 08000004


=> demo hello 0

r@@@@@@@

e@@@@@@@

d@@@@@@@

r@@@@@@@

e@@@@@@@

d@@@@@@@

=> demo hello 2

g       

r@      

e@@     

e@@@    

n@@@@   

g@@@@@  

=> demo hello 4

  y@@@  

 e@@@@@ 

l@@@@@@@

l@@@@@@@

 o@@@@@ 

  w@@@  

// 看起来 设备 0 2 4 用的是同一个驱动 , 实际证明也是如此 , 用的是 demo_shape_drv


=> demo hello 1

Hello from 05292e00: red 4

=> demo hello 3

Hello from 05292f80: yellow 6

// 看起来 设备 1 3 用的是同一个驱动 , 实际证明也是如此 , 用的是 demo_simple_drv


代码流程分析

// 如果是 demo hello 1

cmd(cmd/demo.c)

do_demo

uclass_get_device(UCLASS_DEMO, devnum, &demo_dev);

demo_hello(demo_dev, ch);

uclass(drivers/demo/demo_uclass.c)

demo_hello

struct demo_ops *ops = device_get_ops(dev);

ops->hello(dev, ch);

Driver(demo-simple.c)

simple_hello

const struct dm_demo_pdata *pdata = dev_get_platdata(dev);

printf('Hello from %08x: %s %dn', (uint)map_to_sysmem(dev), pdata->colour, pdata->sides);

Device(demo-pdata.c)

static const struct dm_demo_pdata red_square = {                                 

    .colour = 'red',                                                             

    .sides = 4.                                                                  

};

U_BOOT_DEVICE(demo1) = {                                                         

    .name = 'demo_simple_drv',                                                      

    .platdata = &red_square,                                                        

};


代码功能分析

uclass(drivers/demo/demo_uclass.c)

通过 UCLASS_DRIVER(demo) 注册 UCLASS_DEMO 类

Driver(demo-simple.c)

通过 U_BOOT_DRIVER(demo_simple_drv) 注册 驱动, 并绑定 到 UCLASS_DEMO

Device(demo-pdata.c)

通过 U_BOOT_DEVICE(demo0) 注册设备,并绑定到 特定的driver

消费者(例如cmd)

通过 uclass_get_device(UCLASS_DEMO, devnum, &demo_dev); 获取到设备

通过 设备类 提供的 操作函数  demo_hello(demo_dev, ch); 来控制设备


u-boot 引入 driver model以前

消费者想要操作一个设备,需要知道以下信息

A. 设备的物理地址是什么

B. 设备的驱动是什么,以及提供了什么API

u-boot 引入 driver model之后

消费者想要操作一个设备,不需要知道A和B,只需要知道下面的就行了

1. 该设备属于哪个类

2. 该类的操作函数


以上都是从消费者角度考虑的,但是没考虑 driver model 的核心实现(即 uclass的核心实现)


而我认为 initf_dm 和 initr_dm 就是 核心实现的初始化


driver mode 的初始化

看了一些博客

先来个总体感觉

1. initf_dm 和 initr_dm 的过程是类似的,下面以initf_dm 为例讲述

2. initf_dm 目的在于 初始化一个 树型 数据结构 ,里面存储的是 Device,并初始化 gd->dm_root

3. Device 是 从 U_BOOT_DEVICE 和 fdt 中找的

4. 在 对 Device 进行 树型 排列的过程中,还要 针对  每一个 Device 做一些 recipe

5. recipe 包括 为 Device 找到 Driver , 为 Device 找到 UCLASS

6. 因为 Device 是 树型结构,操作一个Device 的时候还可能对 parents 做一些动作


u-boot 运行过程中

board_init_f 

initf_dm

bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, 'dm_f');

ret = dm_init_and_scan(1);

// 主要是负责 root driver和root device 的bind 和 probe

// core/root.c 中的 U_BOOT_DRIVER(root_driver)

// core/root.c 中的 UCLASS_DRIVER(root)

// drivers/core/device.c 中的 device_bind_common 动态创建了 U_BOOT_DEVICE(root)

dm_init

INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);

device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);

drv = lists_driver_lookup_name(info->name);

device_bind_common(parent, drv, info->name, (void *)info->platdata, 0, ofnode_null(), platdata_size, devp);

uclass_bind_device

没有 drv->bind


(((gd_t *)gd)->dm_root)->node = offset_to_ofnode(0);

device_probe((((gd_t *)gd)->dm_root))

device_ofdata_to_platdata

没有dev->parent

uclass_pre_probe_device

clk_set_defaults(dev, 0);

没有drv->probe

uclass_post_probe_device

// 主要是负责 U_BOOT_DEVICE 声明的 device 和 对应的 driver  bind/probe

dm_scan_platdata(1)

lists_bind_drivers((((gd_t *)gd)->dm_root), pre_reloc_only);

bind_drivers_pass

const int n_ents = ll_entry_count(struct driver_info, driver_info) = 0;

return 0

// 主要是负责 fdt 声明的 device 和 对应的 driver  bind/probe

// 设备树预处理文件 : output/arch/arm/dts/.s3c64xx-ok6410a.dtb.dts.tmp

dm_extended_scan_fdt(gd->fdt_blob,1);

dm_scan_fdt(blob, pre_reloc_only);

dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);

查看节点有没有enable ,没有的话(sdhci@7C300000,sdhci@7C400000)什么都不做,返回.有的话继续


for_each_node

lists_bind_fdt

// 没有 compatible string 的 node 

什么都不做 (chosen,aliases,memory,config,)

// 有 compatible 且没有子节点

对每个节点(interrupt-controller@10490000,clock@1800000,serial0@7F005000,sdhci@7C200000)

driver_check_compatible

device_bind_with_driver_data

device_bind_common

uclass_bind_device

有 drv->bind则执行bind,没 drv->bind则不执行

// 有 compatible 且有 子节点

对每个节点(包括该节点pinctrl@7f008000和所有子节点gpa-gpq)

device_bind_with_driver_data

device_bind_common

uclass_bind_device

有 drv->bind

dm_scan_other(1);

bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);

board_init_r

initr_dm

gd->dm_root_f = gd->dm_root;

gd->dm_root = ((void *)0);

bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, 'dm_r');

ret = dm_init_and_scan(0);

dm_init(0)

dm_scan_platdata(0)

dm_extended_scan_fdt(gd->fdt_blob,0);

dm_scan_other(0);

bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);

initr_dm_devices

// null


initf_dm initr_dm两者的区别

初始化流程

initf_dm

dm_init_and_scan(true) // pre_reloc_only = 1

// device_bind_by_name

// if (pre_reloc_only && !(drv->flags & DM_FLAG_PRE_RELOC)) return; // return表示不做下面的初始化动作

// 也就是说 该设备 需要在 重定位之前初始化 ,则 drv->flags & DM_FLAG_PRE_RELOC != 0

// 这里初始化的设备就是 drv->flags & DM_FLAG_PRE_RELOC != 0 的设备

// 哪些驱动 是 DM_FLAG_PRE_RELOC 

// pinctrl@7f008000以及子节点

// clock@1800000

// serial0@7F005000

initr_dm

dm_init_and_scan(false) // pre_reloc_only = 0

消费者(其实这个消费者也是DM架构的一部分,只不过是核心部分的较外层部分)

initf_dm

initf_dm 之后 initr_dm 之前的消费者(serial_init)

调用device_probe次数:2


serial_init // 主要作用是调用drv->probe 完成硬件的初始化

serial_find_console_or_panic

serial_check_stdout

uclass_get_device_by_of_offset

uclass_get_device_tail

device_probe(dev);

if (dev->parent) device_probe(dev->parent);

if (drv->probe) drv->probe(dev);


initr_dm 

initr_dm 之后的消费者

调用device_probe次数:(18+104)


driver mode 的初始化和 Uclass Driver Device

Uclass Driver Device 的展开

uclass UCLASS_DRIVER(gpio)

struct uclass_driver _u_boot_list_2_uclass_2_gpio __attribute__((__aligned__(4))) __attribute__((unused, section('.u_boot_list_2_''uclass''_2_''gpio'))) = {

 .id = UCLASS_GPIO,

 .name = 'gpio',

 .flags = (1 << 0),

 .post_probe = gpio_post_probe,

 .post_bind = gpio_post_bind,

 .pre_remove = gpio_pre_remove,

 .per_device_auto_alloc_size = sizeof(struct gpio_dev_priv),

};


Driver U_BOOT_DRIVER(s3c64xx_gpio)

struct driver _u_boot_list_2_driver_2_s3c64xx_gpio __attribute__((__aligned__(4))) __attribute__((unused, section('.u_boot_list_2_''driver''_2_''s3c64xx_gpio'))) = {

 .name = 's3c64xx_gpio',

 .id = UCLASS_GPIO,

 .of_match = s3c64xx_gpio_ids,

 .bind = s3c64xx_gpio_bind,

 .probe = s3c64xx_gpio_probe,


 .ops = &gpio_s3c_ops,

 .flags = (1 << 2),

};


Device U_BOOT_DEVICE(demo0)

设备树


初始化中对三个结构体群组的遍历

uclass

遍历:

struct uclass_driver *uclass = ll_entry_start(struct uclass_driver, uclass);

const int n_ents = ll_entry_count(struct uclass_driver, uclass);

struct uclass_driver *entry;

for (entry = uclass; entry != uclass + n_ents; entry++)

遍历的应用:

lists_uclass_lookup

Driver 

遍历:

struct driver *drv = ll_entry_start(struct driver, driver);

const int n_ents = ll_entry_count(struct driver, driver);

struct driver *entry;

for (entry = drv; entry != drv + n_ents; entry++)

遍历的应用:

lists_driver_lookup_name

Device 

遍历: for (offset = fdt_first_subnode(blob, offset); offset > 0; offset = fdt_next_subnode(blob, offset))

遍历2:

gpio_bank_t *base = (gpio_bank_t *)devfdt_get_addr(parent);

for (node = fdt_first_subnode(blob, dev_of_offset(parent)), bank = base; node > 0; node = fdt_next_subnode(blob, node), bank ++)

遍历的应用 : 

dm_scan_fdt_node

s3c64xx_gpio_bind


driver mode 的初始化 做了什么,简单描述

Uclass Driver Device 只是三个结构体群组

driver mode 的初始化作用是 将三个结构体建立联系 // device_bind _common 阶段实现driver 、uclass、device 三者的对接


uclass_driver 

主要的成员是 uclass_driver 和 dev_head 链表。

有一个全局变量 ./root.c:139:   INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);

dev_head 

是一个链表头, 用来链接该类下的所有设备。

可以通过 uclass_foreach_dev(dev, uc) 遍历该class 下的所有设备。


uclass_driver 

是针对某一类设备提供的通用操作接口

通过 UCLASS_DRIVER(_name) 宏申明

uclass 层通过 udevice->driver->ops 获取对应 driver 的操作接口。


driver

声明:

通过 U_BOOT_DRIVER(__name) 宏声明。

bind

如果 driver 实现了 bind 接口,则会被调用

probe

driver 一般都有对应的 probe 接口

通过 device_probe(struct udevice *dev) 调用

需要注意的是driver 的 bind 接口调用的比 probe 接口早, 大部分在 dm_init_and_scan 中就被调用了。

ops 

driver 一般会提供 ops 操作接口,供上一层调用。


dm_init_and_scan(bool pre_reloc_only) 

1.根据名称 (U_BOOT_DEVICE 中和 driver 的 name,或者 dts 和 driver 的 compatible) 匹配到对应的 driver

2.调用device_bind_common 函数

2.1.生成 udevice // dev = calloc(1, sizeof(struct udevice));

2.2.绑定 udevice 和 driver // dev->driver = drv;

2.3 绑定 udevice 和 uclass // dev->uclass = uc;

2.3.根据 driver 中的uclass id 找到对应的 uclass driver,并生成相应的 uclass, 并把该设备挂到 uclass 的dev_head之下 // ret = uclass_bind_device(dev);

2.4.调用 driver 的 bind 函数 // if (drv->bind) drv->bind(dev);


其他 

// 并不存在实际意义上的设备的驱动

  //例如MMC 子系统中的 mmc_blk 驱动

// 驱动位于抽象层,它不和具体的硬件设备直接交互,并不适合用一个 dts(dts 是用来描述具体的硬件信息的) 节点或者 U_BOOT_DEVICE(_name) 宏来为这个驱动显示的申明设备。

1. 该驱动主要是把所有的 mmc 设备注册到更上一层的 blk 子系统中

2. 向 blk 层提供操作 mmc 设备的 blk_ops,向下通过mmc uclass 提供的统一接口控制 mmc 设备。

3. 调用device_bind_xxx 系列 API 来完成驱动和设备和更上一层 uclass 之间的 bind


driver mode 的消费者(核心外围) 做了什么,简单描述

初始化后对三个结构体群组的遍历

// include/dm/uclass.h

uclass_foreach_dev

uclass_get_device_xxx 


做了什么工作(probe)


此时三者已经绑定成功


// 以串口为例

serial_init

serial_find_console_or_panic

serial_check_stdout

uclass_get_device_by_of_offset

uclass_get_device_tail

device_probe(dev);

if (dev->parent) device_probe(dev->parent);

if (drv->probe) drv->probe(dev);


driver 结构体中的 bind 和 probe 的区别

调用时序

bind : initf_dm中/initr_dm中 

// post_bind: Called after a new device is bound to this uclass

// device_bind_common 

probe   : initf_dm后/initr_dm后 

// post_probe: Called after a new device is probed

// 模块驱动(mmc/gpio/i2c/serial) lib 中提供的 函数A 封装了 device_probe

// 一般在 (mmc_initialize/serial_init) 的时候调用 该 函数A

功能:

bind : 一般是 xxx-uclass.c 提供的,驱动文件拿来 赋值 给 bind 成员

probe : 硬件初始化


driver mode 的消费者(真正消费者) 做了什么,简单描述

1. 通过 uclass_get_device_xxx 获取 device 句柄

2. 通过 uclass 提供的函数 yyy 来控制设备

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • 短波AM发射器电路设计图

  • 带有短路保护系统的5V直流稳压电源电路图

  • 如何调制IC555振荡器

  • 基于ICL296的大电流开关稳压器电源电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章