栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

关于linux设备树的简单理解(基于linux-5.13.5)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

关于linux设备树的简单理解(基于linux-5.13.5)

1. 设备树文件
  • 内核版本: linux-5.13.5
  • 设备树文件所在路径:linux-5.13.5archarmbootdts
  • 每个xxx.dts对应一个板子

dts目录下有两种文件:xxx.dts xxx.dtsi, 因为每个板子都有一个xxx.dts文件,在有多个板子共用同一个芯片时,那么dts文件中对于芯片的信息就会重复,这时就需要将这些重复的信息放入一个文件中,也就是dtsi文件,供dts文件调用,调用方式:

#include "omap36xx.dtsi"
2. 设备树文件编译

Linux在目录:linux/scripts/dtc下准备了编译设备树文件的工具DTC,在make dtbs的时候,会根据Makefile找到该目录下的DTC工具进行编译
如果把.dts文件当作.c文件,那么DTC工具就相当于gcc编译器,它会将.dts文件编译成.dtb文件,.dtb文件就相当于.bin文件

在Ubuntu中编译设备树文件方法:

export ARCH=arm
make deconfig
make dtbs


由打印信息可以看出,在arch/arm/boot/dts目录下生成了很多的dtb文件,make ARCH=arm设置的就是编译arch/arm下的dts文件,当然arch下还有很多架构,可以按需修改,进入如下目录可以看到生成的dtb文件。

如果我们自己写了一个dts文件,比如叫做zyuandy-zhang-can.dts,我们需要将该文件添加到arch/arm/boot/dts/目录下,再打开arch/arm/boot/dts/Makefile中添加zyuandy-zhang-can.dtb即可,因为dtc的编译规则是根据名字找到xxx.dts文件编译成xxx.dtb,具体添加到哪个系列下,要看开发板的型号,我添加到了CONFIG_ARCH_EXYNOS4系列下,make dtbs之后就会生成对应的dtb

3. DTS语法 3.1 设备节点

设备树以根目录" / "开始,每个根目录可以理解为一个开发板,那么每个节点就是该开发板上的设备

// SPDX-License-Identifier: GPL-2.0

/dts-v1/;

#include "omap36xx.dtsi"
#include "omap3-cm-t3x30.dtsi"

/ {
	model = "CompuLab CM-T3730";
	compatible = "compulab,omap3-cm-t3730", "ti,omap3630", "ti,omap36xx", "ti,omap3";

	...
};

&omap3_pmx_wkup {
	dss_dpi_pins_cm_t3730: pinmux_dss_dpi_pins_cm_t3730 {
		pinctrl-single,pins = <
			OMAP3_WKUP_IOPAD(0x2a08, PIN_OUTPUT | MUX_MODE3)   
			...
		>;
	};
};

&omap3_pmx_wkup节点的意思是向omap36xx.dtsi中的omap3_pmx_wkup节点中追加信息,在omap36xx.dtsi文件中没有搜索到omap3_pmx_wkup,但是该文件引入了omap3.dtsi文件,在omap3.dtsi中找到了节点:

omap3_pmx_wkup: pinmux@a00 {
	compatible = "ti,omap3-padconf",
		     "pinctrl-single";
	reg = <0xa00 0x5c>;
	#address-cells = <1>;
	#size-cells = <0>;
	#pinctrl-cells = <1>;
	#interrupt-cells = <1>;
	interrupt-controller;
	pinctrl-single,register-width = <16>;
	pinctrl-single,function-mask = <0xff1f>;
};

那么追加之后,节点就变成了:

omap3_pmx_wkup: pinmux@a00 {
	compatible = "ti,omap3-padconf",
		     "pinctrl-single";
	reg = <0xa00 0x5c>;
	#address-cells = <1>;
	#size-cells = <0>;
	#pinctrl-cells = <1>;
	#interrupt-cells = <1>;
	interrupt-controller;
	pinctrl-single,register-width = <16>;
	pinctrl-single,function-mask = <0xff1f>;
	
	dss_dpi_pins_cm_t3730: pinmux_dss_dpi_pins_cm_t3730 {
		pinctrl-single,pins = <
			OMAP3_WKUP_IOPAD(0x2a08, PIN_OUTPUT | MUX_MODE3)   
			...
		>;
	};
};

节点定义格式: 节点名称@节点单元地址

chipid@10000000 { //chipid就是节点名称 1000000为该节点的起始地址
			...
};

还有一种格式: 节点标签名: 节点名称@节点单元地址

serial_0: serial@13800000 { //serial_0是标签名 serial是节点名称 13800000是节点的起始地址
			...
};

由于有的节点名字过长,使用的时候不方便,所以就有了标签名,例如&serial_0就表示引用serial节点

serial节点就是用来描述数据手册中UART0的信息,在数据手册的Memory Map部分,如下图:

3.2 通用属性
sdhci_0: sdhci@12510000 {
			compatible = "samsung,exynos4210-sdhci";
			reg = <0x12510000 0x100>;
			interrupts = ;
			clocks = <&clock CLK_SDMMC0>, <&clock CLK_SCLK_MMC0>;
			clock-names = "hsmmc", "mmc_busclk.2";
			status = "disabled";
};

compatible属性:该属性用来绑定设备和驱动,格式:compatible = “厂商,驱动名称",一个节点代表一个设备,sdhci设备要想运行就需要驱动程序,在驱动程序中会有一个of_match_table属性,用来表示该驱动程序支持哪些设备,如下图所示,sdhci_s3c_dt_mach[]中的compatible属性支持 samsung公司的s3c6410-sdhci设备与samsung公司的exynos4210-sdhci设备

status属性:该属性表示设备的状态信息,格式:status = “value”; 常用的value有两种

valuedescribe
okey设备可操作
disable设备不可操作

reg属性:表示该设备的地址信息,格式:reg = addr表示设备的起始地址,len代表地址的长度,合起来就是该设备的地址空间,具体如何配置需要参照数据手册

#address-cells与#size-cells属性

apb: bus@fe000000 {  //某些地址为了讲解方便进行了修改
    compatible = "simple-bus";
	reg = <0x0 0xfe000000 0x0 0x1000000>;
	#address-cells = <2>;
	#size-cells = <2>;
	ranges = <0x1 0x2 0x3 0xfe000000 0x5 0x1000000>;

	reset: reset-controller@0 {
        compatible = "amlogic,meson-a1-reset";
		reg = <0x1 0x3 0x2 0x8c>;
		#reset-cells = <1>;
};

这两个属性用来控制子结点中reg属性中addr和len的数量,格式:#address-cells = ; #size-cells = ; 上述代码中,由于父节点apb(节点标签名)中的address-cells = 2,所以reg中有两个addr,size-cells = 2,所以有两个len

rangs属性,该属性是一个地址转换表,格式:rangs = ;上述代码中,父节点的

4. 开发板、设备树、驱动程序之间的关系

根节点的compatible属性:前边讲到设备节点的compatible属性是用来匹配设备与驱动的,根节点的compatible属性比较特殊,因为根节点代表的不是设备而更像是开发板,难道Linux内核烧到开发板上就能运行,显然不是的,要看内核支不支持该开发板,在没有引入设备树之前,是通过机器ID来判断的,这里主要讲解设备树的匹配方式,就是根据根节点的compatible属性判断,一个板子对应一个设备树文件,在内核启动阶段会调用start_kernel()函数

start_kernel() -> setup_arch(&command_line) -> setup_machine_fdt() -> of_flat_dt_match_machine() 在该函数中:

dt_root = of_get_flat_dt_root(); //获取设备树的根节点
while ((data = get_next_compat(&compat))) { //获取machine_desc中的.dt_compat属性
    score = of_flat_dt_match(dt_root, compat); //将根节点的cpmpatible属性与每个machine_desc中的.dt_compat进行比较,相同就返回machine_desc
	if (score > 0 && score < best_score) {
		best_data = data;
		best_score = score;
	}
}

如果返回的best_data不为空,就证明找到了对应开发板的驱动程序

get_next_compat会调用arch_get_next_mach函数,配合while循环到__arch_info_begin段去遍历machine_desc结构体,将取到的machine_desc结构体的.dt_compat属性存入compat中,作为of_flat_dt_match函数的参数

static const void * __init arch_get_next_mach(const char *const **match)
{
	static const struct machine_desc *mdesc = __arch_info_begin;
	const struct machine_desc *m = mdesc;
	if (m >= __arch_info_end)
		return NULL;
	mdesc++;
	*match = m->dt_compat;
	return m;
}

of_flat_dt_match会将取到的.dt_compat属性与根节点的compatible属性进行比较,如果相同说明开发板与设备树文件匹配成功

static int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
	unsigned int tmp, score = 0;

	if (!compat)
		return 0;

	while (*compat) {
		tmp = of_fdt_is_compatible(initial_boot_params, node, *compat);
		if (tmp && (score == 0 || (tmp < score)))
			score = tmp;
		compat++;
	}

	return score;
}

前边说到去__arch_info_begin去遍历machine_desc结构体,为什么呢?

在Linux/arch/arm目录下有很多的mach-xxxx文件,这里边的文件基本都是有厂家编写好的,每一个都代表一个或一系列的开发板,那么我们就以mach-imx目录下的mach-imx6q.c举例,该文件就是用来做开发板与设备树匹配的

在该文件中,我们可以看到以下代码

static const char * const imx6q_dt_compat[] __initconst = {
	"fsl,imx6dl",
	"fsl,imx6q",
	"fsl,imx6qp",
	NULL,
};

DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)")
	.l2c_aux_val 	= 0,
	.l2c_aux_mask	= ~0,
	.smp		= smp_ops(imx_smp_ops),
	.map_io		= imx6q_map_io,
	.init_irq	= imx6q_init_irq,
	.init_machine	= imx6q_init_machine,
	.init_late      = imx6q_init_late,
	.dt_compat	= imx6q_dt_compat, 
MACHINE_END

DT_MACHINE_START在linux/arch/arm/include/asm/mach/arch.h中定义,内容如下

#define DT_MACHINE_START(_name, _namestr)		
static const struct machine_desc __mach_desc_##_name	
 __used							
 __section(".arch.info.init") = {			
	.nr		= ~0,				
	.name		= _namestr,

#endif

宏定义展开之后其实是定义了一个machine_desc结构体,并将该结构体存到了.arch.info.init段中,这就是去__arch_info_begin去遍历machine_desc结构体的原因

现在machine_desc知道了从何而来,要到哪里去,可是要和设备树的根节点作比较,machine_desc从.arch.info.init段中获取,那设备树文件在哪取出来的?

of_flat_dt_match调用的of_fdt_is_compatible函数中有一个参数initial_boot_params,这个就是设备树文件的起始位置,是uboot提供给内核的,根节点就是从initial_boot_params指向的设备树中取出的,这样设备树文件与设备的匹配就清楚了

那驱动程序与开发板的设备是怎么匹配上的呢?

开发板就是我们说的设备树文件,以linux/arch/arm/boot/dts/s3c64xx.dtsi文件举例,假设我们的开发版已经和该文件匹配成功,所以该文件中每一个节点就代表开发板上的一个设备

sdhci0: sdhci@7c200000 {
    compatible = "samsung,s3c6410-sdhci";
    ...
};

要想使用该sdhci0设备,就需要有对应的驱动程序,在前文通用属性,设备的compatible属性的讲解中,该设备的compatible属性值"samsung,s3c6410-sdhci"与drivers/mmc/host/sdhci-s3c.c文件中的sdhci_s3c_dt_mach[]中的compatible属性相同,所以该设备的驱动程序就是sdhci-s3c.c文件,这样驱动程序与开发板的设备关系就清楚了

5. 设备树解析

调用流程start_kernel() -> setup_arch(&command_line) -> unflatten_device_tree()

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root, 
				early_init_dt_alloc_memory_arch, false);

	
	of_alias_scan(early_init_dt_alloc_memory_arch);

	unittest_unflatten_overlay_base();
}

of_root(linuxdrivers/of/fdt.c)在__unflatten_device_tree执行之后会指向设备树的根节点,__unflatten_device_tree第一次调用unflatten_dt_nodes给设备节点们分配空间,第二次调用才是真正的解析出了节点的内容,并将内容封装成device_node结构体

void *__unflatten_device_tree(const void *blob,
			      struct device_node *dad,
			      struct device_node **mynodes,
			      void *(*dt_alloc)(u64 size, u64 align),
			      bool detached)
{
	int size;
	void *mem;
	int ret;

	
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
    
	size = ALIGN(size, 4);

	
	mem = dt_alloc(size + 4, __alignof__(struct device_node));

	memset(mem, 0, size);

	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

	
	ret = unflatten_dt_nodes(blob, mem, dad, mynodes);

    ...
        
	return mem;
}

这时,设备树文件已经被解析成多个device_node存储到了内存中,等到之后用到设备树的驱动程序调用

6. 绑定信息文档

在linuxdocumentationdevicetreebindings下有很多的绑定信息文档,以.txt形式存在,可以帮助我们更好的编写设备树文件和添加设备节点

Required properties:
- compatible: Every devices present in OMAP SoC should be in the
  form: "ti,XXX"
- ti,hwmods: list of hwmod names (ascii strings), that comes from the OMAP
  HW documentation, attached to a device. Must contain at least
  one hwmod.

Optional properties:
- ti,no_idle_on_suspend: When present, it prevents the PM to idle the module
  during suspend.
- ti,no-reset-on-init: When present, the module should not be reset at init
- ti,no-idle-on-init: When present, the module should not be idled at init
- ti,no-idle: When present, the module is never allowed to idle.

Example:

spinlock@1 {
    compatible = "ti,omap4-spinlock";
    ti,hwmods = "spinlock";
};
7. of函数集的使用

linuxincludelinuxof.h中定义了很多以of开头的函数,这些函数可以用来获取设备的节点信息,要使用这些函数要引入一些头文件

#include 
#include 
#include 

假设我们要获取下面代码中的uart4节点的compatible属性

/ {
	cpus {
		cpu: cpu@0 {
			
			operating-points-v2 = <&cpu0_opp_table>;

			clock-latency = <300000>; 
		};
	};
	ocp@68000000 {
		uart4: serial@4809e000 {
			compatible = "ti,omap3-uart";
			ti,hwmods = "uart4";
			status = "disabled";
			reg = <0x4809e000 0x400>;
			interrupts = <84>;
			dmas = <&sdma 55 &sdma 54>;
			dma-names = "tx", "rx";
			clock-frequency = <48000000>;
		};
	};
};

函数:

static inline struct device_node *of_find_node_by_path(const char *path);//根据节点路径查找指定结点

用法举例:

struct device_node *node;
node = of_find_node_by_path("/ocp/uart4"); //uart4也可以用serial

节点获取到了不是我们最终的目标,我们的最终目标是获取节点的属性值,可以通过以下函数进行获取

属性结构体(linux/include/linux/of.h):

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
	unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
	unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
	struct bin_attribute attr;
#endif
};

函数:

extern struct property *of_find_property(const struct device_node *np, // 节点
					 const char *name, // 属性名称
					 int *lenp); // 属性值的字节数,一般为NULL

用法举例:

struct property *prop;
prop = of_find_property(node, "compatible", NULL); // node为之前获取到的节点
printf("compatible = %srn", prop->value); //该输出值等于"ti,omap3-uart"

在of.h中获取节点与其属性的函数还有很多这里不再一一举例

8. device_node与platform_device的转换

随便找了一个驱动函数的probe函数,看一看它是怎么获取节点与节点的属性的

static int plat_mpc8xxx_spi_probe(struct platform_device *pdev)
{
	struct resource *mem;
	int irq;
	struct spi_master *master;

	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!mem)
		return -EINVAL;

	irq = platform_get_irq(pdev, 0);
	if (irq <= 0)
		return -EINVAL;
    
	return PTR_ERR_OR_ZERO(master);
}

在上述代码中,我们并没有看到它用of函数获取结点,也没有看到device_node结构体,但是获取到的mem就是节点的reg属性,它是怎么获取到的呢?

of_platform_default_populate_init -> of_platform_populate() -> of_platform_bus_create() -> of_platform_device_create_pdata() -> of_device_alloc()

static int __init of_platform_default_populate_init(void)
{
	struct device_node *node;

	for_each_matching_node(node, reserved_mem_matches)
		of_platform_device_create(node, NULL, NULL);

	node = of_find_node_by_path("/firmware");
	if (node) {
		of_platform_populate(node, NULL, NULL, NULL);
		of_node_put(node);
	}

	
	of_platform_default_populate(NULL, NULL, NULL);

	return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

由于用arch_initcall_sync,该函数会在启动内核的过程中被调用

经过上述的调用过程,最终会调用到of_device_alloc函数

struct platform_device *of_device_alloc(struct device_node *np,
				  const char *bus_id,
				  struct device *parent)
{
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	dev = platform_device_alloc("", PLATFORM_DEVID_NONE); //申请platform_device结构体空间
	if (!dev)
		return NULL;

	
	while (of_address_to_resource(np, num_reg, &temp_res) == 0) //从设备树中获取reg的数量
		num_reg++;
	num_irq = of_irq_count(np); //获取irq的数量

	
	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL); //申请resource结构体空间
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res; //将申请的res赋值给platform_device结构体的resource属性
		for (i = 0; i < num_reg; i++, res++) { //将device_node的reg属性赋值给resource的属性
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)//将device_node的irq属性赋值给resource的属性
			pr_debug("not all legacy IRQ resources mapped for %pOFnn",
				 np);
	}

	dev->dev.of_node = of_node_get(np); //将device_node赋值给platform_device的dev.of_node属性
	dev->dev.fwnode = &np->fwnode;
	dev->dev.parent = parent ? : &platform_bus;

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);

	return dev;
}
EXPORT_SYMBOL(of_device_alloc);

该函数会将device_node转化为platform_device,所以之前的通过platform_device来获取节点的reg属性就很合理了

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/296769.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号