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 来控制设备
上一篇:OK6410A 开发板 (三) 20 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 system clock
下一篇:OK6410A 开发板 (三) 18 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 env
- 〖Linux〗OK6410a蜂鸣器的驱动程序编写全程实录
- 开发环境搭建 (一) OK6410A 开发环境 1官方环境 OK
- 开发环境搭建 (一) OK6410A 开发环境 2 更改环境 OK
- 开发环境搭建 (一) OK6410A 开发环境 3 更改环境 FAIL
- 开发环境搭建 (二) OK6410A 开发环境 其他
- 开发环境搭建 (一) OK6410A 开发环境 4 更改环境 OK
- OK6410A 开发板 (二) 环境熟悉
- OK6410A 开发板 (三) u-boot-1.1.6 boot 解析
- OK6410A 开发板 (四) OK6410A 裸机代码
- OK6410A 开发板 (五) u-boot-2021.01 移植 到 ok6410a