Linux 设备树语法(.dts)及如何从设备树获取节点信息
设备树简介
一个设备信息用树形结构表示如下:
如何用设备树进行描述呢?
/{ // 表示root节点
cpu{ // cpu节点
name = val; // 属性名name,val是属性值。val形式:1)"string"(双引号括起来);2)<u32 u32 u32> (尖括号,有多少个32位就放多少个,空格间隔);3) [12 34 56](16进制单字节)。val可以是这3种形式组合,如<0x123>,"abcd",[34]
};
memory{ // 内存节点
};
I2C{ // I2C控制器节点
};
};
设备树中基本单元,称为“node”(节点)。基本格式:
[label:]node-name[@unit-address]{ // node-name:节点名,unit-address:节点地址;label是标号,方便引用节点
[properties definitions] // 各种属性
[child nodes] // 子节点
};
label是标号,方便引用节点。例如,定义一个label uart0,
/dts-v1/; // 表示版本
/{
uart0:uart@fe001000{
compatible="ns16550"
reg=<0xfe001000 0x100>
};
};
有两种办法可以修改node uart@fe001000:
// 1. 在根节点外使用label引用node
&uart0{ // 通过label
status="disabled";
};
// 2. 在根节点外使用全路径
&{/uart@fe0020000} {
status="disabled";
};
常用节点
1)根节点
dts文件必须有一个根节点
/dts-v1/;
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-celss = <1>;
};
根节点中必须有这些属性:
#address-cells // 在其子节点reg属性中,用多少个u32整数来描述地址(address)
#size-celss // 在其子节点reg属性中,用多少个u32整数来描述大小(size)
compatible // 定义一系列的字符串,用来指定内核中哪个machine_desc可以支持本设备,即这个板子兼容哪些平台,可以直接用这个平台的驱动来初始化当前板子
// uImage: smdk2410 smdk2440 mini2440 ==> machine_desc
model // 我们这个板子是什么名字?
// 如有2款板子配置基于一致,compatible一样,就通过model来区分这两款板子
2)CPU节点
一个设备肯定有CPU。一般不需要我们设置,dtsi文件中定义好了
cpus{
#address-cells=<1>;
#size-cells=<0>;
cpu0:cpu@0{ //多核, 可能还要cpu1, cpu2, cpu3, ...
...
};
};
3)memory节点
芯片厂家不可能事先确定你的板子用多大内存,所以memory节点需要板厂设置。
memory{
reg=<0x80000000 0x20000000>; // 起始地址0x80000000,大小500MB
};
4)chosen节点
可通过设备树文件给内核传入一些参数,在chosen节点中设置bootargs属性。chosen节点是虚拟节点,不对应实际设备。
chosen{
bootargs="noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; // “bootargs”就是传递给内核的参数,“root=”告诉内核其哪个分区找到根文件系统,“init=:”启动哪个应用程序,“console=”使用哪个串口
};
常用属性
1)#address-cells,#size-celss
cell指定一个32位的数值,
address-cells:address要用多少个32位数来表示;
size-cells:size要用多少个32位数来表示。
例如,一段内存,怎么描述它的起始地址 + 大小?
下例中,address-cells为1,所以reg中用1个数(u32)表示地址,即用0x80000000来表示地址;size-cells为1,所以reg用1个数来表示大小,即用0x20000000表示大小:
/{
#address-cells=<1>;
#size-cells=<1>;
memory{
reg=<0x80000000 0x20000000>; // 起始地址0x80000000,大小0x20000000 byte
};
};
2)compatible
“compatible”表示兼容,对于某个LED,内核可能有3个驱动A、B、C都支持它,那么可以这样写:
led{
compatible="A","B","C";
};
内核启动时,就会为这个LED按这样的优先顺序为它找到驱动程序A、B、C。
根节点下面也有compatible属性,用来选择哪个“machine desc”:一个内核可以支持machine A,也支持machine B,内核启动后会根据节点的compatible属性找到对应的machine desc结构体,执行其中的初始化函数。
compatible的值,建议取这样的形式:"manufacturer,model",即“厂家名,模块名”。
注:machine desc意为“机器描述”,到内核启动流程才会涉及。
3)model
model属性与compatible类似,区别在于:
compatible属性是一个字符串列表,表示你的硬件可以兼容A、B、C等驱动;
model用来准确定义这个硬件是什么。
例如,根节点中可以这样写:
/{
compatible = "samsung,smdk2440","samsung,mini2440"; // 代表我的板子兼容samsung的smdk2440和mini2440,优先使用内核里面它们的驱动
model = "jz2440_v3"; // 代表我这个板子就是jz2440
};
4)status
dtsi文件中定义了很多设备,但在你的板子上某些设备是没有的。这是你可以给这个设备添加一个status属性,设置为"disabled"。
&uart1{
status = "disabled";
};
status可能取值:
- "okay":表示设备正常运行;
- "disabled":表示设备不可操作,但后面可以恢复工作;
- "fail":表示发生了严重错误,需要修复;
- "fail-sss":表示发生了严重错误,需修复;sss表示错误信息。
常用"okay"和"disabled"。
dtsi文件
dtsi文件是专门给别的dts文件包含(include)用的,本身语法和dts文件一样。
一个板子boardA,对应设备树文件A.dts文件,如何包含xxx.dtsi文件?
// A.dts
#include xxx.dtsi
// 可以在A.dts中引用特点节点,为其专门指定属性
// 比如禁用uart0
&uart0{ // 通过label引用uart0
status = "disabled"; // // 禁用uart0
};
5)reg
reg本意register,表示寄存器地址。
设备树里,可用来描述一段空间。对于ARM系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。
reg属性只,是一系列"address size",表示用多少个32bit数来表示address和size,由其父节点的#address-cells、#size-cells决定。
/dts-v1/;
/{
#address-cells = <1>; // 下面的node用1个u32整数表示地址
#size-cells = <1>; // 下面的node用1个u32整数表示大小
memory{
reg = <0x80000000 0x20000000>; // 起始地址0x80000000,大小0x20000000
};
};
6)interrupt
有些设备需要使用中断,可以配置interrupt属性。常用2个属性值,如interrupt< 2 3 >,第一个"2"表示中断号,第二个"3"表示中断电平/边沿信息(level/edge)。
interrupt属性值中的中断电平/边沿值,与include/dt-bindings/interrupt-controller/irq.h中IRQ_TYPE_xxx 一致:
// include/dt-bindings/interrupt-controller/irq.h
#define IRQ_TYPE_NONE 0
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8
7)name
过时,不建议用。
值是字符串,用来表示节点名字。跟platform_driver匹配时,优先级最低。compatible属性在匹配过程中,优先级最高。
8)device_type
过时,不建议用。
值是字符串,用来表示节点类型。跟platform_driver匹配时,优先级为中。compatible属性在匹配过程中,优先级最高。
编译、更换设备树
通常不会从零开始写dts文件,而是修改。
在内核中直接make
对于64bit CPU,dts文件位于
# 相对于Linux源码根目录
arch/arm64/boot/dts
对于32bit CPU,dts文件位于
# 相对于Linux源码根目录
arch/arm/boot/dts/
以100ASK IMX6ULL Pro板子为例,进入ubuntu上板子Linux内核源码目录,执行命令编译dtb文件
$ touch arch/arm/boot/dts/100ask_imx6ull-14x14.dts # 假装修改了dts文件
$ make dtbs V=1
注:需要先设置环境变量ARCH、CROSS_COMPILE、PATH。执行make目录不是dts文件所在目录,而是Linux源码根目录。
可以看到如下输出
$ make dtbs V=1
...
mkdir -p arch/arm/boot/dts/ ; arm-linux-gnueabihf-gcc -E -Wp,-MD,arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.d.pre.tmp -nostdinc -I./arch/arm/boot/dts -I./arch/arm/boot/dts/include -I./drivers/of/testcase-data -undef -D__DTS__ -x assembler-with-cpp -o arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.dts.tmp arch/arm/boot/dts/100ask_imx6ull-14x14.dts ;
./scripts/dtc/dtc -O dtb -o arch/arm/boot/dts/100ask_imx6ull-14x14.dtb -b 0 -i arch/arm/boot/dts/ -Wno-unit_address_vs_reg -d arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.d.dtc.tmp arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.dts.tmp ; cat arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.d.pre.tmp arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.d.dtc.tmp > arch/arm/boot/dts/.100ask_imx6ull-14x14.dtb.d
可以看到,利用了arm-linux-gnueabihf-gcc 进行编译,"-E"是指预编译,将dts文件先预编译为临时文件"dts.tmp"。预编译为临时文件后,才会使用设备树的编译器dtc,把临时文件编译为dtb文件。
dts之所以能使用include语法,就是因为会先用gcc(arm-linux-gnueabihf-gcc)命令进行预编译。如果不用gcc预编译,那么dts中的#include "imx6ull.dtsi",就应该改成/include/imx6ull.dtsi。
修改设备树文件
修改100ASK IMX6ULL PRO评估版的设备树文件100ask_imx6ull-14x14.dts
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
/* add node by martin */ // 添加内容
100ask_test {
xxx_led = "100ask_led_for_test";
};
chosen {
stdout-path = &uart1;
};
...
};
通过vim/vi编辑完dts文件后,可以直接编译。
$ make dtbs
CHK include/config/kernel.release
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
CHK include/generated/bounds.h
CHK include/generated/timeconst.h
CHK include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
DTC arch/arm/boot/dts/100ask_imx6ull-14x14.dtb
可以看到生成了100ask_imx6ull-14x14.dtb文件,拷贝到nfs目录(~/nfs_rootfs/)
$ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
因为设备树文件(.dtb)是通过bootloader(uboot)传给内核的。可以在启动板子的时候,按空格进入uboot。
此时输入print命令,也可以看到uboot使用的设备树文件
=> print
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
...
fdt_addr=0x83000000
fdt_file=100ask_imx6ull-14x14.dtb
fdt_high=0xffffffff
fdtcontroladdr=9ef40478
findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then setenv fdt_file imx6ull-14x14-alpha.dtb; fi; fi;
...
这里fdt_file为 100ask_imx6ull-14x14.dtb,就是所使用的设备树文件。
- 从uboot进入app,启动板子
=> boot
- 进入boot目录
# cd /boot
# ls
100ask_imx6ull-14x14.dtb 100ask_myir_imx6ull_mini.dtb zImage
里面有dtb文件。
- 挂载nfs文件系统
# mount -t nfs -o nolock,vers=3 192.168.1.4:/home/martin/nfs_rootfs /mnt
注意:这里远程主机的nfs目录需要根据实际情况配置
- 拷贝设备树文件到boot目录
# cp 100ask_imx6ull-14x14.dtb 100ask_imx6ull-14x14.dtb_bak # 拷贝前先备份一下旧的dtb文件
# cp /mnt/100ask_imx6ull-14x14.dtb .
- 板子启动后查看设备树
# reboot # 重启板子
# ls /sys/firmware/
devicetree fdt
/sys/firmware/devicetree 目录下是以目录结构呈现的dtb文件,根节点对应base目录,每个节点对应一个目录,每个属性对应一个文件。
这些属性的值如果是字符串,可以使用cat命令打印;如果是数值,可以用hexdump命令打印出来。
而/sys/firmware/fdt文件,就是dtb格式的设备树文件,可以把它复制出来放到ubuntu上,执行下面命令反编译出来(-I dtb:输入格式dtb,-O dts:输出格式dts)
$ cd /home/martin/100ask_imx6ull-sdk/Linux-4.9.88/ # 板子所用内核源码目录, 根据实际情况决定
$ ./scripts/dtc/dtc -I dtb -O dts # 从板子上复制出来的ftd文件(dtb), 反编译为dts文件
查看我们之前修改的dts文件,板子是否在运行
# cd /sys/firmware/devicetree/base/
# ls -l
total 0
-r--r--r-- 1 root root 4 Jan 1 1970 #address-cells
-r--r--r-- 1 root root 4 Jan 1 1970 #size-cells
drwxr-xr-x 2 root root 0 Jan 1 1970 100ask_test
drwxr-xr-x 2 root root 0 Jan 1 1970 aliases
drwxr-xr-x 2 root root 0 Jan 1 1970 backlight
drwxr-xr-x 2 root root 0 Jan 1 1970 chosen
drwxr-xr-x 6 root root 0 Jan 1 1970 clocks
-r--r--r-- 1 root root 34 Jan 1 1970 compatible
drwxr-xr-x 3 root root 0 Jan 1 1970 cpus
drwxr-xr-x 3 root root 0 Jan 1 1970 gpio-keys
drwxr-xr-x 2 root root 0 Jan 1 1970 interrupt-controller@00a01000
drwxr-xr-x 3 root root 0 Jan 1 1970 leds
drwxr-xr-x 2 root root 0 Jan 1 1970 memory
-r--r--r-- 1 root root 36 Jan 1 1970 model
-r--r--r-- 1 root root 1 Jan 1 1970 name
drwxr-xr-x 2 root root 0 Jan 1 1970 pxp_v4l2
drwxr-xr-x 5 root root 0 Jan 1 1970 regulators
drwxr-xr-x 3 root root 0 Jan 1 1970 reserved-memory
drwxr-xr-x 2 root root 0 Jan 1 1970 sii902x-reset
drwxr-xr-x 13 root root 0 Jan 1 1970 soc
drwxr-xr-x 2 root root 0 Jan 1 1970 sound
drwxr-xr-x 3 root root 0 Jan 1 1970 spi4
可以看到目录100ask_test,就是我们之前修改dts文件,而创建的节点。
之前修改的dts:
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
/* add node by martin */
100ask_test {
xxx_led = "100ask_led_for_test";
};
...
}
进入100ask_test目录,可以看到下面几个文件:
# ls -l
total 0
-r--r--r-- 1 root root 12 Jan 1 1970 name
-r--r--r-- 1 root root 20 Jan 1 1970 xxx_led
name和xxx_led 刚好是我们之前添加的100ask_test的属性,其值是"100ask_led_for_test"。可以用cat命令查看其值:
# cat name
100ask_test
# cat xxx_led
100ask_led_for_test
反编译fdt
# cd /sys/firmware
# ls
devicetree fdt
# mount -t nfs -o nolock,vers=3 192.168.1.4:/home/martin/nfs_rootfs /mnt
# cp fdt /mnt/100ask_imx6ull_fdt
怎么转换为platform_device
内核处理设备树文件,我们知道所有node会生成device_node,会有部分转换为platform_device,但是如何转换为platform_device呢?
1)platform_device中包含resourc数组,它来自device_node的reg,interrupt属性;
2)platform_device.dev.of_node 指向device_node,可以通过它获得其他属性;
总线设备驱动模型中,有一个platform_bus_type(bus_type 类型),它有platform设备列表、platform driver列表,通过.match成员(函数指针)判断是否匹配,如果匹配,就调用driver的.probe。
回顾下platform_device与platform_driver的名字匹配顺序:
1)platform_device的.driver_override 与 platform_driver.driver中的.name;
2)platform_device的.of_node数组的某个.name与 与 platform_driver.driver.of_match_table数组的某个.name(来源于设备树);
3)platform_device的.name 与 platform_driver.id_table数组中的某个.name;(常用)
4)platform_deivce的.name 与 platform_driver.driver中的.name;(常用)
platform_get_resource从设备树获取资源
platform_get_resource 跟设备树没什么关系,但设备树的节点被转换为platform_device后,设备树中的reg属性、interrupts属性也会被转换为“resource”。
此时,可以使用该函数取出这些资源。
函数原型:
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type 支持取这几类资源:IORESOURCE_MEM, IORESOURCE_REG, IORESOURCE_IRQ等
* @num: resource index 代表这类资源中的哪个
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
对于设备树节点中的reg属性(地址),通过type=IORESOURCE_MEM来获得;
对于设备树节点中的interrupts属性(中断),通过type=IORESOURCE_IRQ来获得;
怎么获取转换为platform_device的设备树节点中的非标准属性,比如pin(pin=xxx)?
对于根节点"/ { };",会保存在一个全局变量of_root里面( device_node类型)。可以通过访问该全局变量(代表根节点),得到任意一个节点,得到节点后可以读出属性。
于是,可以使用内核提供的函数,直接访问device_node,从而读出这些属性。
这些函数可以分为3来:找到节点,找到属性,获取属性的值。这些函数位于内核源码include/linux/of.h。
1)找到节点
a. of_find_node_by_path
根据路径找到节点,比如"/"就对应根节点,"/memory"对应memory节点。
函数原型:
static inline struct device_node *of_find_node_by_path(const char *path);
b. of_find_node_by_name
根据名字找到节点,节点如果定义了name属性,我们可以根据名字找到它。
函数原型:
static inline struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
c. of_find_node_by_type
根据类型找到节点,节点如果定义了device_type属性,我们可以根据类型找到它。
函数原型:
extern struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
d. of_find_compatible_node
根据compatible找到节点,节点如果定义了compatible属性,那我们可以根据compatible属性找到它。
函数原型:
/*
* from: 表示从哪个节点开始寻找, NULL表示从根节点开始寻找
* type: 字符串, 用来指定device_type属性的值, 可以传入NULL
* compatible: 字符串, 用来指定compatile属性的值
*/
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compatible);
e. of_find_node_by_phandle
根据phandle找到节点。
dts文件被编译为dtb文件时,每个节点都有一个数字ID,这些ID彼此不同。可以用数字ID来找到device_node。这些数字ID就是phandle。
函数原型:
/*
* handle: 要找的phandle (数字ID)
*/
struct device_node *of_find_node_by_phandle(phandle handle);
f. of_get_parent
找到device_node的父节点。
函数原型:
struct device_node *of_get_parent(const struct device_node *node);
g. of_get_next_parent
实际上也是找到device_node的父节点,跟of_get_parent返回结果一样。区别在于它多调用了下列函数,把node节点的引用计数-1。这意味着调用of_get_next_parent后,你不再需要调用of_node_put释放node节点。
of_node_put(node);
h. of_get_next_child
取出下一个子节点。
函数原型:
/*
* node: 父节点
* prev: 上一个子节点, NULL表示想找到第1个子节点
*/
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
不断调用of_get_next_child,更新prev参数,就能得到所有子节点。
i. of_get_next_available_child
取出下一个“可用”的子节点,有些节点的status是"disabled",那就会跳过这些节点。
函数原型:
/*
* node: 父节点
* prev: 上一个子节点, NULL表示想找到第1个子节点
*/
struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev);
j. of_get_child_by_name
根据名字取出子节点。
函数原型:
/*
* node: 父节点
* name: 要取出的子节点名字
*/
struct device_node *of_get_child_by_name(const struct device_node *node,
const char *name);
2)找到属性
a. of_find_property
找到节点中的属性。
函数原型:
/*
* np: 表示节点, 我们要在这个阶段中找到名为name的属性
* lenp: 用来保存这个属性的长度, 即它的值的长度
*/
struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
设备树中,节点大概长这样:
xxx_node {
xxx_pp_name = "hello";
};
上面节点中,"xxx_pp_name" 就是属性的名字,值为"hello",值长度6。
3)获取属性的值
a. of_get_property
根据名字找到节点的属性,并且返回它的值。
函数原型:
/*
* np表示节点, 我们要在这个节点中找到名为name的属性, 然后返回其值
* lenp: 保存这个属性的长度, 即值的长度
*/
const void *of_get_property(const struct device_node *np, const char *name,
int *lenp);
不论属性保存的是数字,还是字符串,保存的都是字节流。
属性property结构体:
struct property {
char *name; // 属性的名字
int length; // 属性名字的长度
void *value; // 属性值对应字节流
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
b. of_property_count_elems_of_size
根据名字找到节点的属性,确定它的值有多少个元素(elem)。
函数原型:
/**
* of_property_count_elems_of_size - Count the number of elements in a property
*
* @np: device node from which the property value is to be read. // 节点
* @propname: name of the property to be searched. // 名为propname的属性
* @elem_size: size of the individual element // 每个元素尺寸
*
* Search for a property in a device node and count the number of elements of
* size elem_size in it. Returns number of elements on sucess, -EINVAL if the
* property does not exist or its length does not match a multiple of elem_size
* and -ENODATA if the property does not have a value.
*/
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);
返回指定名字的节点属性的元素个数:
return prop->length / elem_size; // 元素总大小 / 元素尺寸
在设备树中,节点大概长这样:
xxx_node {
xxx_pp_name = <0x50000000 1024> <0x60000000 2048>;
};
调用of_property_count_elems_of_size(np, "xxx_np_name", 8),返回值2;
调用of_property_count_elems_of_size(np, "xxx_np_name", 4),返回值4;
c. 读整数u32/u64
函数原型:
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value);
static inline int of_property_read_u64(const struct device_node *np,
const char *propname, u64 *out_value);
在设备树中,节点大概长这样:
xxx_node {
name1 = <0x50000000>;
name2 = <0x50000000 0x60000000>;
};
调用of_property_read_u32(np, "name1", &val),val得到值0x50000000;
调用of_property_read_u64(np, "name2", &val),val得到值0x60000000 50000000;
d. 读某个整数u32/u64
函数原型:
extern int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index, u32 *out_value);
在设备树中,节点大概长这样:
xxx_node {
name2 = <0x50000000 0x60000000>;
};
调用of_property_read_u32_index(np, "name2", 1, &val),val得到值0x60000000。
e. 读数组
函数原型:
extern int of_property_read_variable_u8_array(const struct device_node *np,
const char *propname, u8 *out_values,
size_t sz_min, size_t sz_max);
extern int of_property_read_variable_u16_array(const struct device_node *np,
const char *propname, u16 *out_values,
size_t sz_min, size_t sz_max);
extern int of_property_read_variable_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz_min,
size_t sz_max);
extern int of_property_read_variable_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz_min,
size_t sz_max);
在设备树中,节点大概长这样:
xxx_node {
name2 = <0x50000012 0x60000000>;
};
上面例子中属性name2的值,长度8(byte)。
调用of_property_read_variable_u8_array(np, "name2", out_values, 1, 10),out_values将会保存这8个8位数:0x12, 0x00, 0x00, 0x50, 0x34, 0x00, 0x00, 0x60。
调用of_property_read_variable_u16_array(np, "name2", out_values, 1, 10),out_values将会保存这4个16位数:0x0012, 0x5000, 0x0034, 0x6000。
总之,这些函数要么能取到全部的数值,要么一个数值都取不到。
如果值长度在sz_min和sz_max之间,就返回全部的数值;否则,一个数值都不返回。
f. 读字符串
函数原型:
/*
* 返回np的属性(名为propname)的值, (*out_string)指向这个值, 把它当做字符串.
*/
extern int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string);
如何修改设备树文件?
之前已经讲过修改dts文件、编译并烧录的方法,这里要讲的是怎么确定设备树与驱动程序的职责,设备树内容来源等。
一个写得好的驱动程序,会尽量确定所用资源。把那些不确定度资源,留个设备树,让设备树来指定。
根据原理图确定“驱动程序无法确定的硬件资源”,再在设备树文件中填写对应内容。
那么,所填写内容的格式是什么?
可以这样确定:
1)看绑定文档
内核文档Documentation/devicetree/bindings/
做得好的厂家一般也会提供设备树的说明文档。
2)参考同类型单板的设备树文件
3)网上搜索
4)实在没办法时,只能研究驱动源码