从Zephyr设备树生成流程一文中知道, 设备树作为C宏被Zephyr引用,C宏是gen_defines.py搭配yaml文件解析dts生成。涉及到dts转化为C宏的所有python脚本文件都在zephyr/scripts/dts下,分别为
dtlib.py 底层的dts解析
edtlib.py 位于dtlib之上,用来解释绑定属性,使用dtlib解析dts
gen_defines.py 使用edtlib.py生成C宏
分析这些脚本虽然可以让我们深入了解DTS转换成宏的流程,但对于我们一般使用Zephyr DTS并无太大必要,本文不分析这些脚本,只说明Zephyr使用什么规则将dts转化为C宏(这也主要是edtlib.py 的工作内容),以此帮助我们在分析Zephyr驱动时知道一些宏的来源,同时当了解这些规则也会对分析宏生成脚本有帮助。
概述
dts文件用来描述硬件信息,但构建系统并不知道dts内那些信息对设备驱动有用,也就无法知道dts那些内容会被生成宏。于是Zephyr提供了bindings文件yaml,该文件告诉构建系统那些dts内的信息会被转换成宏。在构建阶段使用gen_defines.py会尝试把dts内每个节点和yaml文件内容匹配,只为能匹配上的dts节点生成宏。阅读本文需要了解dts的基本概念和binding,dts的基本概念网上有很多,这里就不做介绍也可以参考文末zephyr dts的介绍链接. Binding下面会简单介绍,已方便之后理解如何生成宏。
Devicetree bindings
Devicetree的binding文件放在zephyr/dts/bindings, 用于告诉gen_defines.py dts中那些信息要被生成宏和以什么样的形式生成宏。在构建阶段使用gen_defines.py会尝试把dts内每个节点和yaml文件内容匹配,只为能匹配上的dts节点生成宏。
匹配方式
dts通过note内的compatible 属性指和yaml文件名进行匹配,例如:
1 | sdram0: memory@80000000 { |
上面这个节点的compatible的值是”mmio-sarm”,那么它就会去dts/bindings中去找mmio-sarm.yaml文件来绑定,如下
1 | description: Generic on-chip SRAM description |
node可以指定多个compatible,从前到后依次匹配,先匹配到那个就用那个,例如
1 | is25wp064: is25wp064@0 { |
如果找到了issi,is25wp064.yaml就直接用,没找到就继续找jedec,spi-nor.yaml
节点如果没有指定compatible,则使用父节点的compatible,例如下例中red_pwm_led和green_pwm_led都是匹配的pwm-leds.yaml
1 | pwmleds { |
如果节点描述了总线上的硬件(例如I2C或SPI),则在将节点映射到绑定时要考虑总线类型。 在查找节点的绑定时,要检查父节点的绑定是否包含bus: <bus type>和只会绑定和父节点bus type匹配的节点。例如
1 | &i2c0 { |
ssd1306@3c绑定的是solomon,ssd1306fb.yaml,当中会include到i2c-device.yaml
1 | on-bus: i2c |
其父节点i2c0绑定的是nordic,nrf-twi.yaml,当中互include到i2c-controller.yaml
1 | bus: i2c |
可以看到父节点绑定描述了bus是i2c,子节点绑定描述on-bus是i2c,二者匹配才会继续生成宏。
绑定文件语法
本文主旨不是说明绑定文件语法,详细可以参考源代码中dts/binding-template.yaml
在2.1以前的zephyr和当前zephyr 2.2.99的绑定语法不一样, 当前zephyr还同时支援两种语法,但2.3后将废弃2.1老的绑定语法。
宏生成
由dts产生的宏都是以DT_开头,全部为大写, C宏基本都是dts的属性,例如
1 | #define DT_NXP_IMX_FLEXSPI_402A8000_JEDEC_SPI_NOR_0_JEDEC_ID {0x9d, 0x70, 0x17} |
说明了一个SPI Flash上的jedec id是多少,接下来我们来宏名字和对应的值是怎么生成的。
节点C标识
宏的C标识是由节点或者节点属性以DT_<node>的形式生成,这里以MadMachine SwiftIO中spi flash,led,pwm的dts实例说明不同节点C标识<node>的生成规则
在mm_swiftio.dts中有如下片段
1 | aliases { |
被预编译后展开为(zephyr.dts), 对于led没有父节点所以没有变化,spi flash被展开, pwm是mm_swiftio.dts中include的dtsi带有的
1 | aliases { |
要生成的节点C标识有下面3种
1. DT_(<bus>_)<compatible>_<unit-address>转换规则
对于每个节点来说通常都满足DT_(<bus>_ )<compatible>_<unit-address>,也就是节点的兼容属性转换为C标识符,后跟其单元地址,
例如spi@402a8000的C标识就是DT_NXP_IMX_FLEXSPI_402A8000
- 该节点没有bus
- 兼容属性compatible为”nxp,imx-flexspi”, 将字母数字转换为大写,其它符号转换为下划线得到NXP_IMX_FLEXSPI
- 单元地址402a8000,转换为大写402A8000
如果有bus就需要加入bus,如果兼容属性有多个,使用首个匹配的兼容属性
例如is25wp064@0的C标识符是DT_NXP_IMX_FLEXSPI_402A8000_JEDEC_SPI_NOR_0
- 该节点的bus是其父节点spi@402a8000,因此bus使用NXP_IMX_FLEXSPI_402A8000
- 兼容属性compatible为”jedec,spi-nor”, 转换为JEDEC_SPI_NOR
- 单元地址0,转换为大写0
兼容属性的确认:
is25wp064@0 先找issi,is25wp064.yaml找不到,然后使用”jedec,spi-nor.yaml”
bus的确认:
在”jedec,spi-nor.yaml”中有
1 | include: [spi-device.yaml, "jedec,spi-nor-common.yaml"] |
里面使用了spi-device.yaml,其中包含
1 | on-bus: spi |
因此可知道is25wp064@0是在spi bus上。
再解析其父节点的nxp,imx-flexspi.yaml当包含了spi-controller.yaml
1 | include: spi-controller.yaml |
spi-controller.yaml中有
1 | bus: spi |
因此可以将子节点的spi bus和父节点bus对上,而确认bus使用NXP_IMX_FLEXSPI_402A8000。
如果节点没有单元地址,则将父节点的单元地址加上转换为C标识符的节点名称用于<unit-address>
例如pwm0的C标识符是DT_NXP_IMX_PWM_403DC000_PWM0
- 该节点没有bus
- 兼容属性是nxp,imx-pwm,转换为NXP_IMX_PWM
- 该节点没有地址,使用父节点地址403dc000和自己的节点名pwm0,转为<unit-address> 是403DC000_PWM0
如果父节点也没有单元地址,则将直接使用该节点的名称
例如led_0的C标识符是DT_GPIO_LEDS_LED_0
- 该节点没有bus
- 该节点没有兼容属性,就使用父节点的兼容属性gpio-leds, 转换为GPIO_LEDS
- 该节点和父节点都没有单元地址,使用该节点名led_0做为<unit-address> , 转换为LED_0
2. DT_INST_<instance-number>_ <compatible>转换规则
节点兼容属性实例编号,对某个兼容属性进行实例编号,对整个dts中有相同兼容属性且被enable的节点(status = “okay”)进行编号,转换方法就是DT_INST搭配实例号和兼容属性
例如”jedec,spi-nor”转换出来就是DT_INST_0_JEDEC_SPI_NOR
- “jedec,spi-nor”兼容属性只出现了一次,因此只会有一个0编号的JEDEC_SPI_NOR
例如”nxp,imx-pwm”出现了2次并都被enable,就会有2个实例,依次为DT_INST_0_NXP_IMX_PWM和DT_INST_1_NXP_IMX_PWM
3. DT_ALIAS_<alias>
别名生成,对于led1别名,生成后DT_ALIAS_LED1,<alias>转换规则就是别名字母数字转换为大写,其它符号转换为下划线
节点属性宏生成
节点属性宏以DT_<node>_<property>形式转换,上一节已经说明了<node>如何生成,这节再说明属性的转换就可以完成从dts到宏的转换了。
属性转换可以分为2种,一种是通用的,一种是指定关键字的。
通用转换规则
通用的是指非特定的关键字,都以一种通用的规则进行转换,转换规则如下
对于boolean类型,如果节点存在该属性者值为1,否则为0
1 | #define DT_<node>_FOO 0/1 |
对于非boolean类型,如果绑定yaml文件中类别是optional并且该属性不在dts中,则不会生成属性宏。
另外还有phandle-array和enum类型,phandle-array后文结合clock介绍。
以is25wp064@0的jedec-id = [ 9D 70 17 ];举例,属于uint8-array类型
<node> 前文已说过如何转为NXP_IMX_FLEXSPI_402A8000_JEDEC_SPI_NOR_0
<property>是jedec-id,转为JEDEC_ID
按照uint8-array进行进行转换因此最后会得到
1 | #define DT_NXP_IMX_FLEXSPI_402A8000_JEDEC_SPI_NOR_0_JEDEC_ID {0x9d, 0x70, 0x17} |
特殊关键字转换规则
在dts中用得最多的就是特殊关键字,这里一一说明
reg属性
reg会被转换成一组地址和大小的宏:DT_<node>_BASE_ADDRESS(_<index>) 和 DT_<node>_SIZE( _<index>).
当只有一个寄存器时没有index
例如flexpwm@403dc000的reg = < 0x403dc000 0x4000 >;会被转换为
1 | #define DT_NXP_FLEXPWM_403DC000_BASE_ADDRESS 0x403dc000 |
index是在有多个寄存器是才有,被当做索引例如
例如spi@402a8000的 < 0x402a8000 0x4000 >, < 0x60000000 0x800000 >会被转换为
1 | #define DT_NXP_IMX_FLEXSPI_402A8000_BASE_ADDRESS_0 0x402a8000 |
interrupts属性
中断生成规则为DT_<node>_IRQ_<index>(_ <name>)和DT_<node>_IRQ_<index>(_<name>)_PRIORITY , node就是前面的C标识符,index是中断顺序编号,当有属性指定中断名时才会有<name>
以spi@402a8000的interrupts = < 0x6c 0x0 >为例,0x6c是IRQ号,0是中断优先级,将会生成
1 | #define DT_NXP_IMX_FLEXSPI_402A8000_IRQ_0 108 |
多中断和带中断名的示例
1 | timer@456 { |
产生宏
1 | #define DT_<node>_IRQ_TIMER_A 1 |
clocks属性
clocks是phandle-array类型,前文没有介绍这里合并一起分析。phandle-array可以认为是引用了其它节点的类型,如下uart中的clocks
1 | ccm: ccm@400fc000 { |
在nxp,imx-ccm.yaml中可以找到clock-cells的声明如下
1 | clock-cells: |
可以生成下面的宏DT_<node>_CLOCK_CONTROLLER_(<index>), DT_<node>_CLOCK_NAME_<index>, DT_<node>_CLOCK_OFFSET_<index>,DT_<node>_CLOCK_BITS_<index>
它们对应的值要从clocks = < &ccm 0x3 0x7c 0x18 >;中取 CONTROLLER就是ccm节点的lable,其它的就是clocks中一次定义,转化为宏如下
1 | #define DT_NXP_KINETIS_LPUART_40184000_CLOCK_CONTROLLER "CCM" |
如果clocks的controller节点有fixed-clock的兼容属性, 那么该节点必须有一个clock-frequency属性,值就是HZ,该节点将会生成附加宏DT_<node>_CLOCKS_CLOCK_FREQUENCY例如
1 | sysclk: system-clock { |
会生成
1 | #define DT_FIXED_CLOCK_SYSTEM_CLOCK_CLOCK_FREQUENCY 600000000 |
cs-gpios属性
该属性是提供给bus的片选用 ,例如芯片有spi总线,当一条总线上挂有多个spi device,device node就需要使用该属性,目前mm_swiftio上没有总线上挂多个设备的情况,这里以官网上的一个例子说明
1 | gpioa: gpio@400ff000 { |
上面dts表达的意思是spi总线上挂有spi-slave设备0和1,分别使用gpioa 1和gpioa 2作为片选,可以看出cs-gpios也是phandle-array,这里生成宏将是
1 | #define DT_<node>_CS_GPIOS_CONTROLLER "GPIOA" |
其它宏
本节介绍为属性产生附加宏
节点存在标识
节点存在标识用于标识设备树中包含那些节点匹配条件
存在某个兼容属性 使用#define DT_COMPAT_<compatible> 1,例如有节点使用compatible = “nxp,imx-flexspi”就会有
1 | #define DT_COMPAT_NXP_IMX_FLEXSPI 1 |
总线相关的宏
某个节点如果出现在总线上会附加出现该宏,形式如下
1 | #define DT_<node>_BUS_NAME "<bus-label>" |
<bus-lable> 是该bus节点的lable,<bus-name>是该节点绑定属性的的on-bus。
例如is25wp064: is25wp064@0对应产生的宏就是
1 | #define DT_NXP_IMX_FLEXSPI_402A8000_JEDEC_SPI_NOR_0_BUS_NAME "FLEXSPI0" |
Flash分区的宏
如果节点名的形式是partition@<unit-address>, 这将作为一个flash分区进行解析,例如下面DTS
1 | flash@0 { |
产生的宏是
1 | #define DT_FLASH_AREA_MCUBOOT_ID 0 |
* _ID的宏表示分区的索引,从零开始编号。
* _OFFSET_ <index>和* _SIZE_ <index>宏给对应分区节点中reg内的值,表示分区的偏移地址和大小。
宏生成范式
前面说了这么多只是帮助理解,关于dts生成宏zephyr官网提供了扩充巴克斯范式(ABNF)描述转化的语法,有兴趣的朋友可以参考
1 | ; dt-macro is the top level nonterminal. It defines the possible |
参考
https://docs.zephyrproject.org/latest/guides/dts/intro.html
https://docs.zephyrproject.org/latest/boards/arm/mm_swiftio/doc/index.html
https://docs.zephyrproject.org/latest/guides/dts/macros.html