Linux3.x 以后引入了设备树,用于描述一个硬件平台的板级细节。
设备树可以被 bootloader(uboot)传递到内核,内核从中获取设备树中的硬件信息。
设备树的两个特点:
几个常用的缩写:
设备树是由 一个根节点 和 多个子节点 组成。
/dts-v1/; // 表示版本
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};
node为设备树中的基本单元。格式为:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
0-9 a-z A-Z , . _ + -
组成,且开头只能是大小写字母。
/
来表示。@
为分隔符。
node-name@unit-address
整体同级唯一就是 naem = value。
[label:] property-name;
[label:] property-name = value;
clock-frequency = <0x00000001 0x00000000>;
compatible = "lzm-bus";
local-mac-address = [00 00 12 34 56 78]; // 每个 byte 使用 2 个 16 进制数来表示
local-mac-address = [000012345678]; // 每个 byte 使用 2 个 16 进制数来表示
example = <0x84218421 23>, "hello world";
一般设备树都不需要从零开始写,只需要包含芯片厂商提供的设备树模板,然后再添加,修改即可。
dts 可以包含 dtsi 文件,也可以包含 .h 文件。.h 文件可以定义一些宏。
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
……
};
修改、追加设备树节点都可在文件末尾或新文件修改或追加。
而修改节点,可参考以下两种方法:标签法 或 全路径法:
标签法:
// 方法一:在根节点之外使用标签引用节点
&red_led
{
status = "okay";
}
// 方法二:使用全路径引用节点
&{/led@0x020C406C}
{
status = "okay";
}
全路径法:
在节点 {} 中包含的内容时节点属性。这些属性信息就是板级硬件描述的信息,驱动会通过内核提供的 API 去获取这些资源信息。
注意:节点属性可分为 标准属性 和 自定义属性,即是可以自行添加属性。
compatible 属性:
compatible = "A", "B", "C";
。model 属性:
model = "lzm com,IMX6U-V1";
status 属性:
value | description |
---|---|
okay | 设备正常 |
disabled | 设备不可操作,但后面可恢复正常 |
fail | 发生严重错误,需要修复 |
fail-sss | 发生严重错误,需要修复。sss 表示错误信息 |
#address-cells、#size-cells 属性:
reg 属性:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
ranges 属性:
ranges=<0x05 0x10 0x20>
name、ldevice_type 属性:
名称及内容可自定义,但是名称不能与标准属性重名。获取方式,后述。
根节点:
CPU:
memory:
chosen:
chosen
{
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
aliases:
aliases {
can0 = &flexcan1;
gpio0 = &gpio1;
}
一般的程序猿会修改设备树即可,不必从零开始。
编译时需要设置一下三个环境变量 ARCH、CROSS_COMPILE、PATH。
在开发环境中进入板子对应的内核源码目录,使用内核中的 makefile 即可,执行如下命令来编译 dtb 文件:
make dtbs V=1
上述命令是单独编译设备树。
会编译以下设备树:
在arch/arm/Makefile 或 arch/arm/boot/Makefile 或 arch/arm/boot/dts/Makefile
等相关 Makefile 中找到 dtb-$(xxx)
,该值包含的就是要编译的 dtb 。
如该文件中宏 dtb-$(CONFIG_SOC_XXX) 包含的 .dtb 就会被编译出来。
如果想编译自己的设备树,添加该值内容,并把自己的设备树放在 arch/arm/boot/dts
下即可。
(具体查看该 arch/arm/boot/Makefile 内容)
意思是手工使用 dtc 工具直接编译。
dtc 工具存放于内核目录 scripts/dtc 下。
若直接使用 dtc 工具手工编译的话,包含其它文件时不能使用 #include
,而必须使用 /include
。
#include
是因为使用了 交叉编译链。编译、反编译的示例命令如下,-I 指定输入格式,-O 指定输出格式,-o 指定输出文件:
./scripts/dtc/dtc -I dts -O dtb -o tmp.dtb arch/arm/boot/dts/xxx.dts // 编译 dts 为 dtb
./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/xxx.dtb // 反编译 dtb 为 dts
一般步骤:
make dtbs
。目录 /sys/firmware/devicetree 下以目录结构呈现的 dtb 文件。
目录 /sys/firmware/fdt 文件,就是 dtb 格式的设备树文件。
设备树生命过程:
DTS --(PC)--> DTB --(内核)--> device_node -·(内核)·-> platform_device。
流程:
对于 device_node 和 platform_device,建议去内核源码看看它们的成员。
simple-bus
;simple-mfd
;isa
;arm,amba-bus
。在驱动程序中,内核加载设备树后。可以通过以下函数获取到设备树节点中的资源信息。
获取节点函数及获取节点内容函数称为 of 函数。
device_node 结构体如下:
struct device_node
{
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#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
};
of_device_id 结构体如下:
/* Struct used for matching a device */
struct of_device_id
{
char name[32];
char type[32];
char compatible[128];
const void *data;
};
of_find_node_by_path():
struct device_node *of_find_node_by_path(const char *path)
。of_find_node_by_type():
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
。of_find_compatible_node():
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
。of_find_matching_node_and_match():
struct inline device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
。of_get_parent():
struct device_node *of_get_parent(const struct device_node *node)
。of_get_child():
struct device_node *of_get_child(const struct device_node *node, struct device_node *prev)
。property:
struct property
{
char *name;
int length;
void *value;
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
resource 结构体:
struct resource
{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
of_find_property():
函数原型:struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
源码路径:内核源码/include/linux/of.h。
np:设备节点。
name:属性名称。
lenp:实际获得属性值的长度(函数输出参数)。
返回值:
可以了解下 获取属性值函数 of_get_property() ,与 of_find_property() 的区别是一个返回属性值,一个返回属性结构体。
of_property_read_u8_array:
//8位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//16位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
//32位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
//64位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
of_property_read_u8:
//8位整数读取函数
int of_property_read_u8 (const struct device_node *np, const char *propname,u8 *out_values)
//16位整数读取函数
int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)
//32位整数读取函数
int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)
//64位整数读取函数
int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values)
of_property_read_string_index:(推荐)
int of_property_read_string_index(const struct device_node *np,const char *propname, int index, const char **out_string)
of_property_read_string:(不推荐)
int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)
of_property_read_bool():
static inline bool of_property_read_bool(const struct device_node *np, const char *propname)
设备树提供寄存器的地址段,但是一般情况下都会使用 ioremap 映射为虚拟地址使用。
of_address_to_resource 只是获取 reg 的值,也就是寄存器值。
of_iomap 函数就是获取 reg 属性值&指定哪一段内存&映射为虚拟地址。
of_address_to_resource:
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
内核源码/drivers/of/address.c
。of_iomap:
void __iomem *of_iomap(struct device_node *np, int index)
原文:https://www.cnblogs.com/lizhuming/p/14621305.html