缩略语清单:
写在前边
本文将从Android上指纹驱动初始化为切入点,然后详细分析从开机到初始化整个过程的详细流程,最终将Linux platform device和DTS流程和关系梳理清楚,并类比到其他设计驱动设备。
fp_probe这个函数每次在系统初始化后都会自动执行,主要完成指纹设备相关结构体的初始化,注册其他模块的通知链等。随后在fp_open中,会去解析配置的DTS(设备树),获取指纹sensor引脚配置,根据这些配置进一步去做上电、复位等时序控制,最终让指纹sensor处于正常工作状态中。
指纹初始化这段流程看似再正常不过,而且工作得也比较好,那问题在哪儿呢?总感觉有些环节来得太容易了,反而不太踏实,比如:
1)fp_probe什么时候执行?
2)fp_probe执行时,指纹DST配置发生了什么?
3)指纹驱动和指纹DTS配置匹配方式有哪些?
为理清以上几个问题,决定从Linux内核5.10源码中去寻找答案,于是开始了下文。
1 开机初始化过程
这一问涉及Linux/Android系统的开机初始化,以MTK Android手机平台为例,主要流程如下图1。
图1.开机初始化流程
步骤1-3:设备上电,PC指针跳转到Boot ROM中的boot code开始执行,主要将ROM中的pre-loader代码加载到ISRAM中执行。
步骤4-6:pre-loader主要工作是初始化DRAM,然后加载ATF代码到DRAM中执行
【注】需要平台使能EL3,否则pre-loader将直接加载LK
步骤7-8:ATF加载LK代码到DRAM中执行
步骤9-12:LK解压bootimage,并将解压后的产物Linux kernel和Ramdisk加载到DRAM中,最后LK跳转到Linux kernel进行初始化。
2 fp_init执行时机
在第1节中提到Linux kernel开始执行,部分代码如下,主要是准备好c语言运行时环境,包括初始化init_task栈,设置VBAR_EL1,保存FDT地址,计算kimage_voffset,清空bss段,及根据配置开启VHE等。
kernel-5.10/arch/arm64/kernel/head.S
接着跳转到第1个c 语言函数start_kernel开始执行,部分代码如下://kernel-5.10/init/main.c
start_kernel这个函数做的事情非常多,和fp_probe相关的流程如下:
start_kernel -> arch_call_rest_init(void)
-> rest_init()
-> kernel_init(void *unused)
-> do_basic_setup()
->do_initcalls()
->do_initcall_level(int level, char *command_line)
->do_one_initcall( )
在do_initcall_level函数中,根据不同段section的顺序,依次调整入口函数。
【关于顺序】kernel 编译时创建了很多 section(例如 .initcall0、.initcall1、.initcall1s 等),这些 section 按照一定的顺序排列在代码段中
于是,各驱动模块的initcall函数依次执行:
对于指纹驱动,一般会有如此定义:xxx_initcall(fp_init);在do_one_initcall( )中会调用到fp_init,然后通过module_platform_driver()函数将指纹驱动注册成平台驱动类型,到这里fp_init执行时机相对是比较清楚的,各驱动工程师也很容易梳理其中的调用关系,不过fp_probe的执行时机相对复杂一点。
3 fp_probe执行时机
当前的线索来到了platform driver的注册和初始化这里,其中是通过怎样的流程,最终调用到指纹的fp_probe函数的,本节将继续沿坡讨源,虽幽必显。
对于platform driver,register相关代码:
->platform_driver_register( ) //宏定义,实际调用如下
->__platform_driver_register(struct platform_driver *drv, struct module *owner)
->driver_register(&drv->driver);
->driver_find(drv->name, drv->bus); // 表示 platform_driver 结构体中.driver.name
->bus_add_driver(drv);
说明:
1)对于platform driver,会定义一个结构体platform_driver变量,包括以下成员
lname: 驱动名字
lof_match_table: 用于匹配的表
lprobe:对应指纹fp_probe
lremove:移除时调用,暂不关注
在driver_register中,会对所有的设备进行attach操作,然后通过总线匹配操作(bus->match),去匹配驱动和设备name,这里的设备名是DTS中设置的,如果匹配上,就进行probe操作。
【注】匹配二字,说明有多个设备,在一个链表上,即bus_for_each_dev遍历时的链表klist_devices。
->driver_attach(drv);
->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
->fn(dev, data); //对每个platform bus的设备进行__driver_attach
->__driver_attach(struct device *dev, void *data)
->driver_match_device(drv, dev); //匹配驱动和设备name
->drv->bus->match ? drv->bus->match(dev, drv) : 1;
->device_driver_attach(drv, dev);
->driver_probe_device(drv, dev);
//kernel-5.10/drivers/base/dd.c
->really_probe(dev, drv);
->dev->bus->probe(dev);
//将会调用FP设备注册的probe函数
->drv->probe(dev);
->module_add_driver(drv->owner, drv);
->driver_create_file(drv, &driver_attr_uevent);
->dev->bus->probe(dev)将会调用FP设备注册的probe函数,即fp_probe,到这里,终于弄清楚了fp_probe调用时机,差不多20个函数调用,藏得比较深!
搞清楚这个问题后,接着下个问题,此时指纹的DTS在做什么?为何这里能匹配到DTS中的设备名?
4 DTS执行时机
为搞清楚这个问题,需要重新溯源,这也侧面反应了是一个比较复杂的调用逻辑。
回到第1节占的开机初始化,有以下调用:
接着梳理:总体上分两个阶段
//kernel-5.10/arch/arm64/kernel/setup.c
第一阶段:解析DTS文件,生成device_node节点
-> setup_arch( )
->setup_machine_fdt (__fdt_pointer) //用于解析传递给内核的DTB
-> of_flat_dt_get_machine_name( ) //获取DTS文件中'/'node下的compatible属性值
->of_get_flat_dt_prop(dt_root, "compatible", NULL);
->unflatten_device_tree( )
->unflatten_dt_nodes(blob, mem, dad, mynodes);
unflatten_device_tree解析DTS并创建device_node,每个dts节点对应一个device_node,其定义如下:
第二阶段:device_node转变成platform_device
系统启动时,会依次调用init段中的函数,如第2节所分析,整体调用过程:
start_kernel( )
-> rest_init( )
-> kernel_thread(kernel_init, NULL, CLONE_FS)
-> kernel_init( )
-> kernel_init_freeable( )
-> do_basic_setup( )
-> do_initcalls( )
//kernel-5.10/drivers/of/platform.c
关键的声明:arch_initcall_sync(of_platform_default_populate_init);
根据这个声明,在do_initcalls中会调用of_platform_default_populate_init函数
of_platform_default_populate_init(void)
->of_platform_device_create(node, NULL, NULL)
->of_platform_device_create_pdata(np, bus_id, NULL, parent)
->of_device_alloc(np, bus_id, parent);
->platform_device_alloc("", PLATFORM_DEVID_NONE);
->dev->dev.parent = parent ? : &platform_bus; //设置platform device的parent
->dev->dev.bus = &platform_bus_type;
->platform_device_put(dev);
->of_platform_default_populate(NULL, NULL, NULL);
->of_platform_populate(root, of_default_bus_match_table, lookup,parent);
完成上述操作后,DTS文件描述的device_node都转换成了platform_device,并注册到platform_bus_type.klist_devices链表上,即第2节的bus_for_each_dev遍历时的链表klist_devices。
klist_devices链表:在总线操作(bus_add_driver、bus_add_device等)时也会用到。
这样platform驱动通过总线操作匹配platform设备时,就可以通过定义的匹配表(of_match_table)和设备名DTS(compatible字段)进行匹配,一旦匹配成功,会执行驱动的probe函数,即fp_probe。
设计真是巧妙,驱动、设备、总线三者完美配合!
5 driver与DTS匹配方式
感叹完驱动、设备、总线的设计后,剩下最后一个问题,驱动和设备匹配方式有哪些?
直接看内核的匹配函数实现:
详细看下match的规则,kernel-5.10/drivers/base/platform.c
其代码流程表明有3种解锁匹配的方法:
① of_match_table
该方法匹配优先级最高,即匹配DTS中的compatible属性和驱动中fp_match_table定义的compatible字符串是否一样
② ID table
第二优先级,定义platform_device_id数组,并声明MODULE_DEVICE_TABLE(platform, tbl),然后和DTS中的compatible进行匹配
③ name
第三优化先级,使用platform_driver的name字段与DTS的compatible进行匹配
原来如此,代码已说明一切!!
6 结束语
本文写到这儿,算是告一段落了,总结一下:这篇文章从指纹的驱动常见入口函数开始,通过Linux内核开机溯源,梳理了fp_init、fp_probe和DTS初始化相关知识和流程,同时分析了相关的关键调用。进一步可以类比到其他的platform device和DTS相关初始流程,达到举一反三,触类旁通的效果,希望对读者有所帮助。
【参考文章】:
https://zhuanlan.zhihu.com/p/615272622?utm_id=0
https://www.cnblogs.com/kingdix10/p/15881420.html
【源码获取】:
Linux内核源码:
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/?h=linux-5.10.y
往
期
推
荐
长按关注内核工匠微信
Linux内核黑科技| 技术文章| 精选教程