设备树解析 & i2c设备模型

本文详细介绍了手机启动过程中涉及的关键概念,如总线的类型及其工作原理,MTK和高通平台的启动流程,以及设备树在ARM平台硬件配置中的作用。还探讨了i2c设备的初始化流程,包括i2c_client、i2c_adaptor和i2c_driver的交互。
摘要由CSDN通过智能技术生成

目录

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结构体:

struct device_node{

    const char *name;           //节点名称 节点的name属性转换而来

    const char *type;           //节点类型 节点的device_type转换而来 比如 cpu soc memory serial等

    phandle phandle;            //由节点的phandle和"linux,phandle"属性转换而来,自引用的节点标记

    const char *full_name;      // 从“/”开始,表示该node的full path

    struct fwnode_handle fwnode;

    struct property *properties;    //这是一个设备树节点的属性链表,属性可能有很多种,比如:"interrupts","timer","hwmods"等等

    struct property *deadprops;     //如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表

    struct device_node *parent;     //父节点   组成一颗树

    struct device_node *child;

    struct device_node *sibling;

#if defined(CONFIG_OF_OBJ)

    struct kobject kobj;            //用于在/sys目录下生成相应用户文件

#endif

    unsigned long _flags;

    void *data;

#if defined(CONFIG_SPARC)

    const char *path_component_name;

    unsigned int unique_id;

    struct of_irq_controller *irq_trans;

#endif

}

将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结构体的内容

struct platform_device {

    const char  *name;          //设备名称,该名称在注册时,会拷贝到dev.init_name中

    int     id;                 //用于标识设备的ID,在设备与驱动匹配的时候,如果遇到同名的设备,需要用到

    bool        id_auto;

    struct device   dev;        //platform_device 其核心逻辑还是一个device,

    u32     num_resources;      //

    struct resource *resource;  //是主要进行转换的内容,描述设备的资源

    const struct platform_device_id *id_entry;

    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */

    struct mfd_cell *mfd_cell;

    /* arch specific additions */

    struct pdev_archdata    archdata;

};

这里需要了解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的节点兼容性

Device Tree(三):代码分析

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值