Linux设备树详解(四)kernel的解析

本文深入探讨了设备树(DTB)如何将参数传递给内核,内核如何解析设备树并转化为设备节点结构。在启动过程中,U-Boot将设备树信息传递给内核,内核通过初始化流程获取设备树地址,并使用`setup_machine_fdt`等函数将设备树转换为设备节点。设备树中的`/chosen`节点用于传递运行时参数,`/memreserve`用于预留内存。内核通过`of_platform_populate`遍历设备树,为每个节点创建platform_device,并根据`compatible`属性匹配总线。整个过程展示了设备树在Linux内核设备驱动模型中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

uboot将一些参数,设备树文件传给内核,那么内核如何处理这些设备树文件呢?本章就kernel解析设备树的过程和原理,本章的主要内容以Device Tree相关的数据流分析为索引,对ARM linux kernel的代码进行解析。主要的数据流包括:

  1. 设备树对于内核的意义
  2. 从u-boot传递dtb开始,kernel初始化流程,如何将dtb并将其转换成Device Tree Structure
  3. 传递运行时参数传递以及platform的识别流程分析
  4. 如何将Device Tree Structure并入linux kernel的设备驱动模型。

1. 设备树的作用

由前面几章已经大致可以得出设备树对于内核的作用

作用详细描述
平台标识告诉内核dtb支持哪些平台 ; 用DT 来标识特定的machine ; root 节点的compatible 字段,匹配machine_desc的dt_compat
运行时配置chosen节点的属性
设备信息集合传递各种设备信息

2. 初始化流程

从上一章我们已经知道fdt的地址是作为参数传递到kernel。下面看一下kernel阶段怎么获取这个地址值的。bootloader启动内核时,会设置r0,r1,r2三个寄存器,

r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;

对于启动的流程代码如下:

ENTRY(stext)
 ARM_BE8(setend	be )			@ ensure we are in BE8 mode

THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.
THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
THUMB( .thumb ) @ switch to Thumb now.
THUMB(1: )

#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install
#endif
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9

mrc	p15<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> r9<span class="token punctuation">,</span> c0<span class="token punctuation">,</span> c0		@ get processor id
bl	__lookup_processor_type		@ r5<span class="token operator">=</span>procinfo r9<span class="token operator">=</span>cpuid
movs	r10<span class="token punctuation">,</span> r5				@ invalid processor <span class="token punctuation">(</span>r5<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">?</span>

THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error ‘p’

#ifdef CONFIG_ARM_LPAE
mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
and r3, r3, #0xf @ extract VMSA support
cmp r3, #5 @ long-descriptor translation table format?
THUMB( it lo ) @ force fixup-able long branch encoding
blo __error_lpae @ only classic page table format
#endif

#ifndef CONFIG_XIP_KERNEL
adr r3, 2f
ldmia r3, { r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
#else
ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
#endif

<span class="token comment">/*
 * r1 = machine no, r2 = atags or dtb,
 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
 */</span>
bl	__vet_atags

#ifdef CONFIG_SMP_ON_UP
bl __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
bl __fixup_pv_table
#endif
bl __create_page_tables

<span class="token comment">/*
 * The following calls CPU specific code in a position independent
 * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
 * xxx_proc_info structure selected by __lookup_processor_type
 * above.  On return, the CPU will be ready for the MMU to be
 * turned on, and r0 will hold the CPU control register value.
 */</span>
ldr	r13<span class="token punctuation">,</span> <span class="token operator">=</span>__mmap_switched		@ address to jump to after
					@ mmu has been enabled
adr	lr<span class="token punctuation">,</span> <span class="token function">BSYM</span><span class="token punctuation">(</span><span class="token number">1f</span><span class="token punctuation">)</span>			@ <span class="token keyword">return</span> <span class="token punctuation">(</span>PIC<span class="token punctuation">)</span> address
mov	r8<span class="token punctuation">,</span> r4				@ set TTBR1 to swapper_pg_dir

ARM( add pc, r10, #PROCINFO_INITFUNC )
THUMB( add r12, r10, #PROCINFO_INITFUNC )
THUMB( ret r12 )
1: b __enable_mmu
ENDPROC(stext)
.ltorg
#ifndef CONFIG_XIP_KERNEL
2: .long .
.long PAGE_OFFSET
#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  1. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
  2. __vet_atags : 判断是否存在可用的ATAGS或DTB

在汇编的阶段,大概可以看出来用变量__atags_pointer指向FDT的首地址,执行完汇编的阶段就会调到C代码的流程里面了

3. 平台信息的处理(machine_desc)

进入到start_kernel的处理流程中

asmlinkage void __init start_kernel(void)
{
    ...
    setup_arch(&command_line); //设置架构相关的内容
    ...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

由于涉及的知识点内容实在太多,那么我们只是重点的关注fdt的处理,直接进到setup_ arch()函数。

void __init setup_arch(char **cmdline_p)
{
...
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

首先通过set_machine_fdt来set_machine描述符,如果返回值是NULL,那么就采用传统的方式,如果u-boot传递了,就采用设备树方式

  1. 传统方式:对于如何确定mdesc,旧的方法是静态定义若干的machine描述符(struct machine_desc),在系统启动的时候,通过machine type ID作为索引,在这些静态定义的machine描述符中,找到对应哪个ID匹配的描述符。
  2. 设备树:通过__atags_pointer来找到对应的machine_desc设备描述符

首先我们来看看struct machine_desc的定义方式:

struct machine_desc {
...
	unsigned int		nr;		/* architecture number	*/
	const char		*name;		/* architecture name	*/
	unsigned long		atag_offset;	/* tagged list (relative) */
	const char *const 	*dt_compat;	/* array of device tree
...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

nr成员就是过去使用的machine type ID。内核machine描述符的table有若干个entry,每个都有自己的ID。bootloader传递了machine type ID,指明使用哪一个machine描述符。而dtb方式中目前匹配machine描述符使用compatible strings,也就是dt_compat成员,这是一个string list,定义了这个machine所支持的列表。

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 <span class="token operator">=</span> <span class="token function">of_flat_dt_match_machine</span><span class="token punctuation">(</span>mdesc_best<span class="token punctuation">,</span> arch_get_next_mach<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>mdesc<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>prop<span class="token punctuation">;</span>
	<span class="token keyword">int</span> size<span class="token punctuation">;</span>
	<span class="token keyword">unsigned</span> <span class="token keyword">long</span> dt_root<span class="token punctuation">;</span>

	<span class="token function">early_print</span><span class="token punctuation">(</span><span class="token string">"\nError: unrecognized/unsupported "</span>
		    <span class="token string">"device tree compatible list:\n[ "</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

	dt_root <span class="token operator">=</span> <span class="token function">of_get_flat_dt_root</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	prop <span class="token operator">=</span> <span class="token function">of_get_flat_dt_prop</span><span class="token punctuation">(</span>dt_root<span class="token punctuation">,</span> <span class="token string">"compatible"</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">while</span> <span class="token punctuation">(</span>size <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
		<span class="token function">early_print</span><span class="token punctuation">(</span><span class="token string">"'%s' "</span><span class="token punctuation">,</span> prop<span class="token punctuation">)</span><span class="token punctuation">;</span>
		size <span class="token operator">-</span><span class="token operator">=</span> <span class="token function">strlen</span><span class="token punctuation">(</span>prop<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
		prop <span class="token operator">+</span><span class="token operator">=</span> <span class="token function">strlen</span><span class="token punctuation">(</span>prop<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
	<span class="token function">early_print</span><span class="token punctuation">(</span><span class="token string">"]\n\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

	<span class="token function">dump_machine_table</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* does not return */</span>
<span class="token punctuation">}</span>

<span class="token comment">/* We really don't want to do this, but sometimes firmware provides buggy data */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>mdesc<span class="token operator">-&gt;</span>dt_fixup<span class="token punctuation">)</span>
	mdesc<span class="token operator">-&gt;</span><span class="token function">dt_fixup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">early_init_dt_scan_nodes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/* Change machine number to match the mdesc we're using */</span>
__machine_arch_type <span class="token operator">=</span> mdesc<span class="token operator">-&gt;</span>nr<span class="token punctuation">;</span>

<span class="token keyword">return</span> mdesc<span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

setup_machine_fdt函数的功能就是根据Device Tree的信息,找到最适合的machine描述符。其主要做了下面几件事情

  1. 传进来的fdt地址是物理地址,所以用phys_to_virt()函数转换为虚拟地址,同时进行合法检测
  2. 在machine描述符的列表中scan,找到最合适的那个machine描述符。和传统的方法类似,也是静态定义的。DT_MACHINE_START和MACHINE_END用来定义一个machine描述符。编译的时候,compiler会把这些machine descriptor放到一个特殊的段中(.arch.info.init),形成machine描述符的列表。
  3. of_get_flat_dt_prop(dt_root, “compatible”, &size)使用compatile属性的值, 跟’’‘每一个machine_desc.dt_compat’’'比较,
    成绩为"吻合的compatile属性值的位置",成绩越低越匹配, 对应的machine_desc即被选中
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;
<span class="token keyword">if</span> <span class="token punctuation">(</span>m <span class="token operator">&gt;=</span> __arch_info_end<span class="token punctuation">)</span>
	<span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span>

mdesc<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token operator">*</span>match <span class="token operator">=</span> m<span class="token operator">-&gt;</span>dt_compat<span class="token punctuation">;</span>
<span class="token keyword">return</span> m<span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

_arch_info_begin指向machine描述符列表第一个entry。通过mdesc++不断的移动machine描述符指针(Note:mdesc是static的)。match返回了该machine描述符的compatible string list。具体匹配的算法倒是很简单,就是比较字符串而已,最终找到对应的machine type。 从该流程可以知道,内核是可以支持很多种不同类型的设备,只要在bootloader传递的时候,传递对应不同的dtb表即可。

4. 运行时参数传递

设备树只是起一个信息传递的作用,对这些信息配置的处理,也比较简单,即从设备树的DTB文件中,把这些设备信息提取出来赋给内核中的某个变量即可。那么在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。紧接上章的函数继续分析

void __init early_init_dt_scan_nodes(void)
{
	/* Retrieve various information from the /chosen node */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
<span class="token comment">/* Initialize {size,address}-cells info */</span>
<span class="token function">of_scan_flat_dt</span><span class="token punctuation">(</span>early_init_dt_scan_root<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/* Setup memory, calling early_init_dt_add_memory_arch */</span>
<span class="token function">of_scan_flat_dt</span><span class="token punctuation">(</span>early_init_dt_scan_memory<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

该函数主要完成3个工作:

  • 扫描 /chosen node,保存运行时参数(bootargs)到boot_command_line,此外,还通过early_init_dt_check_for_initrd处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中 。其中主要是解析dts的配置为
	chosen {
		bootargs = "earlycon=sprd_serial,0x70100000,115200n8 loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw androidboot.hardware=sc9830";
		linux,initrd-start = <0x85500000>;
		linux,initrd-end = <0x855a3212>;
	};
 
 
  • 1
  • 2
  • 3
  • 4

bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来

  • 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 ,memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示
  • 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo通过memblock_add保存了系统内存相关的信息

5. dtb解析成device node

uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,为什么内核运行中,他不会去覆盖DTB所占用的那块内存呢?在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来。内核在arm_memblock_init中会使用early_init_fdt_scan_reserved_mem来配置fdt的内存,通知也回对memreserve指定内存进行保留操作。

void __init early_init_fdt_scan_reserved_mem(void)
{
	int n;
	u64 base, size;
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>initial_boot_params<span class="token punctuation">)</span>
	<span class="token keyword">return</span><span class="token punctuation">;</span>

<span class="token comment">/* Reserve the dtb region */</span>
<span class="token function">early_init_dt_reserve_memory_arch</span><span class="token punctuation">(</span><span class="token function">__pa</span><span class="token punctuation">(</span>initial_boot_params<span class="token punctuation">)</span><span class="token punctuation">,</span>
				  <span class="token function">fdt_totalsize</span><span class="token punctuation">(</span>initial_boot_params<span class="token punctuation">)</span><span class="token punctuation">,</span>
				  <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">/* Process header /memreserve/ fields */</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">;</span> n<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token function">fdt_get_mem_rsv</span><span class="token punctuation">(</span>initial_boot_params<span class="token punctuation">,</span> n<span class="token punctuation">,</span> <span class="token operator">&amp;</span>base<span class="token punctuation">,</span> <span class="token operator">&amp;</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>size<span class="token punctuation">)</span>
		<span class="token keyword">break</span><span class="token punctuation">;</span>
	<span class="token function">early_init_dt_reserve_memory_arch</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> size<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token function">of_scan_flat_dt</span><span class="token punctuation">(</span>__fdt_scan_reserved_mem<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">fdt_init_reserved_mem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

  • initial_boot_params实际上是dtb的虚拟地址,在early_init_dt_verify初始化的时候设定,首先进来就判断dtb是否存在,如果存在就将dtb的空间进行保留
  • 对fdt中的每一个节点调用__fdt_scan_reserved_mem函数,进行reserved-memory节点的扫描,之后调用fdt_init_reserved_mem函数进行内存预留的动作

说完了dtb对于内存的流程,那么来到这节的重点,dtb解析成device node,首先来看看下面的代码

void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, &of_allnodes,
				early_init_dt_alloc_memory_arch);
<span class="token comment">/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */</span>
<span class="token function">of_alias_scan</span><span class="token punctuation">(</span>early_init_dt_alloc_memory_arch<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

分析以上代码,在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。那么Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:

struct device_node {
	const char *name;					//device node name 
	const char *type;					//对应device_type的属性 
	phandle phandle;					//对应该节点的phandle属性
	const char *full_name;				//从“/”开始的,表示该node的full path
<span class="token keyword">struct</span>	property <span class="token operator">*</span>properties<span class="token punctuation">;</span>		<span class="token comment">//该节点的属性列表 </span>
<span class="token keyword">struct</span>	property <span class="token operator">*</span>deadprops<span class="token punctuation">;</span>		<span class="token comment">//如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表 </span>
<span class="token keyword">struct</span>	device_node <span class="token operator">*</span>parent<span class="token punctuation">;</span>		<span class="token comment">//parent、child以及sibling将所有的device node连接起来</span>
<span class="token keyword">struct</span>	device_node <span class="token operator">*</span>child<span class="token punctuation">;</span>	
<span class="token keyword">struct</span>	device_node <span class="token operator">*</span>sibling<span class="token punctuation">;</span>
<span class="token keyword">struct</span>	device_node <span class="token operator">*</span>next<span class="token punctuation">;</span>			<span class="token comment">//通过该指针可以获取相同类型的下一个node </span>
<span class="token keyword">struct</span>	device_node <span class="token operator">*</span>allnext<span class="token punctuation">;</span>		<span class="token comment">//通过该指针可以获取node global list下一个node</span>
<span class="token keyword">struct</span>	kobject kobj<span class="token punctuation">;</span>
<span class="token keyword">unsigned</span> <span class="token keyword">long</span> _flags<span class="token punctuation">;</span>
<span class="token keyword">void</span>	<span class="token operator">*</span>data<span class="token punctuation">;</span>

#if defined(CONFIG_SPARC)
const char path_component_name;
unsigned int unique_id;
struct of_irq_controller irq_trans;
#endif
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

对于dtb最后都存储在,其结构图如下
在这里插入图片描述

6. linux kernel的设备驱动模型

在linux kernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。在驱动初始化的时候会将代表该driver的一个数据结构挂入bus上的driver链表,device的数据结构挂入bus上的devie链表,那么如何让device遇到“对”的那个driver呢?那么就要靠缘分了,也就是bus的match函数来完成。在传统的方式中,代码中会定义一个static struct platform_device *xxx_devices的静态数组,在初始化的时候调用platform_add_devices。这些静态定义的platform_device往往又需要静态定义各种resource,那么对于设备树,也就是需要根据device_node的树状结构(root是of_allnodes)将一个个的device node挂入到相应的总线device链表中即可。

static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	if (machine_desc->init_machine)
		machine_desc->init_machine();
#ifdef CONFIG_OF
	else
		of_platform_populate(NULL, of_default_bus_match_table,
					NULL, NULL);
#endif
	return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

那么Linux系统是怎么知道哪些device node要注册为platform_device,哪些是用于i2c_client,哪些是用于spi_device?不知道你有没有注意到调用of_platform_populate的时候给它传递了一个参数of_default_bus_match_table

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
#ifdef CONFIG_ARM_AMBA
	{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
	{} /* Empty terminated list */
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

那么在dts文件会也会对对应的驱动进行配置

ap-apb {
		compatible = "simple-bus";
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;
	uart0: serial@70000000 {
		compatible = "sprd,sc9836-uart";
		reg = &lt;0x70000000 0x100&gt;;
		interrupts = &lt;GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH&gt;;
		clock-names = "uart", "source","enable";
		clocks = &lt;&amp;clk_uart0&gt;, &lt;&amp;ext_26m&gt;,
			&lt;&amp;clk_ap_apb_gates 13&gt;;
		status = "disabled";
	};

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如果某个device node的compatible属性的值与数组of_default_bus_match_table中的任意一个元素的compatible的值match,那么这个device node的child device node(device_node的child成员变量指向的是这个device node的子节点,也是一个链表)仍旧会被注册为platform_device。下面来看看重点的解析过程

int of_platform_populate(struct device_node *root,
			const struct of_device_id *matches,
			const struct of_dev_auxdata *lookup,
			struct device *parent)
{
	struct device_node *child;
	int rc = 0;
root <span class="token operator">=</span> root <span class="token operator">?</span> <span class="token function">of_node_get</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token function">of_find_node_by_path</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>root<span class="token punctuation">)</span>
	<span class="token keyword">return</span> <span class="token operator">-</span>EINVAL<span class="token punctuation">;</span>

<span class="token function">for_each_child_of_node</span><span class="token punctuation">(</span>root<span class="token punctuation">,</span> child<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	rc <span class="token operator">=</span> <span class="token function">of_platform_bus_create</span><span class="token punctuation">(</span>child<span class="token punctuation">,</span> matches<span class="token punctuation">,</span> lookup<span class="token punctuation">,</span> parent<span class="token punctuation">,</span> true<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>rc<span class="token punctuation">)</span>
		<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token function">of_node_put</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> rc<span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

该函数主要完成完成:

  1. 获取根节点,如果传递进来的参数root为NULL,那么需要通过of_find_node_by_path函数找到device tree中的根节点。
  2. 得到根节点之后,就可以通过这个根节点来遍历device tree中的节点了。得到一个子节点之后,调用of_platform_bus_create函数为每一个节点创建platform_device结构体
static int of_platform_bus_create(struct device_node *bus,
				  const struct of_device_id *matches,
				  const struct of_dev_auxdata *lookup,
				  struct device *parent, bool strict)
{
	const struct of_dev_auxdata *auxdata;
	struct device_node *child;
	struct platform_device *dev;
	const char *bus_id = NULL;
	void *platform_data = NULL;
	int rc = 0;
<span class="token comment">/* Make sure it has a compatible property */</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>strict <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">of_get_property</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> <span class="token string">"compatible"</span><span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"%s() - skipping %s, no compatible prop\n"</span><span class="token punctuation">,</span>
		 <span class="token constant">__func__</span><span class="token punctuation">,</span> bus<span class="token operator">-&gt;</span>full_name<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

auxdata <span class="token operator">=</span> <span class="token function">of_dev_lookup</span><span class="token punctuation">(</span>lookup<span class="token punctuation">,</span> bus<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>auxdata<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	bus_id <span class="token operator">=</span> auxdata<span class="token operator">-&gt;</span>name<span class="token punctuation">;</span>
	platform_data <span class="token operator">=</span> auxdata<span class="token operator">-&gt;</span>platform_data<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">of_device_is_compatible</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> <span class="token string">"arm,primecell"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token comment">/*
	 * Don't return an error here to keep compatibility with older
	 * device tree files.
	 */</span>
	<span class="token function">of_amba_device_create</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> bus_id<span class="token punctuation">,</span> platform_data<span class="token punctuation">,</span> parent<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

dev <span class="token operator">=</span> <span class="token function">of_platform_device_create_pdata</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> bus_id<span class="token punctuation">,</span> platform_data<span class="token punctuation">,</span> parent<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dev <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">of_match_node</span><span class="token punctuation">(</span>matches<span class="token punctuation">,</span> bus<span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>

<span class="token function">for_each_child_of_node</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> child<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token function">pr_debug</span><span class="token punctuation">(</span><span class="token string">"   create child: %s\n"</span><span class="token punctuation">,</span> child<span class="token operator">-&gt;</span>full_name<span class="token punctuation">)</span><span class="token punctuation">;</span>
	rc <span class="token operator">=</span> <span class="token function">of_platform_bus_create</span><span class="token punctuation">(</span>child<span class="token punctuation">,</span> matches<span class="token punctuation">,</span> lookup<span class="token punctuation">,</span> <span class="token operator">&amp;</span>dev<span class="token operator">-&gt;</span>dev<span class="token punctuation">,</span> strict<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span>rc<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
		<span class="token function">of_node_put</span><span class="token punctuation">(</span>child<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token keyword">break</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token function">of_node_set_flag</span><span class="token punctuation">(</span>bus<span class="token punctuation">,</span> OF_POPULATED_BUS<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> rc<span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

  1. 需要确定节点是否有"compatible"属性,如果没有"compatible"属性,则直接返回,即不会创建platform设备的。
  2. 如果"compatible"属性值有"arm,primecell",则会调用of_amba_device_create函数去创建amba_device,它设计了AMBA的总线来连接SOC内的各个block。符合这个总线标准的SOC上的外设叫做ARM Primecell Peripherals
  3. 如果不是ARM Primecell Peripherals,那么我们就需要向platform bus上增加一个platform device了,of_platform_device_create_pdata才是真正的platform_device
  4. 一个device node可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉
static struct platform_device *of_platform_device_create_pdata(
					struct device_node *np,
					const char *bus_id,
					void *platform_data,
					struct device *parent)
{
	struct platform_device *dev;
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">of_device_is_available</span><span class="token punctuation">(</span>np<span class="token punctuation">)</span> <span class="token operator">||</span>
    <span class="token function">of_node_test_and_set_flag</span><span class="token punctuation">(</span>np<span class="token punctuation">,</span> OF_POPULATED<span class="token punctuation">)</span><span class="token punctuation">)</span>
	<span class="token keyword">return</span> <span class="token constant">NULL</span><span class="token punctuation">;</span>

dev <span class="token operator">=</span> <span class="token function">of_device_alloc</span><span class="token punctuation">(</span>np<span class="token punctuation">,</span> bus_id<span class="token punctuation">,</span> parent<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dev<span class="token punctuation">)</span>
	<span class="token keyword">goto</span> err_clear_flag<span class="token punctuation">;</span>

<span class="token function">of_dma_configure</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>dev<span class="token operator">-&gt;</span>dev<span class="token punctuation">)</span><span class="token punctuation">;</span>
dev<span class="token operator">-&gt;</span>dev<span class="token punctuation">.</span>bus <span class="token operator">=</span> <span class="token operator">&amp;</span>platform_bus_type<span class="token punctuation">;</span>
dev<span class="token operator">-&gt;</span>dev<span class="token punctuation">.</span>platform_data <span class="token operator">=</span> platform_data<span class="token punctuation">;</span>

<span class="token comment">/* We do not fill the DMA ops for platform devices by default.
 * This is currently the responsibility of the platform code
 * to do such, possibly using a device notifier
 */</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">of_device_add</span><span class="token punctuation">(</span>dev<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
	<span class="token function">platform_device_put</span><span class="token punctuation">(</span>dev<span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token keyword">goto</span> err_clear_flag<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">return</span> dev<span class="token punctuation">;</span>

err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

  1. of_device_is_available函数,这个函数主要是用于检测"status"属性的,如果没有"status"属性,那还好说直接返回true。如果有"status"属性,而它的值又不是"okay"或"ok",那么不好意思,返回false,否则还是返回true。所以"status"属性就是用来检测是否可用,是否需要创建platform_node
  2. of_device_alloc除了分配struct platform_device的内存,还分配了该platform device需要的resource的内存。当然,这就需要解析该device node的interrupt资源以及memory address资源。
  3. 回到of_platform_device_create_pdata函数中,平台设备已经申请好了,然后对平台设备继续进行赋值操作,例如平台设备的总线赋值为平台总线,平台设备的私有数据赋值为platform_data,最终会调用of_device_add函数将平台设备注册到内核中。

也就是说当of_platform_populate()函数执行完毕,kernel就为DTB中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者"arm,amba-bus"的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并注册设备。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据of_root节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。

7 参考文档

http://www.wowotech.net/device_model/dt-code-analysis.html
https://blog.youkuaiyun.com/thisway_diy/article/details/84336817

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值