目录
1、基础概念
1、总线
2、手机启动流程
1、MTK启动流程
2、高通启动流程的差别
3、设备树解析
1、设备树相关
2、设备树解析
4、 i2c 设备初始化流程
1、基础概念
1、总线
总线是连接多个设备或者接入点的数据传输通路。
老的电脑主机的都有PCI插槽类似现在的内存条,可以插声卡、网卡、视频采集卡等,是PC的万用插槽。这些设备与主机的通信就是走的PCI总线,但后来带宽跟不上,被淘汰了。
总线的英文为Bus,公共汽车线路,连接的设备是公交站,传输的数据包就是乘客。每个乘客都要知道自己从哪站上,到哪站下,然后等到站的时候就下去进入另一个设备进行处理。
公交车需要调度室,对应总线那就是控制器。
现阶段的SoC包括ARM,集成了大量的内总线:PCIE、USB、I2C、spi等,这些总线控制器已经集成到芯片内部,通过芯片引脚来连接外围器件。
2、手机启动流程
1、MTK启动流程
参考了:MTK bootloader 启动过程_mtk hypervisor_bobuddy的博客-CSDN博客
要完全将这张图看懂,还是需要一些功夫的。
首先需要了解的是这几种存储介质的区别:
首先要介绍一下,NOR Flash,ISRAM,NAND Flash,DRAM,我们需要回忆的是:Flash VS RAM,Flash是非易失性存储器,也即掉电数据不丢失,而RAM则相反。
Flash :NOR(或非) VS NAND(与非)这两者最大的区别是,CPU可以直接运行NOR上存储的程序,不需要额外的控制电路,而NAND不可以直接运行,并且其是按块进行访问的,比如其中一个典型的是eMMC,一般每块512字节。
RAM: RAM有两大类,一种称为静态RAM(Static RAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。
另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。
总结一下,读取速度排名 :SRAM > DRAM ≈ NOR Flash > NAND Flash,还有价格因素,SRAM比DRAM贵很多,NOR也比NAND贵很多。
现在就能理解为什么Boot Code要和LK、Kernel等img分开存放了。也能理解CPU内cache和内存的区别了。
然后还需要了解的是Boot Code、Pre-loader、ATF、LK、Kernel、Ramdisk分别是什么程序,有什么作用。
Boot Code:位于Boot ROM(NOR flash)可直接执行,可片上线性寻址,掉电不消失,这段程序可以初始化NAND,一般采用单线模式(类似串口协议)进行初始化,同时也初始化ISRAM(stack空间),在NAND初始化后将NAND上的Pre-loader加载到ISRAM(Internal SRAM)进行运行。
Pre-loader :可编程BootLoader,初始化硬件,UART,GPIO,DRAM,TIMER,RTC,PMIC 等等,建立起最基本的运行环境,最重要的就是初始化DRAM,加载image到内存,另外还需要创建C程序运行环境。
ATF:(Arm Trust Firmware)就是运行在EL3上的一个monitor。实现了REE(normal world)和TEE(secure world)的切换,在开机引导中负责Trusted OS的引导。ATF 包含了 service router、PSCI、Interrupt Handler 等功能, 该项目是开源的, 相关代码可以参
考 GitHub:GitHub - ARM-software/arm-trusted-firmware: Read-only mirror of Trusted Firmware-A 。arm-trusted-firmware会有几个阶段需要进行,分别叫做 BL1、BL2、BL31、BL32、BL33,这部分就不细说了。
LK:Little Kernel,也是一个开源项目,是一个小型操作系统,继续完成硬件初始化,使能MMU,设置启动模式,根据MAGIC()匹配dtb,加载并引导内核,这里先要知道boot.img 中包括 boot header, kernel,ramdisk,second stage ,device tree,LK需要解析boot.img并加载kernel和dtb。具体参考:https://www.cnblogs.com/ant-man/p/9102055.html
Kernel: 负责程序调度、各种硬件资源的管理,为应用程序提供基本的运行环境。其第一个执行代码为head.S ,第一个C代码为start_kernel。
Ramdisk:安卓根文件系统,ramdisk.img是编译后生成的/out/target/product/“generic”/root目录下经过打包压缩而成的。主要是存放android启动后第一个用户进程init可执行文件和init.*.rc等相关启动脚本以及sbin目录下的adbd工具。
再去看上面的流程图就很明了了。
2、高通启动流程的差别
高通平台的启动流程稍有不同,但是每个阶段做的工作基本是一致的,只是具体实现和叫法不一样。大致来说,都是先PBL(对应boot code)——SBL(对应pre-loader)——UEFI(对应 LK, XBL——ABL)——Kernel。
另外因为硬件不同每种芯片启动流程还有些不一样。参考:高通平台常用缩写:高通平台常用缩写_hlos operation_lalalalala的博客-CSDN博客
UEFI:英特尔开发的项目,针对X86启动的,是C语言开发的,采用了分层思想和,模块化设计。包含了上电时序、驱动实现、网络配置、类shell环境的建立等功能。
高通在MSM8998上引入了UEFI,用来代替LK。高通UEFI由XBL和ABL两部分组成。XBL负责芯片驱动及充电等核心应用功能。ABL包括芯片无关的应用如fastboot。LK的设备驱动都放在了XBL核心,Linux加载启动及fastboot等功能组件则作为独立的UEFI应用存在。
引入UEFI一个显而易见的好处是,初始化硬件部分的代码可以复用,XBL和SBL都能用,另外UEFI的可寻址范围更大,扩展性强,可裁剪性、等优点。
3、设备树解析
1、设备树相关
设备树主要是针对ARM平台的硬件配置而开发设计出来的,在引入设备树之前,关于硬件配置的代码直接写在内核中,后来由于硬件的型号、管脚越来越多,导致内核越来越臃肿,于是需要一个额外的image来负责存储配置硬件信息。
这样也是进一步地将硬件与软件进行解耦了。
直观地说,在内核中看到的dts 、dtsi即为我们编写的设备树文件,而dtb(device tree blob)、dtbo则为编译后的设备树文件,dtb.img、dtbo.img则为将整个系统dtb文件打包后的文件。
如果我们要编写设备树文件,建议参考官方文档:Device Tree Usage - eLinux.org, 进一步了解参考Linux文档:Linux and the Devicetree — The Linux Kernel documentation
一般需要了解的基础内容包括:根节点、属性 、compatible、reg等,具体就不细讲了这方面参考资料很多。
需要补充几点的是:
一个大括号{ } 编译后对应一个设备节点,另外我们看到源码中很多dts文件,很多文件都有根节点,其实有很多是引用,编译器会进行整合,最终编译出来的dtb.img只有一个根节点,整个系统为一个树状结构。
另外需要知道几个设备树相关的文件,可以打开adb shell进行对照查看。
进入/sys/firmware目录后便可看到二个文件,一个是devicetree文件夹,另一个是fdt(原始dtb文件,可以用hexdump -C fdt 将其打印出来查看就会发现里面的数据和dtb文件是一致的)。
/sys/firmware/devicetree:以目录结构呈现的dtb文件。 根节点对应base目录,每一个节点对应一个目录, 每一个属性对应一个文件
/sys/devices/platform:系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的。对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性(例如进入/sys/devices/platform/led/后若发现该目录下有of_node节点,就表明该platform_device来自设备树)
2、设备树解析
简单了解设备树后,看看设备树在内核中是如何解析的:
设备树的解析就是要将dtb中的fdt(扁平设备树)最终解析成platform_device,这里面主要分为两个步骤,第一步是将fdt解析成device_node,第二步是将device_node转换为platform_device,这两步对应上图中start_kernel的两个箭头。调用流程以4.19版的kernel为基准。
首先需要看的是device_node结构体:
|
将dtb转换成device_node的具体流程如下:需要注意的是下图是arm32位的流程,64位的代码有一些不一样,比如就没有machine_desc相关内容。
总结一下就是:
1、在setup_machine_fdt内进行了设备树的初次扫描,获取了一些总览信息,验证设备树地址信息,获取cmdline作为启动参数、读取根节点信息等;
2、arm_memlock_init 为设备树的相关内存需求保留内存空间;
3、unflatten_device_tree是真正进行设备树解析的函数,主要填充节点的工作在populate_node内完成。
转换为device_node后,就需要考虑如何转换为platform_device,如何挂载到platform总线上。
这里需要理解一下platform总线,platform总线对应的是在SoC内集成的独立外设控制器和挂接在SoC内存空间的外设,被称为虚拟总线,也就对应之前说的SoC内总线。
首先需要明确的是,不是所有的device_node都会进行转换,所以就有了以下的转换条件:
1.具有compatible属性;2.一般只对根节点的一级子节点进行转换,二级子节点不转换,除非是一级子节点的compatible属性为"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus",
这几种是arm的片上总线,挂接在上面的设备也被称为platform_device;
没有被转换为platform_device的节点怎么处理由其父节点的platform_driver来决定。
转换的过程:首先要看下面platform_device结构体的内容
|
这里需要了解initcall机制:
在start_kernel函数的最后会调用rest_init函数,掉用到do_initcalls会去按顺序加载initcall宏定义的函数。
之后的流程如下(只介绍关键步骤):
4、 i2c 设备初始化流程
介绍i2c设备注册匹配流程
i2c_client
i2c_adaptor
i2c_driver
介绍相关函数以及使用 如 i2c_master_send,i2c_master_recv,i2c_add_driver,i2c_del_driver,i2c_device_id
attr i2c_transfer algorithm 等
以及相关
从使用中去理解背后原理。
platform_driver的注册是通过module_platform_driver宏函数来进行的:
然后通过module_driver来添加注册和注销的函数(采用名字加init,exit的形式),最终还是调用module_init宏进行注册;
#define module_init(x) __initcall(x)
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn,6)
这个初始化在arch_initcall之后;
i2c_driver、spi_driver、usb_driver、pci_driver的注册也是走module_driver这个流程,所以说这些驱动地位对等。
针对于i2c节点, i2c控制器, 它会被转换为platform_device, 在内核需要编写对应的platform_driver程序,在实现probe函数的时候,需要调用
i2c_add_numbered_adapter函数,该函数会在i2c总线上注册适配器(adapter),并且将设备树中的i2c子节点转换成i2c_client
Kernel-设备驱动注册 | Rocky_Ansi Blog
设备树的compatible的节点兼容性文章来源:https://www.toymoban.com/news/detail-768187.html
Device Tree(三):代码分析文章来源地址https://www.toymoban.com/news/detail-768187.html
到了这里,关于设备树解析 & i2c设备模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!