指纹驱动初始化引发的思考

缩略语清单: 

c963a6ed4025c3d9ee941bb3ab3efc66.png

写在前边    

本文将从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。    

3c63de88f2146aae2e58781778597cbc.jpeg

图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

a72fcfffa6ff1abdbac95831c9bb9906.png

接着跳转到第1个c 语言函数start_kernel开始执行,部分代码如下:

//kernel-5.10/init/main.c

482cbe439367a197408ecce112abaff6.png

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 按照一定的顺序排列在代码段中       

3e36e66fd7cf1b24bc22b759041bb94c.png

 于是,各驱动模块的initcall函数依次执行:

d5905ca7146f10d58ea66c8f2a80fd2c.png

对于指纹驱动,一般会有如此定义: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相关代码:

7b06960a96b44fe3fe278d6733c07d33.png

->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变量,包括以下成员

0f0df840fee7aa485363e5b4c1c017a7.png

lname: 驱动名字    

lof_match_table: 用于匹配的表

lprobe:对应指纹fp_probe

lremove:移除时调用,暂不关注

在driver_register中,会对所有的设备进行attach操作,然后通过总线匹配操作(bus->match),去匹配驱动和设备name,这里的设备名是DTS中设置的,如果匹配上,就进行probe操作。

f2f3ab1c61c87b148dc73b0f8b7bd33c.png

【注】匹配二字,说明有多个设备,在一个链表上,即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节占的开机初始化,有以下调用:

e483a74c84d9b3f28a635306a65b6020.png

  接着梳理:总体上分两个阶段

//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,其定义如下:

d8a4f28f1f9078633147bebeb5c07aa7.png

第二阶段: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等)时也会用到。

88a1372ae91a34fd6a056db0b41e84fa.png

  这样platform驱动通过总线操作匹配platform设备时,就可以通过定义的匹配表(of_match_table)和设备名DTS(compatible字段)进行匹配,一旦匹配成功,会执行驱动的probe函数,即fp_probe。      

设计真是巧妙,驱动、设备、总线三者完美配合!

5 driver与DTS匹配方式 

 

感叹完驱动、设备、总线的设计后,剩下最后一个问题,驱动和设备匹配方式有哪些?         

直接看内核的匹配函数实现:

7b02e03ef8c8dca4954955e0163debc2.png

详细看下match的规则,kernel-5.10/drivers/base/platform.c

35eed61632f72c2223289c142dd0bd1d.png

其代码流程表明有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内核并发与同步机制解读(arm64)上

Linux内核并发与同步机制解读(arm64)下

crash实战:手把手教你使用crash分析内核dump

b43ca8aabae821355acf0e433ec17082.gif

长按关注内核工匠微信

Linux内核黑科技| 技术文章| 精选教程

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内核工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值