祝各位道友念头通达
GitHub Gitee 语雀 打赏

设备树笔记

设备树

写法技巧

注释

使用 // 和 /xxxx/ 注释

引用

dtsi 和 dts 写法一样, 不过dts可以引用 dtsi, 也可以引用头文件, dtsi整合了公共部分

#include "pcw.dtsi"
#include <dt-bindings/gpio/gpio.h>

Linux platform 虚拟总线

  1. platform_bus 用来连接platform_device和platform_driver;
  2. platform_device用来存储设备信息;
  3. platform_driver是设备驱动。

/sys/bus/platform/devices, 中查看设备: 地址+名称
/sys/bus/platform/drivers, 中查看设备驱动

i2c@e0004000 {
	compatible = "cdns,i2c-r1p10";
	status = "disabled";
	clocks = <&clkc 38>;
	interrupt-parent = <&intc>;
	interrupts = <0 25 4>;
	reg = <0xe0004000 0x1000>;
	#address-cells = <1>;
	#size-cells = <0>;
};

命名规则

# 设备树命名规则
[label:]node-name[@unit-address] {
	[properties definitions]
	[child nodes]
};
  • "[]"中的内容表示可选的,可有也可以没有;
  • 节点名字前加上"label"则方便在 dts
    文件中被其他的节点引用,我们后面会说这个;
  • 其中"node-name"是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如"uart1"就表示这个节点是 UART1 外设。
  • "unit-address"一般表示设备的地址或寄存基地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”
  • 每个节点都有若干属性,属性又有相对应的值(值不是必须要有的),而一个节点当中又可以嵌套其它的节点,形成父子节点

赋值规则

  1. 字符串
compatible = "arm,cortex-a9";
  1. 32 位无符号整形数据
clock-latency = <1000>;
reg = <0x00000000 0x00500000>;

32 位无符号整形数据使用尖括号括起来,例如属性 clock-latency 的值是一个 32 位无符
号整形数据 1000,而 reg 属性有两个数据,使用空格隔开,那么这个就可以认为是一个数组,
很容易理解!

  1. 二进制数据
    二进制数据使用方括号括起来,例如上面这个就是一个二进制数据组成的数组。
local-mac-address = [00 0a 35 00 1e 53];
  1. 字符串数组
compatible = "n25q512a","micron,m25p80";
  1. 混合值
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
  1. 节点引用
clocks = <&clkc 3>;

标准属性

如何查看设备树相关属性的文档, 以下是个人记录常用的
在linux源码中有 "Documentation/devicetree/bindings" 中, 搜索相关配置就会有相关的设备树的属性,
比如基于GPIO的led驱动, led设备树有 "leds/common.txt" 这个是所有led所共有的属性,而其它的led文件则是不同平台或者不同类型设备所区分的一些属性

1.compatible 属性

compatible: "兼容性" 属性, 可以是字符串、字符串列表
字符串命名方式一般使用 "<制造商>,<型号>"
作用1: linux内核在启动的时候会根据该值查找对应的驱动程序并加载, 一般的驱动程序问及那都会有一个OF匹配表,保存一些compatible的值,如果设备节点中的compatible值和OF任何一个值相等,就会使用对应的驱动
作用2: 根节点也有 compatible属性, 代表处理器类型, 内核在启动的时候会检测是否支持改系列类型,如果不支持则会处理失败
eg:

compatible = "xlnx,xuartps", "cdns,uart-r1p8";

2. model

字符串描述符, 内核解析会打印出来

3. status

设备状态

描述
okay 设备是可操作的,启动设备,默认值
disable 当前设备不可操作,但是在未来可以变为可操作的
fail 设备不可用, 设备监测到了一系列的错误,变得不可操作
fail-sss 同上 sss是检测出的错误信息

default-state:

(optional) The initial state of the LED. Valid
values are "on", "off", and "keep". If the LED is already on or off
and the default-state property is set the to same value, then no
glitch should be produced where the LED momentarily turns off (or
on). The "keep" setting will keep the LED at whatever its current
state is, without producing a glitch. The default is off if this
property is not present.

4 "#address-cells" 和 "#size-cells" 属性

值: 无符号 32 位整形
可以用在任何拥有子节点的设备节点中,用于描述子节点的地址信息
#address-cells: 用来描述子节点 "reg" 属性的地址表中的首地址 cell的数量
#size-cells: 用来描述子节点 "reg" 属性的地址表中地址长度 cell的数量
这两个属性描述了子节点该如何编写reg属性值
eg: qspi的子节点中寄存器基地址用32bit表示, 没有长度,所以 reg=<0x0>, 而flash的字节点中起始地址和长度都为一个字节表示

&qspi {
	#address-cells = <1>;
	#size-cells = <0>;
	flash0: flash@0 {
		compatible = "n25q512a","micron,m25p80";
		reg = <0x0>;
		#address-cells = <1>;
		#size-cells = <1>;
		spi-max-frequency = <50000000>;
		partition@0x00000000 {
			label = "boot";
			reg = <0x00000000 0x00500000>;
		};
		...

5. reg 属性

用于描述设备地址空间资源信息, 一般都是描述某个外设寄存器的地址范围信息, flash设备分区信息, 其长度可以比实际长度长,只要在操作的时候确定起始地址,操作不越界即可

6. ranges 属性

如果属性值为空值,则表明子地址空间和父地址空间完全相同
规则: ranges = <local, parent, length>, 将local地址映射到 parent地址上

soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;
    serial {
        device_type = "serial";
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};

serial 是串口设备节点,reg 属性定义了 serial 设备寄存器的起始地址为0x4600,寄存器长度为 0x100。经过地址转换,serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。

7. device_type

属性值为字符串,表示节点类型, 一般用于cpu和memory节点

8. 特殊设备和属性

在根节点中有 aliases, chosen, memory 节点

8.1 aliases

给主要的功能起别名, 为了方便内核访问节点。

caliases {
	ethernet0 = &gem0;
	i2c0 = &i2c_2;
	i2c1 = &i2c0;
};

8.2 chosen

chosen节点
bootargs: ttyPS0: 内核启动后设备 /dev/ttyPS0, 为串口0, 改值如果没有设置,该值可能会从 uboot中传过来
stdout-path: 指定了串口输出设备为串口 serial0, n:波特率,8:停止位

chosen {
	bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait";
	stdout-path = "serial0:115200n8";
};

8.3 memory

描述设备系统内存的基地址以及系统内存的大小

9. dma-ranges

用于设置DMA内存映射配置, pcie 设备树会有这个

	pciec0: pcie@fe000000 {
		compatible = "renesas,pcie-r8a7795",
			     "renesas,pcie-rcar-gen3";
		reg = <0 0xfe000000 0 0x80000>;
		#address-cells = <3>;
		#size-cells = <2>;
		bus-range = <0x00 0xff>;
		device_type = "pci";
		ranges = <0x01000000 0 0x00000000 0 0xfe100000 0 0x00100000
			0x02000000 0 0xfe200000 0 0xfe200000 0 0x00200000
			0x02000000 0 0x30000000 0 0x30000000 0 0x08000000
			0x42000000 0 0x38000000 0 0x38000000 0 0x08000000>;
		/* Map all possible DDR as inbound ranges */
		dma-ranges = <0x42000000 0 0x40000000 0 0x40000000 0 0x40000000>;
		interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>,
			<GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>,
			<GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
		#interrupt-cells = <1>;
		interrupt-map-mask = <0 0 0 0>;
		interrupt-map = <0 0 0 0 &gic GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&cpg CPG_MOD 319>, <&pcie_bus_clk>;
		clock-names = "pcie", "pcie_bus";
		power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
		resets = <&cpg 319>;
		status = "disabled";
	};

phandle 和 linux,phandle

在系统启动之后, 在 /proc/device-tree 下会显示所有的设备树映射, 然后有的设备会有 phandlelinux,phandle 属性
意思就是: 如果当前节点被其它节点引用, 则直接引用 phandle 的值, phandle 也就是指向当前节点名称的指针

驱动程序对设备树的应用

1. 函数匹配设备树

image

struct device_node * of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
struct device_node * of_find_matching_node(struct device_node *from, const struct of_device_id *matches);
struct device_node * of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match);
struct device_node * of_find_matching_node_by_address(struct device_node *from, const struct of_device_id *matches, u64 base_address);
struct device_node * of_find_node_by_name(struct device_node *from, const char *name);
struct device_node * of_find_node_by_path(const char *path);
struct device_node * of_find_node_by_phandle(phandle handle);
struct device_node * of_find_node_by_type(struct device_node *from, const char *type);
struct device_node * of_find_node_opts_by_path(const char *path, const char **opts);
struct device_node * of_find_node_with_property(struct device_node *from, const char *prop_name);

2. 设备树属性读取

image

关于device结构体说明

device

struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
};
  • parent:指向设备的“父”设备,它所连接的设备,在大多数情况下,父设备是某种总线或主机控制器,如果该成员为NULL,则该设备为顶级设备;
  • p:用于保存设备驱动核心部分的私有数据;
  • kobj:嵌入的struct kobject对象实例;
  • init_name:设备的初始名称
  • type:设备的类型,用于标识设备类型并携带特定类型信息;
  • mutex:用于同步的互斥锁;
  • bus:该设备所处于的总线;
  • driver:该设备所分配的驱动程序;
  • platform_data:设备中特定的平台数据;
  • driver_data:指向驱动程序特定的私有数据;
  • of_node:与设备树相联系的结构体指针;
  • devt:用于表示设备的设备号;
  • devres_lock:保护设备资源的自旋锁;
  • devres_head:设备资源的双向链表头;
  • knode_class:接入class链表时所需要的klist节点;
  • class:指向设备所属class的指针;
  • groups:该设备的属性集合;
  • release:函数指针,当设备需要释放时调用此函数。

关于device_node结构体说明

struct device_node {
        const char *name; /* 节点中属性为name的值 */
        const char *type; /* 节点中属性为device_type的值 */
        char    *full_name; /* 节点的名字,在device_node结构体后面放一个字符串,full_name指向它 */
        struct  property *properties; /* 链表,连接该节点的所有属性 */
        struct  device_node *parent; /* 指向父节点 */
        struct  device_node *child; /* 指向孩子节点 */
        struct  device_node *sibling; /* 指向兄弟节点 */
};
posted @ 2023-01-11 15:55  韩若明瞳  阅读(578)  评论(0编辑  收藏  举报