Linux设备树详解概述.docx
《Linux设备树详解概述.docx》由会员分享,可在线阅读,更多相关《Linux设备树详解概述.docx(130页珍藏版)》请在冰点文库上搜索。
![Linux设备树详解概述.docx](https://file1.bingdoc.com/fileroot1/2023-7/2/725a3e7f-4c7c-4ec0-99c3-7a967638d468/725a3e7f-4c7c-4ec0-99c3-7a967638d4681.gif)
Linux设备树详解概述
Linux设备树详解
概述
}设备树(DeviceTree)是一种描述硬件的数据结构,在操作系统(OS)引导
阶段进行设备初始化的时候,数据结构中的硬件信息被检测并传递给操作系统
最早诞生于OpenFirmware,FlattenedDeviceTree(FDT)格式标准。
}dts文件(DeviceTreeSource,dts)是以ASCII文本形式描述设备树内容。
}dtb文件是二进制格式,编译工具为:
DeviceTreeCompiler(DTC)。
}2011年被引入ARMLinux内核。
ARMLinux设备树描述了内核的软/硬件信息。
节点(node)和属性(property)
}节点用以归类描述一个硬件信息或是软件信息(好比文件系统的目录)
}节点内描述了一个或多个属性,属性是键值对(key/value),描述具体的
软/硬信息。
为什么ARMLinux社区会引入设备树呢?
}主要是想解决ARMLinux内核代码冗余的问题。
学习参考
内核源码目录Documentation\devicetree设备树范例的说明文档
内核源码drivers/of目录下是设备树操作实现源码
内核源码include/linux目录下的of_xxx.h是设备树的头
DTS描述键值对的语法:
}1、字符串信息
}2、32bits无符号整型数组信息
}3、二进制数数组
}4、混和形式
}5、字符串哈希表
/dts-v1/;
#include""//此设备树依赖于文件
#include//gpio引脚配置文件
/{//根节点rootnode
model="FriendlyARMTINY4412boardbasedonExynos4412";
compatible="friendlyarm,tiny4412","samsung,exynos4412","samsung,exynos4";
chosen{
stdout-path=&serial_0;
};
节点语法规范说明
节点名:
语法:
[@]
规范:
名字是ASCII字符串
(字母、数字、"-"、等等构成)
最长可以是31个字符
一般的,应该以设备类型命名
unit-address一般的是设备地址
/*****示例*****/
/{
serial@101F0000{
……
};
gpio@101F3000{
……
};
interrupt-controller@{
……
};
spi@{
…….
};
external-bus{
……
};
i2c@1,0{
…….
rtc@58{
......
};
};
};
};
节点名及节点路径
/{
…
dm9000{
…
};
…
};
节点名:
dm9000
节点路径:
/dm9000
节点别名(节点引用)
为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点
/{
aliases{
demo=&demo0;
};
…
demo:
demo0@{
…
};
…
};
节点名:
demo0
节点路径:
/demo0@
节点别名:
demo(等价/demo0@)
/**********************************/
引用语法范例1:
&demo{
…
};
引用语法范例2:
/{
reference-node{
property=<&demo>;
};
…
};
…
};
合并节点内容
一般的,一个硬件设备的部分信息不会变化,但是部分信息是可能会变化的,就出现了节点内容合并。
即:
先编写好节点,仅仅描述部分属性值;使用者后加一部分属性值。
在同级路径下,节点名相同的“两个”节点实际是一个节点。
/*参考板的已经编写好的node节点*/
/{
node{
property=value;
};
};
/*移植者添加的节点*/
/{
node{
property2=value;
};
};
/***合并后的节点内容***/
/{
node{
property2=value;
};
};
替换节点内容
一般的,一个硬件设备的部分属性信息可能会变化,但是设备树里面已经描述了所有的属性值,使用者可以添加已有的属性值,以替换原有的属性值,就出现了节点内容替换。
在同级路径下,节点名相同的“两个”节点实际是一个节点。
/*参考板的已经编写好的node节点*/
/{
node{
property=value;
status=”disabled”;/{
};node{
};property=value;
⇨status=”okay”;
/*移植者添加的node节点*/};
/{};
node{
status=”okay”;
};
};
引用节点内容
一般的,一个设备可能会使用到别的节点的内容,可以通过节点的别名来引用到其内容。
引用的目的可能是合并两个节点的内容、替换部分内容、或是使用部分内容。
/*参考板的已经编写好的node节点*/
/{
node:
node@{
property=value;
};
};
/*移植者添加的node节点*/
&node{
property=value;
status=“okay”;
}
/*移植者添加demo节点*/
/{
demo{
property=<&node>;
};
};
说明:
demo节点的属性property引用了节点的node的属性值,一般的,引用的目的是使用node节点的部分属性内容
chosen节点
}chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,譬如bootloader传递内核启动参数给内核
chosen{
bootargs=“root=/dev/nfsrwnfsroot=192.168.1.1console=ttyS0,115200”;
};
查找节点
}一般的,涉及设备、总线、驱动的概念,即所谓设备信息和驱动代码分离的驱动框架,如platform、i2c、usb、spi、pci、等等;或是分层驱动框架(MTD设备驱动、framebuffer设备驱动、input设备驱动、...),则设备树中设备节点的会内核初始化时候被查找到,驱动代码将不关心节点的查找。
}如果仅仅是接口驱动框架(字符设备驱动、块设备驱动、网络设备驱动),则需要使用内核节点查找函数查找设备树中的设备节点。
查找办法
}通过节点的compatible属性值查找指定节点
}通过节点名查找指定节点
}通过节点路径查找指定节点
节点描述
头文件:
include/of.h
structdevice_node{
constchar*name;//节点名
constchar*type;//设备类型
phandlephandle;
constchar*full_name;//全路径节点名
structfwnode_handlefwnode;
structproperty*properties;
structproperty*deadprops;/*removedproperties*/
structdevice_node*parent;////父节点指针
structdevice_node*child;//子节点指针
structdevice_node*sibling;
structkobjectkobj;
unsignedlong_flags;
void*data;
#ifdefined(CONFIG_SPARC)
constchar*path_component_name;
unsignedintunique_id;
structof_irq_controller*irq_trans;
#endif
};
功能:
通过compatible属性查找指定节点
structdevice_node*of_find_compatible_node(structdevice_node*from,
constchar*type,constchar*compat);
参数:
structdevice_node*from-指向开始路径的节点,如果为NULL,则从根节点开始
constchar*type-device_type设备类型,可以为NULL
constchar*compat-指向节点的compatible属性的值(字符串)的首地址
返回值:
成功:
得到节点的首地址;失败:
NULL
示例:
np=of_find_compatible_node(NULL,NULL,"fsl,imx23-digctl");
digctrl=of_iomap(np,0);
linux-3.12.10-ti2013.12.01\arch\arm\boot\dts\
digctl:
digctl@8001c000{
compatible="fsl,imx28-digctl","fsl,imx23-digctl";
reg=<0x8001c0000x2000>;
interrupts=<89>;
status="disabled";
};
功能:
设备ID表结构,用于匹配设备节点和驱动
structof_device_id{
charname[32];/*设备名*/
chartype[32];/*设备类型*/
charcompatible[128];/*用于与设备树compatible属性值匹配的字符串*/
constvoid*data;/*驱动私有数据*/
};
//注册支持设备树的设备ID表
include/module.h
MODULE_DEVICE_TABLE(of,ID表首地址)
示例:
staticDEFINE_PCI_DEVICE_TABLE(adl_pci6208_pci_table)={
{PCI_VDEVICE(ADLINK,0x6208),BOARD_PCI6208},
{PCI_VDEVICE(ADLINK,0x6216),BOARD_PCI6216},
{0}
};
MODULE_DEVICE_TABLE(pci,adl_pci6208_pci_table);
功能:
通过compatible属性查找指定节点
structdevice_node*of_find_matching_node(structdevice_node*from,
conststructof_device_id*matches);
参数:
structdevice_node*from-指向开始路径的节点,如果为NULL,则从根节点开始
conststructof_device_id*matches-指向设备ID表,注意ID表必须以NULL结束
示例:
conststructof_device_idmydemo_of_match[]={
{.compatible="fs4412,mydemo",},
{}
};
返回值:
成功:
得到节点的首地址;失败:
NULL
功能:
通过路径查找指定节点
structdevice_node*of_find_node_by_path(constchar*path);
参数:
constchar*path-带全路径的节点名,也可以是节点的别名
返回值:
成功:
得到节点的首地址;失败:
NULL
data->current_node=of_find_node_by_path("/");
功能:
通过节点名查找指定节点
structdevice_node*of_find_node_by_name(structdevice_node*from,
constchar*name);
参数:
structdevice_node*from-开始查找节点,如果为NULL,则从根节点开始
constchar*name-节点名
返回值:
成功:
得到节点的首地址;失败:
NULL
节点属性
有默认意义的属性
}1、设备树语法中已经定义好的,具有通用规范意义的属性。
}一般的,如果是设备信息和驱动分离框架的设备节点,则能够在内核初始化找到节点时候,自动解析生成相应的设备信息。
}常见属性的有:
compatible、地址address、中断interrupt
}ARMLinux内核定义好的,一类设备通用的有默认意义
的属性
}一般的,不能被内核自动解析生成相应的设备信息,但是内核已
经编写了相应的解析提取函数。
}常见属性的有:
MAC地址、GPIO口、clock、power、regulator、等等
驱动自定义属性
}针对具体设备,有部分属性很难通用,需要驱动自己定义好,通过内核的属性提取解析函数进行值的获得。
ethernet@{
compatible=“davicom,dm9000”;
reg=<0x0x20x0x2>;
interrupt=<74>;
local-mac-address=[0000deadbeef];
davicom,no-eeprom;
reset-gpios=<&gpf12GPIO_ACTIVE_LOW>;
vcc-supply=<ð0_power>;
};
compatible属性
一般的,用于匹配设备节点和设备驱动,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致,则节点的内容是给驱动的。
}设备树中的命名规范如下
/{
node{
compatible=“厂商名,名称”;
...
};
...
};
vcc-supply=<ð0_power>;
};
设备树示例
/{
…
mydemo{
compatible=“fs4412,mydemo”;
…
}
}
/*platform框架的探测函数*/
staticintdemo_probe(structplatform_device*devices)
{
//设备树对应节点的信息已经被内核构造成structplatform_devic
…
}
staticconststructof_device_iddemo_of_matches[]={
{.compatible=“fs4412,mydemo”,},
{}
}
MODULE_DEVICE_TABLE(of,demo_of_matches);
staticstructplatform_driverdemo_drv={
.driver={
.name=DEMONAME,
.owner=THIS_MODULE,
.of_match_table=of_match_ptr(demo_of)
}
}
属性-address
#address-cells:
描述子节点reg属性值的地址表中首地址cell数量
#size-cells:
描述子节点reg属性值的地址表中地址长度cell数量
reg:
描述地址表
/{
parent-node{
#address-cell=<1>;
#size-cells=<1>;
…
son-node{
reg=;
…
};
};
};
说明:
父节点#address-cells值为1,#size-cells值为1,则子节点中reg的值就是一个首地址紧接着一个地址上都为一个单元。
CPU地址描述
每个CPU都分配了唯一的一个ID,描述没有大小的CPUids
cpus{
#address-cells=<1>;
#size-cells=<0>;
cpu@0{
compatlibe=“arm,cortex-a9”;
reg=<0>;
};
cpu@1{
compatible=“arm,cortex-a9”;
reg=<1>;
};
};
内存映射设备(MemoryMappedDevices)
描述一个设备的内存地址的时候,一般使用1个cell(32bits)描述地址,紧接着1一个cell
(32bits)描述地址长度
/{
#address-cells=<1>;
#size-cells=<1>;
…
serial@101f0000{
compatible=“arm,p1011”;
reg=<0x101f00000x1000>;
};
gpio@101f3000{
compatible=“arm,p1061”;
reg=<0x101f30000x1000
0x101f40000x0010>;
};
spi@{
compatible=“arm,p1022”;
reg=<0x0x1000>;
};
…
};
非内存映射设备(NonMemoryMappedDevices)
}譬如i2c设备,有一个寻址地址,没有内存地址那样的地址长度和范围,一般使用1个cell(32bits)描述该地址,而没有描述地址长度的cell。
i2c@1,0{
compatible=“acme,a1234-i2c-bus”;
#address-cells=<1>;
#size-cells=<0>;
reg=<100x1000>;
rtc@58{
compatible=“maxim,ds1338”;
reg=<58>;
};
};
地址转换范围Ranges(AddressTranslation)
有些设备是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地
址映射范围。
/{
compatible=“acme,coyotes-revenge”;
#address-cells=<1>;
#size-cells=<1>;
…
external-bus{
#address-cells=<2>;
#size-cells=<1>;
ranges=<000x0x10000//片选1,ethernet
100x0x10000//片选2,i2c控制器
200x0x>;//片选3NORFLASH
ehternet@0,0{
compatible=“smc,smc91c1111”;
reg=<000x1000>;
};
};
};
说明:
片选0,偏移0(选中了网卡),被映射到CPU地址空间的0x~0x中,地址长度为0x10000
属性-interrupt
/{
compatible=“acme,coyotes-revenge”;
#address-cells=<1>;
#size-cells=<1>;
interrupt-parent=<&intc>;
interrupt-parent标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;
serial@101f0000{
compatible=“arm,p1011”;
reg=<0x101f00000x1000>;
interrupt=<10>;
interrupts一个中断标识符列表,表示每一个中断输出信号
};
intc:
interrupt-controller@
compatible=“arm,p1190”;
reg=<0x0x1000>;
interrupt-controller;
#interrupt-cells=<2>;
interrupt-controller一个空属性用来声明这个node接收中断信号;
#interrupt-cells这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;
};
};
一般的,如果父节点的#interrupt-cells的值是3,则子节点的interrupts一个cell三个32bits整型值:
<中断域中断触发方式>
}实际解析情况,得根据实际使用内核的设备树参加资料来决定。
/{
gic:
interrupt-controller@{
compatible=“arm,cortex-a9-gic”;
#interrupt-cells=<3>;
interrupt-controller;
cpu-offset=<0x4000>;
reg=<0x0x10000>,<0x0x10000>;
};
pinctl@{
gpx0:
gpx0{
gpio-controller;
#gpio-cells=<2>;S
interrupt-controller;
interrupt-parent=<&gic>;
interrupts=<0160>,<0170>,<0180>,<0190>,
<0200>,<0210>,<0220>,<0230>;
#interrupt-cells=<2>;
};
…
};
ethernet@{
compatible=“davicom,dm9000”;
reg=<0x0x20x0x2>;
interrupt-parent=<&pgx0>;
interrupts=<64>;
davicom,no-e