设备树小记(DeviceTree)


设备树(DeviceTree)

描述一个硬件平台的板级细节,设备树可以被 bootloader(uboot)传递到内核,内核从中获取设备树中的硬件信息。
Linux内核从3.x开始引入设备树的概念,用于实现驱动代码设备信息相分离。
在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。
引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。


可描述的信息

  • CPU数量和类别
  • 总线和桥
  • 中断控制器和中断使用情况
  • 内存及地址和大小
  • 外设连接
  • GPIO控制器和GPIO使用情况
  • CLOCK控制器和CLOCK使用情况

几个缩写理解

  • DTS:是指 .dts 格式的文件,是一种 ASII 文本格式的设备树描述,也是我们要编写的设备树源码,一般一个 .dts 文件对应一个硬件平台,位于 Linux 源码的 /arch/arm/boot/dts 目录下。
  • DTC:是指编译设备树源码的工具,一般情况下,需要手动安装这个编译工具。
  • DTB:是设备树源码编译生成的文件。
  • .dts:设备树源文件。
  • .dtsi:设备树头文件。
  • .dtb:设备树可执行文件。

设备树原理

在这里插入图片描述

(.dts为板级定义,.dsti为Soc级定义,一个Soc可以有多个不同的电路板,多个.dts的共同部分保保存至.dtsi中,类似.h文件)


语法结构

1个根节点“/”,根节点下一系列子节点,子节点也包含一系列子节点,各节点有一系列属性;

属性的值类型

  • 空属性
  • 字符串,使用双引号
  • 字符串列表,字符串之间逗号隔开
  • u32/u64(cells),使用尖括号
  • 二进制数,字节序列,使用方括号
  • 各种组合都会隔开

结构理解

  • { }包围起来的称之为节点,key=value代表节点属性,每一个设备节点都需要一个compatible属性;
  • 节点名:格式< name > [@unit_addr] 设备地址不同,节点名可相同;如果节点没有reg属性,则必须省略,@unit-addr。例如:apb@8000000{ };
    例外,chosen节点用于firmware传递数据给OS(bootloader传参内核):chosen{ bootargs = “console=ttyPS1,115200…”;};
  • 引用(取别名)
    (1) 声明:别名:节点名
    (2) 访问:<&别名>
    (3) 作用:查找方便,编译时,相同节点不同属性信息被合并,相同节点相同属性会被重写(覆盖)
    可在板级.dts文件中增改:&uart0{ status = “okay”; };
  • 节点属性property
    一般由key=value 键值对组成,节点属性类型挺多的

节点属性类型(标准属性)

规范定义的属性:compatible、addr、interrupt,内核初始化启动自动解析生成
内核定好的,通用的,有默认意义的属性,内核使用提取函数解析,“mac_addr”、“gpio”“clock”、“power”、“regulator”

compatible兼容性

设备节点一定要有该属性,这将作为驱动和设备的匹配依据,其值可不只有一个,匹配上了,可通过相应函数提取信息;
格式:compatible = “< manufacturer >,< model >”[,"< m >,< m >"];
厂家名,特定的设备型号,[ ]可选

  • 驱动中用于匹配的结构使用的compatible和设备树中的一模一样
  • 别名匹配对compatible中字符串里的第二个字段敏感
address地址属性

虽然i2c@021a0000,名字后面跟了地址,但正式设置地址在reg属性中

  • #address-cells=< CNT >,描述子节点中首地址的cell数量
  • #size-cells=< CNT >,描述子节点reg中地址长度的cell数量
  • reg=< addr length >:addr基地址,length为长度,两者个数可变,addr由父节点的#address-cells个u32值组成;lenth由父节点的#size-cells个u32值组成
    例如:节点A的#address-cells为2,#size-cells为1,则节点A的子节点B的reg为’<addr,addr,length>’
interrupt中断属性

一个计算机系统大量设备都是通过中断请求CPU服务的,因此设备节点中就需要指定中断号

  • interrupt-controller:一个空属性,声明此node接收中断号,此node是中断控制器
  • #interrupt-cells:中断控制器节点的属性,用于描述子节点中interrupt属性使用了父节点中interrupt属性的具体哪个值
    如父节点的该属性值为3,则interrupt一个cell为<中断域 中断 触发方式>,如父节点的该属性值为2,则是<中断 触发方式>
  • interrupt-parent:标识此设备节点属于哪一中断控制器,如未设置,则主动依附父节点
    (1) 当#interrupt-cells为3时,interrupt=<0,168,4>
    第一个cell为中断类型,0为SPI中断,1为PPI中断
    第二个cell代表具体的类型中断号:PPI:CPU私有外设中断[0-15];SPI:公用外设中断[0-987]
    第三个cell代表中断触发标志
    bit[3:0] 上升沿1 下降沿2 高电平4 低电平8
    bit[15:8] PPI中断CPU掩码
    (2) 当#interrupt-cells为2时,interrupt=<2,4>
    第一个cell为中断类型及中断号
    ID0-ID15:SGI中断(软件触发中断) ID16-ID31:PPI ID32-ID1019:SPI
    第二个cell同上cells为3时第三个cell含义
gpio属性
  • gpio-controller:用来说明该节点描述的是一个gpio控制器
  • #gpio-cells:用于描述gpio使用节点的属性一个cell的内容,即’属性=<&引用GPIO节点别名 GPIO标号 工作模式>’
驱动自定义key属性

基本设备节点类型

所有设备树文件均要包含一个根文件,并且所有设备树文件均应在根节点下存在以下节点:

  • 1个/cpus节点
  • 至少一个/memory节点
Root node

devicetree有一个单独的根节点,所有其他设备节点都是它的后代。根节点的完整路径为/。

/aliases节点

设备树文件可能具有一个别名节点(/aliases),该节点定义一个或多个别名属性。别名节点应位于设备树的根节点,并且具有节点名称/别名。/aliases节点的每个属性都定义了一个别名。属性名称指定别名。属性值指定设备树中节点的完整路径。例如,属性serial0 = "/simple-bus@fe000000/serial@llc500"定义了别名serial0

/memory节点

所有设备树都需要内存设备节点,并描述系统的物理内存布局。如果系统具有多个范围的内存,则可以创建多个内存节点,或者可以在单个内存节点的reg属性中指定范围。
例如:

memory {
       reg =  <0x40000000 0x10000000>;   起始地址0x40000000 长度0x10000000(32MB)
};
/chosen 节点

/chosen 节点不代表系统中的实际设备,而是描述了在运行时由系统固件选择或指定的参数。它应该是根节点的子节点。

chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
/cpus 节点

所有设备树均需要/cpus/cpu节点。它并不代表系统中的真实设备,而是作为代表系统cpu的子cpu节点的容器。


dtc命令

手动编译

dtc -I dts -O dtb -o B.dtb A.dts #编译
dtc -I dtb -O dts -o A.dts B.dtb #反编译

自动编译

make dtbs


常用设备树API

设备树节点结构体

struct device_node //结构体描述设备树节点
关注: const char *name; //节点名
const char *type; //设备类型
phandle phandle;
const char *full_name; //全路径节点名
struct fwnode_handle fwnode;

    struct	property *properties;   //属性结构体
    struct	device_node *parent;    //父节点
    struct	device_node *child;     //子节点
查找节点API
  • of_find_compatible_node(struct device_node *from,const char *type, const char *compatible);
    @from:指向开始路径节点,NULL为从根节点开始;@type:设备类型;@compat:设备树中该节点compatible属性值首地址

  • of_find_matching_node(struct device_node *from,const struct of_device_id *matches);
    @matches:指向设备ID表of_device_id,ID表必须以NULL结束

  • of_find_node_by_path(const char *path);
    @path:全路径节点名或引用

  • of_find_node_by_name(struct device_node *from, const char *name);
    @name:节点名查找

提取属性API
  • 提取指定属性的值:of_find_propety(const struct device_node *np,const char *name,int *lenp);
    @np:设备节点指针;@name:属性名称;@lenp:属性值字节数

  • 提取属性值中数据的数量:of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
    @elem_size:每个数据的单位(字节数)

  • 提取属性值中指定标号的32位数据值:of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);
    @index:标号;@out:输出参数

  • 提取属性值中的字符串:f_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
    @out_string:输出参数,执行字符串

提取addr属性API
  • 提取默认属性’#address_cells’和’#size_cells’
    int of_n_addr_cells(struct device_node *np);
    int of_n_size_cells(struct device_node *np);

  • 提取I/O地址:of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags);
    @size:I/O口地址长度

  • 提取resource:int of_address_to_resource(struct device_node *dev, int index,struct resource *r);

  • 提取gpio属性,得到gpio口编号:int of_get_named_gpio(struct device_node *np, const char *propname, int index);

  • 提取中断号:int of_irq_get(struct device_node *np, int index);
    @index:中断的编号;@返回值:中断号


设备树加载

准备 uboot传参两种方式
  • tag传递参数列表给Linux内核,只需知道内核的地址
    bootm < uImage_addr >
  • dtb镜像,使用设备树
    bootm < uImage_addr > < initrd_addr > < dtb_addr >
加载过程
  1. kernel 入口处获取uboot传来的dtb镜像基地址
  2. 通过early_init_dt_scan( )函数来获取kernel初始化时需要的bootargs和cmd_line等系统引导参数
  3. 调用unflatten_device_tree( )函数来解析dtb文件,构建一个由device_node结构连接而成的单向链表,并使用全局变量of_allnodes保存链表头指针
  4. 内核调用OF的API,获取of_allnodes链表信息来初始化内核其他子系统、设备等

设备树转换

dtb格式

在这里插入图片描述

  • alignment gap:对齐间隙
  • memory reserve map:描述保留的内存部分,被保留不应覆盖
  • device_tree structure:属性的value部分
  • device_tree strings:dtb中大量重复字符串,比如model,compatible等等,为节省空间,将这些字符串统一放在某个地址,需要时使用索引查看,一般为属性的key部分
dtb_header

整个dtb的大致描述信息

struct fdt_header {
	fdt32_t magic;			 /* magic word FDT_MAGIC 魔数,固定值,大端为0xd00dfeed,小端为0xedfe0dd0*/
	fdt32_t totalsize;		 /* total size of DT block 设备树大小,实际所占内存空间*/
	fdt32_t off_dt_struct;		 /* offset to structure struct部分所在内存相对头部的偏移*/
	fdt32_t off_dt_strings;		 /* offset to strings strings部分所在内存相对头部的偏移*/
	fdt32_t off_mem_rsvmap;		 /* offset to memory reserve map    memory reserve map部分所在内存相对头部的偏移*/
	fdt32_t version;		 /* format version 设备树版本*/
	fdt32_t last_comp_version;	 /* last compatible version */

	/* version 2 fields below */
	fdt32_t boot_cpuid_phys;	 /* Which physical CPU id we're
						booting on */
	/* version 3 fields below */
	fdt32_t size_dt_strings;	 /* size of the strings block   strings部分大小*/

	/* version 17 fields below */
	fdt32_t size_dt_struct;		 /* size of the structure block   struct部分大小*/
};

设备树转换过程

在这里插入图片描述

dtb—>device_node
在这里插入图片描述

  • setup_machine_fdt( ); //根据传入的dtb首地址完成一些初始化操作,总览信息
  • arm_memblock_init( ); //内存相关函数,为设备树保留相应的内存空间,保证不被覆盖
  • unflatten_device_tree( ); //对设备树的具体解析

device_node—>platform_device
在这里插入图片描述

  1. decide_node转换为platform_device的条件:
  • 节点中必须有compatible属性;
  • 只对根中的第一级节点(/xx)注册成platform_device,对它们的子节点并不处理;
  • 当一个节点的compatible属性有(“simple-bus”、“simple-mfd”、“isa”、“arm,amba-bus”)中特殊值之一,并且自己已成功注册platform_device,则其子节点(含compatible属性)的也可以转换为platform_device;
  • 根节点 / 是不会处理的。
  1. platform_device 与 devie_node的关联
    struct platform_device中含有struct device,struct device中含有struct device_node*:
  • 以platform_device.device->device_node关联
  • resources部分 I/O(reg属性)、中断(interrupt属性)—>(转换)—>struct resources

注意:并不是所有节点全都转换成platform_device平台总线设备的,总线I2C、SPI节点下的子节点不转换,而应该给对应的总线驱动来处理,这点下节讨论!

--有参考,侵删--

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值