Linux 设备树语法(.dts)及如何从设备树获取节点信息

设备树简介

一个设备信息用树形结构表示如下:


from http://www.100ask.org/

如何用设备树进行描述呢?

/{ // 表示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)实在没办法时,只能研究驱动源码


参考

http://www.100ask.org/

posted @ 2022-06-23 16:29  明明1109  阅读(11699)  评论(0编辑  收藏  举报