16.7. Devicetree

刚开始接触设备树肯定会有一些人比较反感,比较反对。这里我来讲一个本质的东西,在zephyr里面,设备树你可以就理解为宏定义,像我们写bsp驱动或者代码的时候,经常要把波特率,引脚配置啥的搞成宏定义方便大家来看,而不是光秃秃的数字。设备树本质上也就是产生这些宏定义。刚开始可以这样来理解,不过后面理解深刻了之后,会有更多的解读。

所有产生的宏都在文件build\zephyr\include\generated\devicetree_generated.h 里面

这是这里设备树像C语言一样,有个自己独特的语法。当然这个语法肯定比C语言要简单的太多太多。只要简简单单的学习一小会就能学会。

设备树本质上就是将这些宏定义抽象的更方便理解,更方便编辑。

既然它有语法,那它肯定有标准的spec。

它的标准的spec在下面的链接里,非常重要,而且linux和zephyr都是公用这一套标准。大家可以自行查阅

https://github.com/devicetree-org/devicetree-specification/releases/download/v0.4/devicetree-specification-v0.4.pdf

https://github.com/devicetree-org/devicetree-specification

简介

devicetree是比较重要的部分。还有个Kconfig部分,

两者差别可以记住下面的一句话:

  • devicetree是处理硬件差异的部分

  • Kconfig是用来处理软件配置部分

只要加入Kconfig,代表你的固件需要变大,要加入某个代码,比如UART。加载一些.c文件,

但是,对于资源有限设备,这套机制可能过于庞大。因此,Zephyr采用了一种builtin的方式来使用设备树文件,其过程如下:

首先我们看到,有两种格式的文件作为输入,其中一种叫做Devicetree源文件,这些文件可以理解为设备树文件本身;第二种是Devicetree binding文件,这些文件负责描述设备树文件中的内容,包括数据类型等。

随后,构建系统会将他们解析处理为C头文件,这里还有一个比较重要的文件,叫做devicetree.h,里面定义了一些工具宏,辅助我们获取设备树节点信息,这样一来,所有包含了devicetree.h的源文件,都可以借助这些宏来访问设备树信息,后面会举例说明。

zephyr只支持最基础、最标准的硬件驱动,不支持各个厂商的硬件特性。

Kconfig和dts看起来是相辅相成的。

对于driver 需要Kconfig来加入编译,同时,Kconfig也有对dts的依赖关系,需要先写好devicetree才能使能driver。

config NRFX_SPI
	bool

config NRFX_SPI0
	bool "SPI0 driver instance"
	depends on $(dt_nodelabel_has_compat,spi0,$(DT_COMPAT_NORDIC_NRF_SPI))
	select NRFX_SPI

例如,考虑一个包含带有 2 个 UART 或串行端口的 SoC 的板, 实例。

  • 主板具有此 UART硬件这一事实是通过两个 UART 来描述的 设备树中的节点。它们提供 UART 类型(通过 compatible 属性)和某些设置,例如硬件的地址范围 内存中的外设寄存器(通过 reg 属性)。

  • 此外,UART 启动时配置还描述为 设备树。这可能包括 RX IRQ 线等配置 优先级和UART波特率。这些可以在运行时修改,但是 它们的启动时配置在 devicetree 中进行了描述。

  • 是否在构建中包含对 UART 的软件支持 通过 Kconfig 控制。不需要使用 UART 的应用程序可以 使用 Kconfig 从构建中删除驱动程序源代码,即使 板的设备树仍然包含 UART 节点。

参考API: https://docs.zephyrproject.org/latest/build/dts/index.html#devicetree-reference

语法

我们类比于C语言, 设备树有下面的语法

  • dts后缀的类似于C语言的.c类型的文件,是实现主体,dtsi的文件类似于C语言的头文件,定义一些通用的,可以共用的设备树。

  • 每个节点都要有父亲节点,除了根节点,

  • /* */ 注释是可以的,但是# 不是注释,切记

  • 基本单元是node, 每个节点后面都有; 和C语言很像。

设备树的标准通配码: 节点格式定义:

[label:] node-name[@unit-address] {
    [properties definitions]
    [child nodes]
};

我们来举个简单的例子

led0: led_0 {
    gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
    label = "Green LED 0";
};
  • label: led0, 这个是一个标号, 代码中可以通过这个标号可以索引到这个节点#define LED0_NODE DT_ALIAS(led0), 这个label可以被后面的节点用& 来索引到,别名,label后面肯定跟着一个冒号: 标号是唯一的,不能重名

  • node-name: led_0 节点正式名称,根节点名称必须是/ 节点名称可以重名。

  • @unit-address: @842800 这个一般是寄存器地址

  • 下面都是一些键值对,是属性

  • 接着是子节点。

gpio1: gpio@842800 {
	compatible = "nordic,nrf-gpio";
	gpio-controller;
	reg = <0x842800 0x300>;
	#gpio-cells = <2>;
	ngpios = <16>;
	status = "disabled";
	port = <1>;
};

uart0: uart@8000 {
	compatible = "nordic,nrf-uarte";
	reg = <0x8000 0x1000>;
	interrupts = <8 NRF_DEFAULT_IRQ_PRIORITY>;
	status = "disabled";
};

uart1: uart@9000 {
	compatible = "nordic,nrf-uarte";
	reg = <0x9000 0x1000>;
	interrupts = <9 NRF_DEFAULT_IRQ_PRIORITY>;
	status = "disabled";
};

overlay

一般情况下写app的,不会改动到devicetree,只需要知道如何使用即可。 特殊情况下,如果要临时添加或者修改的话,可以用overlay的方式来进行修改,可以修改很多值,这个也是和宏不同的地方。

image-20231210102258435

所有和devicetree相关的:

  • sources (.dts)

  • includes (.dtsi)

  • overlays (.overlay)

  • bindings (.yaml)

相关文件

boards/<ARCH>/<BOARD>/<BOARD>.dts
dts/common/skeleton.dtsi
dts/<ARCH>/.../<SOC>.dtsi
dts/bindings/.../binding.yaml

节点名字node-name

1-31 个字符,以小写字母或者大写字母开始,

image-20231111215608309

下面是个例子

root根节点没有名字和地址,就是叫/,其他节点名字都不能叫这个。

image-20231111215807921

这里节点名字可以重名,但是可以看到和后面的地址连在一起,整个字段就不会重复,也就是其实两个加起来是独一无二的。

节点名字推荐

节点名字尽量不要乱起,能体现功能的,可以在下面的列表中寻找,基本能匹配的就直接使用。如果能在下面的列表找到匹配的,尽量用匹配的 。

image-20231111220111715

image-20231111220121480

• adc • accelerometer • air-pollution-sensor • atm • audio-codec • audio-controller • backlight • bluetooth • bus • cache-controller • camera • can • charger • clock • clock-controller • co2-sensor • compact-flash • cpu • cpus • crypto • disk • display • dma-controller • dsi • dsp • eeprom • efuse • endpoint • ethernet • ethernet-phy • fdc • flash • gnss • gpio • gpu • gyrometer • hdmi • hwlock • i2c • i2c-mux • ide • interrupt-controller

• iommu • isa • keyboard • key • keys • lcd-controller • led • leds • led-controller • light-sensor • lora • magnetometer • mailbox • mdio • memory • memory-controller • mmc • mmc-slot • mouse • nand-controller • nvram • oscillator • parallel • pc-card • pci • pcie • phy • pinctrl • pmic • pmu • port • ports • power-monitor • pwm • regulator • reset-controller • rng • rtc • sata • scsi • serial • sound • spi • spmi • sram-controller • ssi-controller • syscon • temperature-sensor • timer • touchscreen • tpm • usb • usb-hub • usb-phy • vibrator • video-codec • vme • watchdog • wifi

path name

节点path有点类似于路径,这个可以索引到具体某一个节点,比如上面那个节点

/cpus/cpu@1 这个就是索引到对应的节点。这个路径要是唯一的。

节点Node

节点分为aliase 别名节点,memory内存节点,chosen节点,cpus节点

根节点

一个/cpus 节点

至少一个/meomory节点

/root 节点属性

image-20230913205115717

#address-cells

代表子节点中reg的地址属性占几个uint32大小

用一个uint_t 32的数字代表 根节点中子节点的reg属性的地址

#size-cells

用一个uint_t32 的数字代表根节点中子节点的reg属性的大小

model: 唯一的板子标识,通常是”制造商, model”

compatible: 兼容模型

其他是可选的,zephyr中未用到。

aliases节点

这个是给节点取一些别名的

image-20230913205920570

memory节点

image-20230913210013792

chosen 节点

描述参数要用到的值

image-20230913210143264

zephyr chosen 节点

下表记录了一些常用的特定于 Zephyr 的选定节点。

有时,所选节点的标签属性将用于设置 Kconfig 选项的默认值,该选项又配置特定于硬件的设备。这通常是为了向后兼容,以防 Kconfig 选项早于 Zephyr 中的 devicetree 支持。其他情况下,没有Kconfig选项,源码中直接使用devicetree节点来选择设备。

Property Purpose
zephyr,bt-c2h-uart Selects the UART used for host communication in the Bluetooth: HCI UART
zephyr,bt-mon-uart Sets UART device used for the Bluetooth monitor logging
zephyr,bt-uart Sets UART device used by Bluetooth
zephyr,canbus Sets the default CAN controller
zephyr,ccm Core-Coupled Memory node on some STM32 SoCs
zephyr,code-partition Flash partition that the Zephyr image’s text section should be linked into
zephyr,console Sets UART device used by console driver
zephyr,display Sets the default display controller
zephyr,keyboard-scan Sets the default keyboard scan controller
zephyr,dtcm Data Tightly Coupled Memory node on some Arm SoCs
zephyr,entropy A device which can be used as a system-wide entropy source
zephyr,flash A node whose reg is sometimes used to set the defaults for CONFIG_FLASH_BASE_ADDRESS and CONFIG_FLASH_SIZE
zephyr,flash-controller The node corresponding to the flash controller device for the zephyr,flash node
zephyr,gdbstub-uart Sets UART device used by the GDB stub subsystem
zephyr,ieee802154 Used by the networking subsystem to set the IEEE 802.15.4 device
zephyr,ipc Used by the OpenAMP subsystem to specify the inter-process communication (IPC) device
zephyr,ipc_shm A node whose reg is used by the OpenAMP subsystem to determine the base address and size of the shared memory (SHM) usable for interprocess-communication (IPC)
zephyr,itcm Instruction Tightly Coupled Memory node on some Arm SoCs
zephyr,ocm On-chip memory node on Xilinx Zynq-7000 and ZynqMP SoCs
zephyr,osdp-uart Sets UART device used by OSDP subsystem
zephyr,ot-uart Used by the OpenThread to specify UART device for Spinel protocol
zephyr,pcie-controller The node corresponding to the PCIe Controller
zephyr,ppp-uart Sets UART device used by PPP
zephyr,settings-partition Fixed partition node. If defined this selects the partition used by the NVS and FCB settings backends.
zephyr,shell-uart Sets UART device used by serial shell backend
zephyr,sram A node whose reg sets the base address and size of SRAM memory available to the Zephyr image, used during linking
zephyr,tracing-uart Sets UART device used by tracing subsystem
zephyr,uart-mcumgr UART used for Device Management
zephyr,uart-pipe Sets UART device used by serial pipe driver
zephyr,usb-device USB device node. If defined and has a vbus-gpios property, these will be used by the USB subsystem to enable/disable VBUS

CPUS 节点

所有的设备树都需要一个CPU节点,

image-20230913211829137

属性property

属性名字也是1-31个字节,

image-20231111220552357

非标的属性尽量用独一无二的字符串。像下面这样。

fsl,channel-fifo-len ibm,ppc-interrupt-server#s linux,network-index

属性值

image-20231111221054958

image-20231111221109949

这里

  • 32bit数据, 0x11223344

  • 64bit数据, <0x11223344 0x55667788>

  • string, “hello”

  • phandle, 这个比较重要, 这里可以引用其他的节点, &node

  • 这个就是特殊的属性值,由其他地方属性定义

  • stringlist, 多组字符串 , 例如 “hello”, “world”

    属性值有以下类型

Property type How to write Example
string Double quoted a-string = "hello, world!";
int between angle brackets (< and >) an-int = <1>;
boolean for true, with no value (for false, use /delete-property/) my-true-boolean;
array between angle brackets (< and >), separated by spaces foo = <0xdeadbeef 1234 0>;
uint8-array in hexadecimal without leading 0x, between square brackets ([ and ]). a-byte-array = [00 01 ab];
string-array separated by commas a-string-array = "string one", "string two", "string three";
phandle between angle brackets (< and >) a-phandle = <&mynode>;
phandles between angle brackets (< and >), separated by spaces some-phandles = <&mynode0 &mynode1 &mynode2>;
phandle-array between angle brackets (< and >), separated by spaces a-phandle-array = <&mynode0 1 2>, <&mynode1 3 4>;

标准属性值

标准属性

compatible

这个属性是用来匹配设备和驱动的唯一标识,这个是string list

用来匹配对应的driver驱动,优先级从左到右,

推荐的格式是 manufacturer,model , 前面是制造商的名字,后面是model名字,不要用下划线

例子:

compatible = “fsl,mpc8641”, “ns16550”;

先匹配前面的mpc8641 如果找不到再匹配ns16550

model

模型

这个模型特指厂商某一个模型,推荐格式 manufacturer,model ,

例子:

model = “fsl,MPC8349EMITX”;

这个和campatible有什么差别呢?

model有点像告诉别人这个模型叫什么名字,

compatible 类似于告诉别人这个模型和哪些是兼容的,是多选的。

image-20231113160957528

phandle

定义一个唯一的标识符为节点,其他节点可以用这个phandle的值连接到这个属性

phandle 可以指定一个独一无二的数值来供其他的来引用。

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
    reg = <0x10000000 0x100>;
}

其他地方可以通过这样的方式来引用:

another-device-node {
    interrupt-parent = <1>;
};

相当于一次地址引用。这个慎用

status

这个比较关键。不过也比较简单,就代表这个模块是否使能,常见值”okey”

  • “disable” 代表关闭这个模型

  • “okey” 代表这个device可以操作

其他值暂且不需要知道。

具体的可以参考下面的内容。

image-20231113161647192

#address-cells 和#size-cells

#address-cells#size-cells 定义了该节点子节点的reg属性的大小,

image-20230913214350814

比如这个节点,表示的就是子节点serial里面的reg节点,第一个字节代表地址,第二个字节代表大小或者长度

后面的address 的1代表0x4600 占一个单元, size-cells 的1代表0x100占一个单元, (因为有时候64bit的可能会比较大会占两个单元)

通常一个单元最大是32bit,

64bit可以参考下面的图示

image-20231113162004539

reg

reg,根据上面的address-cellssize-cells来读取,通常是一个地址位和一个大小位

image-20230914075644416

ranges

ranges相当于子节点有个偏移量,

通常是3个元素,

  • 子bus的地址偏移

  • 父bus的地址

  • 长度

(child-bus-address, parent-busaddress, length)

image-20230913215130310

这个代表serial这个子节点在父节点中实际上需要做一个偏移,

real adrress = serialaddress+ 子节点偏移(ranges 的0) + 父节点soc的偏移0xe0000000

就是(0x4600+0)+0xe0000000, 最后一个代表的是地址取址范围即长度。

特殊属性

image-20230913230235161

例如

#gpio-cells

#clock-cells

#reset-cells

这些都是特殊节点,也是代表子节点中相关的占位符

<specifier>-map-mask

<specifier>-map-pass-thru

image-20230913230337315

image-20230913230645664

mask 和pass-thru都是互补的

节点标识符

要获取有关特定设备树节点的信息,您需要它的*节点标识符。*这只是一个引用节点的 C 宏。

以下是获取节点标识符的主要方法:

  • 按路径

    与设备树中节点的完整路径一起使用DT_PATH(),从根节点开始。如果您碰巧知道您正在寻找的确切节点,这非常有用。

  • 按节点标签

    用于从节点标签DT_NODELABEL()获取节点标识符。节点标签通常由 SoC 文件提供,以给出与 SoC 数据表匹配的节点名称,例如、 等。.dtsi``i2c1``spi2

  • 按别名

    用于DT_ALIAS()获取特殊节点属性的节点标识符/aliases有时,这是由 led0需要引用特定类型的某些设备(“主板的用户 LED”)但不关心使用哪一个的应用程序(例如使用别名的眨眼)来完成的。

  • 按实例编号

    这主要由设备驱动程序完成,因为实例号是一种基于匹配兼容来引用各个节点的方法。使用 获取这些 DT_INST(),但要小心这样做。见下文。

  • 按所选节点

    用于DT_CHOSEN()获取节点属性的节点标识符/chosen

  • 由家长/孩子

    使用DT_PARENT()DT_CHILD()获取父节点或子节点的节点标识符,从已有的节点标识符开始。

/dts-v1/;

/ {

	aliases {
		sensor-controller = &i2c1;
	};

	soc {
		i2c1: i2c@40002000 {
			compatible = "vnd,soc-i2c";
			label = "I2C_1";
			reg = <0x40002000 0x1000>;
			status = "okay";
			clock-frequency = < 100000 >;
		};
	};
};

以下是获取节点的节点标识符的几种方法i2c@40002000

  • DT_PATH(soc, i2c_40002000)

  • DT_NODELABEL(i2c1)

  • DT_ALIAS(sensor_controller)

  • DT_INST(x, vnd_soc_i2c)对于一些未知的号码xDT_INST()有关详细信息,请参阅 文档。

每一个驱动driver里面都在头部需要注意这个宏

#define DT_DRV_COMPAT openisa_rv32m1_gpio

这个宏定义了compatible 相关的devicetree

所以下面它可以直接用DT_DRV_INST(inst) 来直接索引到对应的devicetree

#define DT_DRV_INST(inst) DT_INST(inst, DT_DRV_COMPAT)

常用API

https://docs.zephyrproject.org/latest/build/dts/api-usage.html

代码中常见API都放在devicetree.h 文件中,DT_开头的宏都是devictree相关的

其他API

以下是一些其他可用 API 的指针。

driver如何使用

驱动中注意下面这个宏,很重要,用来指定当前driver是用的那个devicetree,和compatible息息相关

#define DT_DRV_COMPAT nordic_nrf_gpio
#include <zephyr/devicetree.h>

#define DT_DRV_COMPAT my_driver_compat

/* This is same thing as DT_INST(0, my_driver_compat): */
DT_DRV_INST(0)

/*
 * This is the same thing as
 * DT_PROP(DT_INST(0, my_driver_compat), clock_frequency)
 */
DT_INST_PROP(0, clock_frequency)
serial@40001000 {
        compatible = "vnd,serial";
        status = "okay";
        current-speed = <115200>;
};
#define DT_DRV_COMPAT vnd_serial
DT_DRV_INST(0)                  // node identifier for serial@40001000
DT_INST_PROP(0, current_speed)  // 115200
&i2c0 {
    sensor0: sensor@0 {
        compatible = "vnd,some-sensor";
        status = "okay";
        reg = <0>;
        foo = <1>;
        bar = <2>;
    };

    sensor1: sensor@1 {
        compatible = "vnd,some-sensor";
        status = "okay";
        reg = <1>;
        foo = <2>;
    };

    sensor2: sensor@2 {
        compatible = "vnd,some-sensor";
        status = "disabled";
        reg = <2>;
        baz = <1>;
    };
};
#define DT_DRV_COMPAT vnd_some_sensor

DT_ANY_INST_HAS_PROP_STATUS_OKAY(foo) // 1
DT_ANY_INST_HAS_PROP_STATUS_OKAY(bar) // 1
DT_ANY_INST_HAS_PROP_STATUS_OKAY(baz) // 0

Binding

https://docs.zephyrproject.org/latest/build/dts/bindings.html#dt-bindings

binding代表一些节点的写法规则

这个所有的代码都放在dts\bindings

而且这里的代码代表属性中的一些含义:

比如

image-20230913213149551

这里的含义就是reg必须要有,clocks也必须要有,

#gpio-cells 是2 代表使用这个节点,必须带两个参数:

两个参数含义分别是pin flags

image-20230913213332508

所以这里用到gpio的地方,都需要两个参数

绑定索引

https://docs.zephyrproject.org/latest/build/dts/api/bindings.html#devicetree-binding-index

/dts-v1/;

/ {
        a-node {
                subnode_nodelabel: a-sub-node {
                        foo = <3>;
                };
        };
};

zephyr

  • Zephyr 的 devicetree 源解析器dtlib.py与dtc等其他工具在两个方向上都源兼容: dtlib.py可以解析dtc输出,并且dtc可以解析 dtlib.py输出。

  • Zephyr 的“扩展 dtlib”库edtlib.py不应包含 Zephyr 特定的功能。其目的是为中断和总线等常见元素提供设备树的更高级别视图。

剩余工作示例

  • Zephyr 有自定义的Devicetree 绑定语言语法。虽然 Linux 的 dtschema 尚未满足 Zephyr 的需求,但我们应该尝试遵循它能够在 Zephyr 自己的绑定中表示的内容。

  • 由于绑定语言不灵活,Zephyr 无法支持 Linux 支持的全套绑定。

  • Zephyr 和 Linux 之间的 Devicetree 源代码共享尚未完成。

给 Linux 开发者的注意事项

熟悉 devicetree 的 Linux 开发人员应该注意,这里描述的 API 与 Linux 上使用 devicetree 的方式有很大不同。

Linux 内核不会生成包含所有 devicetree 数据的 C 标头,然后在宏 API 后面进行抽象,而是以二进制形式读取 devicetree 数据结构。二进制表示在运行时解析,例如加载和初始化设备驱动程序。

Zephyr 不会以这种方式工作,因为 devicetree 二进制文件和相关处理代码的大小太大,无法轻松适应 Zephyr 支持的相对受限的设备。

中断和中断MAP

https://www.cnblogs.com/xiaojiang1025/p/6131381.html

  • “Interrupt Generating Devices”,中断发生设备,这种设备可以发生中断。

  • “Interrupt Controllers”,中断控制器,处理中断。

  • “Interrupt Nexus”,中断联结,路由中断给中断控制器。

image-20230913224222815

interrupts

image-20230913224357307

interrupts-paranet

要说interrupt-parent,就得首先讲讲Linux设备管理中对中断的设计思路演变。随着linux kernel的发展,在内核中将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也可以被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了,另外,在硬件上,随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:

在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domain,clock domain等等,所谓domain,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。如上所述,系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(interrupt source,中断源),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。有了irq domain这个概念之后,这个编号仅仅限制在本interrupt controller范围内,有了这样的设计,CPU(Linux 内核)就可以根据级联的规则一级一级的找到想要访问的中断。当然,通常我们关心的只是内核中的中断号,具体这个中断号是怎么找到相应的中断源的,我们作为程序员往往不需要关心,除了在写设备树的时候,设备树就是要描述嵌入式软件开发中涉及的所有硬件信息,所以,设备树就需要准确的描述硬件上处理中断的这种树状结构,如此,就有了我们的interrupt-parant这样的概念:用来连接这样的树状结构的上下级,用于表示这个中断归属于哪个interrupt controller,比如,一个接在GPIO上的按键,它的组织形式就是:

interrupts-extended

interrupts-cells

#interrupts-cells 值如果是2 ,代表这个节点的子节点的interrups里面的数值是两个元素

image-20230914101352149

同时在dts/bindings/interrupt-controller 里面会代表这个interrupts是哪些元素,通常为irqpriority

也有的只有irq

image-20230914101723826

参考

https://www.devicetree.org/specifications/

spec

https://github.com/devicetree-org/devicetree-specification/releases/download/v0.4/devicetree-specification-v0.4.pdf

https://github.com/devicetree-org/devicetree-specification

博客参考:

https://blog.csdn.net/pwl999/article/details/79631434

https://www.nxpic.org.cn/module/forum/thread-623028-1-1.html

参考:

http://www.wowotech.net/linux_kenrel/dt_basic_concept.html