Linux-设备树

 

一:设备树概念

        Linux内核发展早期arm架构关于SOC和开发板及其外设的板级文件极其庞大,随着越来越多的SOC和开发板的诞生,其板级文件以指数级增长,同时越来越多的“垃圾文件”被编译进了Linux内核中。对此linus有话要说:“This whole ARM thing is a fucking pain in the ass”,arm的东西糟糕透顶了,于是arm就引入了powerpc架构中的设备树技术,将这些描述板级信息的内容从Linux中分离出来,用一个专属的文件格式来描述,这个文件就是设备树,文件扩展名为“.dts”,对于相同SOC作出的不同开发板的共同信息则提取到“.dtsi”文件。Linux内核用到的是DTC工具对DTS源文件编译后的DTB二进制文件,DTC工具源码位于linux内核scripts/dtc目录下,设备树源文件DTS位于arch/arm/boot/dts,编译设备树命令为“make dtbs”。

二:设备树语法

1,设备节点

/ {
	aliases {
		can0 = &flexcan1;
		can1 = &flexcan2;
        ... ...
	};

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
        ... ..
        }
    }
}

设备树是采用树形结构来描述板子上的设备信息文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,aliases和cpus是根节点“/”下的两个子节点,而cpu0又是cpus下面的子节点。设备节点命名格式:label:node-name@unit-address”

“label”:节点标签,可以直接通过&label来访问这个节点。不如对于节点“cpu0:cpu@0”来说通过&cpu0就可以访问“cpu@0”这个节点。

“node-name”:节点名字,为ASCII字符串,节点名字可以清洗的描述出该节点的功能,比如“spi1”就是表示这个节点是spi1外设。

“unit-address”:一般表示设备的地址或寄存器首地址,如果没有则可以省略。比如“cpu@0”

2,节点属性

节点是由一堆“属性”组成,节点都是具体的设备,不同的需要的属性不同,用户还可以自定义属性。

(1)compatible:“兼容性”属性,值为一个字符串列表,用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,设备会从这个字符串列表中依次进行匹配查找对应的驱动文件。

格式:“manufacturer,model”,manfacturer表示厂商,model表示模块对应的驱动名字。

示例:compatible = “fsl,imx6ul-evk-wm8960”,“fsl,imx-audio-wm8960”表示厂商飞思卡尔出品,采用vm8960音频芯片驱动模块。在驱动文件mx-wm8960.c中则会有以下内容与设备树文件完成适配。

扫描二维码关注公众号,回复: 12178885 查看本文章
sound {
    compatible = "fsl,imx6ul-evk-wm8960",
    "fsl,imx-audio-wm8960";
}
static const struct of_device_id imx_wm8960_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-wm8960", },
	{ /* sentinel */ }
};

(2)model:属性值为一个字符串,一般情况描述设备模块信息。

sound {
    model = "wm8960-audio";
}

(3)status:属性值为字符串,表示为设备状态

“okay”:表示设备可操作、

“disabled”:表示设备当前不可操作,未来可以变为可操作,例如热插拔设备。

“fail”:表示设备不可操作,设备检测到了一系列错误,未来不太可能变为可操作。

“fail-sss”:表示与“fail”相同,sss为检测到的错误内容。

(4)#address-cells和#size-cells:属性值为无符号32位整形,用于描述子节点的地址信息。

#address-cells属性值决定了子节点reg属性中地址信息所占的字长(32位)。

#size-cells属性值决定了子节点reg属性中长度信息所占的字长(32位)。

(5)reg:属性值和地址相关包括地址长度。

格式:reg = <address1 length1 address2 length2 address3 length3…………>

每个“address length”组合表示一个地址范围,address为起始地址,length为地址长度。#address-cells表示address这个数据所占用的字长,#size-cells表示length这个数据所占的字长。

spi4 {
	#address-cells = <1>;
	#size-cells = <0>;

	gpio_spi: gpio_spi@0 {
		reg = <0>;
	};
};

aips1: aips-bus@02000000 {
    #address-cells = <1>;
    #size-cells = <1>;

    pwm1: pwm@02080000 {
        reg = <0x02080000 0x4000>;
    }    
}

(6)ranges:属性值可以为空或者按照(chile-bus-address,parent-bus-address,length)格式编写的数字矩阵。

chile-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占用的字长

parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells确定此物理地址所占用的字长。

length:子地址空间的长度。

demo_level {
    compatible = "simple-bus";
    ranges = <0x0 0x30000000 0x3000>;
    #address-cells = <1>;
    #size-cells = <1>;

    range@0 {
        compatible = "range";
        reg = <0x100 0x200>;
        reg-names = "range0";
    };
}

节点demo-level属性ranges值为<0x0, 0x30000000, 0x3000>,表示指定一个0x3000大小的地址范围,子地址空间的物理起始地址为0x0,父地址空间的物理起始地址为0x30000000。节点range@0中的reg属性定义寄存器起始地址为0x100,寄存器长度为0x100。经过地址转换可得到range0设备的起始地址为:0x30000100=0x30000000+0x100,结束地址为:0x30000300=0x30000000+0x100+0x200。

(7)name:属性值为字符串,记录了节点名称。

(8)device_type:属性值为字符串,此属性对于设备树来讲只能用于cpu节点或者memory节点。见(二,1插图)

三:设备树与设备匹配方法

         每个节点都有“compatible”属性,根节点“/”下同样存在,Linux内核会通过根节点compatible属性查看是否支持该设备,如果支持就启动Linux内核。

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
}

1,在未引入设备树前

Linux内核会根据uboot传递过来的一个machine id值判断是否支持该设备。Linux内核中描述每个设备的数据格式如以下示例:

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <[email protected]> */
	.atag_offset	= 0x100,

	.init_irq	= s3c2440_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.init_time	= smdk2440_init_time,
MACHINE_END

其中MACHINE_START()原型为:

#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

将参数“S3c2440”和“SMDK2440”带入可得到“.nr = MACH_TYPE_S3C2440; .name  = SMDK2440”,其中MACH_TYPE_S3C2440就是SMDK2440这款开发板的machine id,定义在uboot的include/generated/mach-types.h文件,该数据存储在“.arch.info.init”段中。Linux内核就是根据这个machine id判断是否支持该设备。

2,引入设备树后

Linux内核描述设备的数据格式改为以下,通过观察可发现“.nr”的设置已经不一样了,证明引入设备树后已经不会再根据machine id来判断Linux内核是否支持某个设备。

#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,
static const char *imx6ul_dt_compat[] __initconst = {
	"fsl,imx6ul",
	"fsl,imx6ull",
	NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
	.map_io		= imx6ul_map_io,
	.init_irq	= imx6ul_init_irq,
	.init_machine	= imx6ul_init_machine,
	.init_late	= imx6ul_init_late,
	.dt_compat	= imx6ul_dt_compat,
MACHINE_END

machine_desc结构体中的“.dt_compat”成员变量保存着该设备兼容属性,其属性表imx6ul_dt_compat中含有“fsl,imx6ul”和“fsl,imx6ull”,只要某个设备根节点“/”的compatible属性值与imx6ul_dt_compat表中任何一个值匹配,那么就表示Linux内核支持此设备。

3,Linux内核与设备树匹配machine_desc从而启动Linux内核流程分析

在Linux内核启动函数start_kernel中有子函数“setup_arch(&command_line);”内核会用setup_arch函数来匹配machine_desc

asmlinkage __visible void __init start_kernel(void)
{

	lockdep_init();
    ... ...
	setup_arch(&command_line);
    ... ...
}
void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);                           /* (1) */
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

    ... ...
}

调用(1)处的函数来获取匹配的machine_desc,参数就是atags的首地址,也就是uboot传递给Linux内核的dtb文件的首地址,而返回值mdesc就是找到的最匹配的machine_desc。

const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
	const struct machine_desc *mdesc, *mdesc_best = NULL;

	if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
		return NULL;

	mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);          /* (2) */

	__machine_arch_type = mdesc->nr;

	return mdesc;
}

调用(2)处的函数来获取匹配的machine_desc,参数mdesc_best是默认的machine_desc。找到匹配的machine_desc的过程就是用设备树根节点的compatible属性值和Linux内核中保存的machine_desc结构的“.dt_compat”中的值比较,如果相等的话就表示找到匹配的machine_desc。

const void * __init of_flat_dt_match_machine(const void *default_match,
		const void * (*get_next_compat)(const char * const**))
{
	const void *data = NULL;
	const void *best_data = default_match;
	const char *const *compat;
	unsigned long dt_root;
	unsigned int best_score = ~1, score = 0;

	dt_root = of_get_flat_dt_root();                                      /* (3) */
	while ((data = get_next_compat(&compat))) {
		score = of_flat_dt_match(dt_root, compat);                        /* (4)*/
		if (score > 0 && score < best_score) {
			best_data = data;
			best_score = score;
		}
	}

... ...
	pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

	return best_data;

}

通过(3)处的函数获得设备树根和节点,while循环就是查找匹配的machine_desc的过程,(4)处的函数会将根节点conpatible属性的值和每个machine_desc结构体中.dt_compat的值进行比较。

4、Linux内核解析DTB文件

asmlinkage __visible void __init start_kernel(void)
{
    setup_arch(&command_line);
}

void __init setup_arch(char **cmdline_p)
{
    unflatten_device_tree();
}

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

static void __unflatten_device_tree(void *blob,
			     struct device_node **mynodes,
			     void * (*dt_alloc)(u64 size, u64 align))
{
    size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true); 
}

四:设备树常用操作函数

设备树描述了设备的详细信息,这些信息包括数字类型,字符串类型,数组类型,Linux内核提供了一系列的函数来获取这些信息。

设备都是以节点的方式存在于设备树文件中,为了获取这些设备的属性信息,Linux内核使用device_node结构体来描述一个节点。

1、查找节点的函数

struct device_node {
	const char *name;
	const char *type;
	phandle phandle;
	const char *full_name;
	struct fwnode_handle fwnode;

	struct	property *properties;
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;
	struct	device_node *child;
	struct	device_node *sibling;
	struct	kobject kobj;
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

(1)通过节点名字查找指定的节点

struct device_node *of_find_node_by_name(struct device_node *from,
	const char *name)

(2)通过节点类型type查找指定的节点,也就是device_type属性值

struct device_node *of_find_node_by_type(struct device_node *from,
	const char *type)

(3)通过device_type和compatible这两个属性查找指定的节点

struct device_node *of_find_compatible_node(struct device_node *from,
	const char *type, const char *compatible)

(4)通过of_device_id匹配表来查找指定的节点

static inline 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)

(5)通过路径来查找指定的节点

static inline struct device_node *of_find_node_by_path(const char *path)

2、查找父/子节点的函数

(1)获取指定节点的父节点

struct device_node *of_get_parent(const struct device_node *node)

(2)获取子节点

struct device_node *of_get_next_parent(struct device_node *node)

3、获取属性值函数

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};

(1)查找指定属性

struct property *of_find_property(const struct device_node *np,
				  const char *name,
				  int *lenp)

(2)获取属性中元素的数量,比如获取reg数组属性的数组大小

int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size)

(3)获取属性中指定标号的u32类型数据值

static inline int of_property_read_u32_index(const struct device_node *np,
			const char *propname, u32 index, u32 *out_value)

(4)获取属性中u8,u16,u32,u64类型的数组数据

static inline int of_property_read_u8_array(const struct device_node *np,
			const char *propname, u8 *out_values, size_t sz)

static inline int of_property_read_u16_array(const struct device_node *np,
			const char *propname, u16 *out_values, size_t sz)

static inline int of_property_read_u32_array(const struct device_node *np,
					     const char *propname,
					     u32 *out_values, size_t sz)

static inline int of_property_read_u64_array(const struct device_node *np,
					     const char *propname,
					     u64 *out_values, size_t sz)

(5)获取属性中的整形值

int of_property_read_u8(const struct device_node *np,
                        const char *propname,
                        u8 *out_value)

int of_property_read_u16(const struct device_node *np,
                        const char *propname,
                        u16 *out_value)

int of_property_read_u32(const struct device_node *np,
                        const char *propname,
                        u32 *out_value)

int of_property_read_u64(const struct device_node *np, 
                        const char *propname,
                        u64 *out_value)

(6)获取属性中字符串值

int of_property_read_string(struct device_node *np, const char *propname,
				const char **out_string)

(7)获取#address_cells和#size_cells值

int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);

4、其他常用函数

(1)查看节点compatible属性是否包含compat指定的字符串

int of_device_is_compatible(const struct device_node *device,
		const char *compat)

(2)获取地址相关属性,“reg”或者“assigned-address”

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
		    unsigned int *flags)

(3)将从设备树读取到的地址转换为物理地址

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)

(4)获取IIc,SPI等外设的资源“resource”

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

int of_address_to_resource(struct device_node *dev, int index,
			   struct resource *r)

(5)用户直接内存映射,取代ioremap的功能

void __iomem *of_iomap(struct device_node *np, int index)

猜你喜欢

转载自blog.csdn.net/qq_34968572/article/details/103183058