设备树
设备树 (Device Tree) ,起源于Arm嵌入式设备. 是一种描述硬件的数据结构.
在最初调试多U多串的龙芯定制化工控平台时,使用过这个接口.
在linux内核中也有对应的设备树的介绍,与BIOS下的描述一一对应.
内核下会优先使用从BIOS内获取的设备树资源,如若没有,将使用内核下默认的设备树资源文件.
文章目录
- 设备树
- 设备树从哪里来到哪里去?
- dts的组成
- dtc 源码及使用
- dtb的使用与解析
- 尾声
设备树从哪里来到哪里去?
dts就是所谓的source文件,dtb就是所谓的设备树的binary文件,dtc就是编译工具(编解码).
设备树,摘抄前人的一句话就是:画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
dts的组成
dts代码示例: loongson3_ls7a.dts
platform { compatible = "loongson,nbus", "simple-bus"; #address-cells = <2>; #size-cells = <1>; enable-lpc-irq; ranges = <0x000 0x00000000 0x000 0x00000000 0x20000000 0x000 0x40000000 0x000 0x40000000 0x40000000 0xe00 0x00000000 0xe00 0x00000000 0x80000000>; uart0: serial@10080000 { device_type = "serial"; compatible = "ns16550,loongson"; reg = <0 0x10080000 0x100>; clock-frequency = <50000000>; interrupts = <72>; interrupt-parent = <&platic>; no-loopback-test; }; gpio: gpio@100e0000 { compatible = "loongson,ls7a-gpio"; reg = <0 0x100e0000 0xc00>; gpio-controller; #gpio-cells = <2>; ngpios = <57>; conf_offset = <0x800>; out_offset = <0x900>; in_offset = <0xa00>; gpio_base = <16>; interrupts = <124>; interrupt-parent = <&platic>; };
以上剪切了dts的片段,不难发现 Device Tree是由一系列的结点和属性(property)组成的,上图的片段很好的解释了平台上有哪些设备以及设备资源,最终bootloader给到linux的设备信息.
dtc 源码及使用
dts是自然语言,要转化为linux识别的语言dtb,这个转化过程就是通过dtc实现的.
之前,我在UEFI中使用dtc的时候,是编译好的x86工具,现在发现内核中有对应的源码,所以其余平台移植环境也相对应方便了,内核下dtc源码目录:scripts/dtc.
dts->dtb编译方式: dtc –I dts –O dtb –o xxx.dtb xxx.dts
dtb的使用与解析
在龙芯平台下,当使用2.3接口规范时,bios通过在末尾追加dtb的方式进行传递.
话不多说,上代码:
#ifdef LS3A4000 esys->vers=2; esys->nr_uarts = 7; esys->of_dtb_addr = PcdGet64(PcdFlashFdBaseAl)+ PcdGet32(PcdFlashFdSize); if((*(volatile UINT32 *)esys->of_dtb_addr) != 0xedfe0dd0 ){ //DEBUG((DEBUG_INFO,"not found dtb or dtb has a bad magic: 0x%x @ 0x%llx\n",*(volatile esys->of_dtb_addr = 0; }
- 1 在BIOS传递给kernel时,在传参规范中添加了 esys->of_dtb_addr = PcdGet64(PcdFlashFdBaseAl)+ PcdGet32(PcdFlashFdSize); 这段代码,标志dtb的二进制代码由此开始.
- 2 dtb的第一个32位存取了标志位:(volatile UINT32 *)esys->of_dtb_addr) != 0xedfe0dd0 来判断dtb是否正常有效被追加.
- 3 如何追加到flash的末尾? 本文使用了一个python脚本,方式不为一,脚本代码见下图:
import sys
import getopt def Usage(): print '\tUsage: python BaseTools/dtb.py -f UEFI.fd -d PLATFORM.dtb' exit(1) def WriteDtb(FileName,Dtb): try: D = open(Dtb, 'rb') DtbContent = D.read() D.close() except IOError: print 'Can not open', Dtb Usage() try: F = open(FileName, 'r+') F.seek(0,2) F.write(DtbContent) F.close() except IOError: print 'Can not open', FileName Usage() if __name__ == '__main__': try: Opt, Argv = getopt.getopt(sys.argv[1:],'f:d:') Opt = dict(Opt) except getopt.GetoptError: Usage() if '-f' not in Opt or '-d' not in Opt: Usage() WriteDtb(Opt['-f'], Opt['-d'])
尾声
到这里bios的dts->dtb再到linux内核下的传递就完成了,内核将解析我们传递的dtb获取平台或者处理器信息
彩蛋: 内核下如何解析?
举个例子,内核代码解析Lpc的一些信息:
static bool fw_support_fdt(void)
{ return !!(esys && esys->vers >= 2 && esys->of_dtb_addr);
}static void pch_pic_lpc_init(void)
{ struct device_node *np; if (fw_support_fdt()) { np = of_find_compatible_node(NULL, NULL, "simple-bus"); if (np) { if (of_property_read_bool(np, "enable-lpc-irq")) { if (of_property_read_bool(np, "lpc-irq-low")) { writel(0, LS7A_LPC_INT_POL); } else { writel(-1, LS7A_LPC_INT_POL); } } } } }
阅读代码,
- 1 fw_support_fdt: 首先判断我们的版本号支持fdt(dtb),然后按照我们刚才传递的of_dtb_addr是个有效地址.
- 2 of_find_compatible_node: 查询dts中有simple-bus的节点标识,找到platform的资源.
- 3 在platform节点内查找enable-lpc-irq? 内核想知道BIOS传递的参数是否有操作lpc中断极性的描述.
- 4 继续查找是否有lpc-irq-low的语法表达, 如果是,说明固件想要内核将Lpc的中断极性设置为低电平触发,即内核操作相应寄存器进行设置.
这些描述信息相当于复杂一些的自然语言,你应该有一个很好的理解,dts的源文件也可以自定义平台的资源进行描述.