总体简述
在 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);
上一篇:OK6410A 开发板 (三) 20 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 system clock
下一篇:OK6410A 开发板 (三) 18 u-boot-2021.01 boot 解析 U-boot 镜像运行部分 env
推荐阅读最新更新时间:2024-11-11 20:23
设计资源 培训 开发板 精华推荐
- LT1172IS8、-5.2/1.25A 负降压转换器的典型应用
- 使用 Microchip Technology 的 ZL40120 的参考设计
- 使用 Analog Devices 的 LTC1046CS8 的参考设计
- EVAL-AD7699EDZ,用于 20 引脚 PulSAR AD7699 16 位 PulSAR 模数转换器系列的评估板
- 使用 Analog Devices 的 LTC3374AIFE 的参考设计
- MCP1630 镍氢电池充电器和电量计应用图的典型应用
- #第八届立创电赛#桌面小时钟
- LT6656ACDC-4.096、4.096V 低功率精密高压电源监视器的典型应用
- Galileo Gen 2,基于 Intel Quark SoC X1000 的开发板,32 位 Intel Pentium 处理器级片上系统 (SoC)
- DER-635 - 基于InnoSwitch3-MX和InnoMux芯片组,适用于LED电视的45 W多路输出电源