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

  • 总体简述
在 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 %d\n", (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
    1. 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)
	设备树
    1. 初始化中对三个结构体群组的遍历
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
    1. 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 来控制设备

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值