0、说明
????????设备树子系统,将硬件独有信息抽取到特定格式文件中去。
1、dts格式
dts文件总布局
/dts-v1/;
//保存的内存,不会分给内核使用
[memory reservations]
/ { //根
[property definitions] //属性,如什么类型单板
[child nodes]
};
node节点格式
[label:] node-name[@unit-address] { //@unit-address分辨多个同类node
[properties definitions]
[child nodes]
};
Property属性格式
[label:] property-name = value;
[label:] property-name;
value三种格式
//32位数字 <>
clock-frequency = <0x00000001 0x00000000>;
//字符串 ""
compatible = "simple-bus";
//字节 []
local-mac-address = [00 00 12 34 56 78];
标准的property
?特殊说明
- 每个node唯一值phandle,可以被其他节点通过相同phandle索引到
- 后面的node属性会覆盖之前的属性
- ?#address-cells and #size-cells是指示reg属性的参数特点,单位u32
- 不同类型的属性,可以组合
- 同一级节点不能同名,类似于同目录下不能同名文件
- 通过label可直接应用到node
2、DTB格式
dtc编译dts文件后产生的一个dtb文件。如下,主要有包含头部在内的四部分组成。
2.1? DTB头部
? ? ? ? ?magic(0xd00dfeed)、dtb文件大小、其他段偏移地址和大小等。
2.2?Memory Reservation Block
struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};
2.3?Structure Block
? ? ? ? 存储了所有节点信息。
---------------0x00000001: 根节点开始
---------------:节点名字
---------------0x00000003: 属性开始
---------------:属性value长度(4B)
---------------:属性名字在string中的偏移(4B)
---------------:属性value内容
---------------0x00000003: 属性开始
---------------:属性value长度(4B)
---------------:属性名字在string中的偏移(4B)
---------------:属性value内容
---------------0x00000001:LED点开始
---------------:节点名字
---------------0x00000003: 属性开始
---------------:属性value长度(4B)
---------------:属性名字在string中的偏移(4B)
---------------:属性value内容
---------------0x00000003: 属性开始
---------------:属性value长度(4B)
---------------:属性名字在string中的偏移(4B)
---------------:属性value内容
---------------0x00000002:LED节点结束
---------------0x00000002: 根节点结束
---------------0x00000009: Structure Block的结束
????????
2.4 Strings Block
????????存储了所有属性的名字,如compatible、#address-cells等字符串。共给Structure Block通过偏移来引用。
3、内核中的设备树
3.1 内核中的设备树描述
Documentation
Documentation/devicetree/usage-model.rst
Linux uses DT data for three major purposes:
1) platform identification,? ? ? ? ? ? ? ? ? ? ? ? ?//平台识别信息,如早期初始化时的单板信息 2) runtime configuration, and? ? ? ? ? ? ? ? ? //运行时的配置,如console信息 3) device population.? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //设备? 如uart,SPI
device_node?
struct device_node {
const char *name;
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;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
#define MAX_PHANDLE_ARGS 16
struct of_phandle_args {
struct device_node *np;
int args_count;
uint32_t args[MAX_PHANDLE_ARGS];
};
3.2 linux内核启动阶段的设备树解析
3.2.1 uboot跳转内核的传参
????????uboot中增加使用theKernel(0, 362, 0x30000100);来跳转到内核。uboot将r0-r2三个参数传给了内核。
????????r0 一般设置为0; ????????r1 一般设置为machine id (在使用设备树时该参数没有被使用);? ????????r2 一般设置ATAGS或DTB的开始地址
? ? ? ? 最初在r2的位置,存放了uboot准备好的tags,传递给内核,当使用设备时,r2不再存放tag首地址,而是uboot将设备树加载到内存中的地址。
3.2.2 内核启动前期阶段对uboot传入参数的处理
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* linux/arch/arm/kernel/head.S
*
* Copyright (C) 1994-2002 Russell King
* Copyright (c) 2003 ARM Limited
* All Rights Reserved
*
* Kernel startup code for all 32-bit CPUs
*/
#include <linux/linkage.h>
#include <linux/init.h>
#include <linux/pgtable.h>
#include <asm/assembler.h>
#include <asm/cp15.h>
#include <asm/domain.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <asm/memory.h>
#include <asm/thread_info.h>
#if defined(CONFIG_DEBUG_LL) && !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif
/*
* swapper_pg_dir is the virtual address of the initial page table.
* We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we must
* make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
* the least significant 16 bits to be 0x8000, but we could probably
* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
*/
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif
#ifdef CONFIG_ARM_LPAE
/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE 0x5000
#define PMD_ORDER 3
#else
#define PG_DIR_SIZE 0x4000
#define PMD_ORDER 2
#endif
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET
sub \rd, \rd, #PG_DIR_SIZE
.endm
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
.arm
__HEAD
ENTRY(stext)
ARM_BE8(setend be ) @ ensure we are in BE8 mode
THUMB( badr r9, 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, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
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
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
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
/*
* 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.
*
* The processor init function will be called with:
* r1 - machine type
* r2 - boot data (atags/dt) pointer
* r4 - translation table base (low word)
* r5 - translation table base (high word, if LPAE)
* r8 - translation table base 1 (pfn if LPAE)
* r9 - cpuid
* r13 - virtual address for __enable_mmu -> __turn_mmu_on
*
* On return, the CPU will be ready for the MMU to be turned on,
* r0 will hold the CPU control register value, r1, r2, r4, and
* r9 will be preserved. r5 will also be preserved if LPAE.
*/
ldr r13, =__mmap_switched @ address to jump to after
@ mmu has been enabled
badr lr, 1f @ return (PIC) address
#ifdef CONFIG_ARM_LPAE
mov r5, #0 @ high TTBR0
mov r8, r4, lsr #12 @ TTBR1 is swapper_pg_dir pfn
#else
mov r8, r4 @ set TTBR1 to swapper_pg_dir
#endif
ldr r12, [r10, #PROCINFO_INITFUNC]
add r12, r12, r10
ret r12
1: b __enable_mmu
ENDPROC(stext)
.ltorg
#ifndef CONFIG_XIP_KERNEL
2: .long .
.long PAGE_OFFSET
#endif
开启mmu后跳转到__mmap_switched?? ??? ?
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register (exc_ret for M-class)
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
mov r7, r1
mov r8, r2
mov r10, r0
adr r4, __mmap_switched_data
mov fp, #0
#if defined(CONFIG_XIP_DEFLATED_DATA)
ARM( ldr sp, [r4], #4 )
THUMB( ldr sp, [r4] )
THUMB( add r4, #4 )
bl __inflate_kernel_data @ decompress .data to RAM
teq r0, #0
bne __error
#elif defined(CONFIG_XIP_KERNEL)
ARM( ldmia r4!, {r0, r1, r2, sp} )
THUMB( ldmia r4!, {r0, r1, r2, r3} )
THUMB( mov sp, r3 )
sub r2, r2, r1
bl memcpy @ copy .data to RAM
#endif
ARM( ldmia r4!, {r0, r1, sp} )
THUMB( ldmia r4!, {r0, r1, r3} )
THUMB( mov sp, r3 )
sub r2, r1, r0
mov r1, #0
bl memset @ clear .bss
ldmia r4, {r0, r1, r2, r3}
str r9, [r0] @ Save processor ID
str r7, [r1] @ Save machine type
str r8, [r2] @ Save atags pointer
cmp r3, #0
strne r10, [r3] @ Save control register values
mov lr, #0
b start_kernel
ENDPROC(__mmap_switched)
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data:
#ifdef CONFIG_XIP_KERNEL
#ifndef CONFIG_XIP_DEFLATED_DATA
.long _sdata @ r0
.long __data_loc @ r1
.long _edata_loc @ r2
#endif
.long __bss_stop @ sp (temporary stack in .bss)
#endif
.long __bss_start @ r0
.long __bss_stop @ r1
.long init_thread_union + THREAD_START_SP @ sp
.long processor_id @ r0
.long __machine_arch_type @ r1
.long __atags_pointer @ r2
#ifdef CONFIG_CPU_CP15
.long cr_alignment @ r3
#else
M_CLASS(.long exc_ret) @ r3
AR_CLASS(.long 0) @ r3
#endif
.size __mmap_switched_data, . - __mmap_switched_data
__FINIT
.text
最终将uboot传入的第三个参数r2的内容赋给C变量__atags_pointer中保存。之后调用start_kernel
3.2.3 内核启动阶段start_kernel对设备树的解析匹配
asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line); //处理命令行信息
setup_boot_config(command_line);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
build_all_zonelists(NULL);
page_alloc_init();
pr_notice("Kernel command line: %s\n", saved_command_line);
/* parameters may set static keys */
jump_label_init();
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
if (extra_init_args)
parse_args("Setting extra init args", extra_init_args,
NULL, 0, -1, -1, NULL, set_init_arg);
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
ftrace_init();
/* trace_printk can be enabled here */
early_trace_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (WARN(!irqs_disabled(),
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
radix_tree_init();
/*
* Set up housekeeping before setting up workqueues to allow the unbound
* workqueue to take non-housekeeping into account.
*/
housekeeping_init();
/*
* Allow workqueue creation and work item queueing/cancelling
* early. Work item execution depends on kthreads and starts after
* workqueue_init().
*/
workqueue_init_early();
rcu_init();
/* Trace events are available after this */
trace_init();
if (initcall_debug)
initcall_debug_enable();
context_tracking_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
/*
* For best initial stack canary entropy, prepare it after:
* - setup_arch() for any UEFI RNG entropy and boot cmdline access
* - timekeeping_init() for ktime entropy used in rand_initialize()
* - rand_initialize() to get any arch-specific entropy like RDRAND
* - add_latent_entropy() to get any latent entropy
* - adding command line entropy
*/
rand_initialize();
add_latent_entropy();
add_device_randomness(command_line, strlen(command_line));
boot_init_stack_canary();
time_init();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_init();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
/*
* This needs to be called before any devices perform DMA
* operations that might use the SWIOTLB bounce buffers. It will
* mark the bounce buffers as decrypted so that their usage will
* not cause "plain-text" data to be decrypted when accessed.
*/
mem_encrypt_init();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
setup_per_cpu_pageset();
numa_policy_init();
acpi_early_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pid_idr_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_stack_cache_init();
cred_init();
fork_init();
proc_caches_init();
uts_ns_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init();
pagecache_init();
signals_init();
seq_file_init();
proc_root_init();
nsfs_init();
cpuset_init();
cgroup_init();
taskstats_init_early();
delayacct_init();
poking_init();
check_bugs();
acpi_subsystem_init();
arch_post_acpi_subsys_init();
sfi_init_late();
kcsan_init();
/* Do the rest non-__init'ed, we're now alive */
arch_call_rest_init();
prevent_tail_call_optimization();
}
start_kernel
setup_arch(&command_line);
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);//尝试解析dtb
if (!mdesc) //失败则尝试解析tags
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
setup_machine_fdt
//检查头部是否合法
early_init_dt_verify(phys_to_virt(dt_phys))
fdt_check_header(params)
if (fdt_magic(fdt) != FDT_MAGIC)
return -FDT_ERR_BADMAGIC;
hdrsize = fdt_header_size(fdt);
/* Bounds check memrsv block */
/* Bounds check structure block */
/* Bounds check strings block */
//dtb地址被保存到全局变量initial_boot_params
initial_boot_params = params;
//解析找到最匹配的机器描述信息compatible = "xlnx,zynq-7000";
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);//匹配出最佳
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
early_init_dt_scan_nodes();
3.2.4 内核与设备树的板级信息匹配
设备树:
/ {
#address-cells = <1>;
#size-cells = <1>;
compatible = "xlnx,zynq-7000";
...
};
内核中板级信息(arch/arm/mach-zynq/common.c)
static const char * const zynq_dt_match[] = {
"xlnx,zynq-7000",
NULL
};
DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
/* 64KB way size, 8-way associativity, parity disabled */
#ifdef CONFIG_XILINX_PREFETCH
.l2c_aux_val = 0x30400000,
.l2c_aux_mask = 0xcfbfffff,
#else
.l2c_aux_val = 0x00400000,
.l2c_aux_mask = 0xffbfffff,
#endif
.smp = smp_ops(zynq_smp_ops),
.map_io = zynq_map_io,
.init_irq = zynq_irq_init,
.init_machine = zynq_init_machine,
.init_late = zynq_init_late,
.init_time = zynq_timer_init,
.dt_compat = zynq_dt_match,
.reserve = zynq_memory_init,
MACHINE_END
3.2.5 早期初始化对根节点chosen等属性的解析】
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn("No chosen node found, continuing without\n");
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
3.2.6 早期初始化对所有子节点的解析转换为device_node
????????从initial_boot_params处解析设备树生成根of_root。populate_node、populate_properties处理每个节点及其属性。
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
early_init_fdt_reserve_self();
/* Reserve the dtb region */
// 把DTB所占区域保留下来, 即调用: memblock_reserve
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params),
0);
early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
//从initial_boot_params处解析设备树生成根of_root
unflatten_device_tree(); // arch/arm/kernel/setup.c
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
populate_node
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
np->full_name = fn = ((char *)np) + sizeof(*np);
populate_properties
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
3.2.7 早期初始化device_node转换为platform_device
a. of_platform_default_populate_init (drivers/of/platform.c) 被调用到过程:
start_kernel // init/main.c
rest_init();
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数
b. of_platform_default_populate_init (drivers/of/platform.c) 生成platform_device的过程:
of_platform_default_populate_init
of_platform_default_populate(NULL, NULL, NULL);
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true); // 调用过程看下面
dev = of_device_alloc(np, bus_id, parent); // 根据device_node节点的属性设置platform_device的resource
if (rc) {
of_node_put(child);
break;
}
}
c. of_platform_bus_create(bus, matches, ...)的调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点):
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus节点的platform_device结构体
if (!dev || !of_match_node(matches, bus)) // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点
return 0;
for_each_child_of_node(bus, child) { // 取出每一个子节点
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 处理它的子节点, of_platform_bus_create是一个递归调用
if (rc) {
of_node_put(child);
break;
}
}
第三节总结
- 内核启动前期:解析了设备树中根节点的compatile,并找到最匹配的板级信息
- 内核启动前期:解析了根节点的属性信息,如chosen、memory等
- 内核启动前期:遍历了dtb中的所有节点,并生成了device_node
- 内核启动前期:遍历device_node,生成了设备树中的platform_device
- device_node信息被配置到platform_device中的res信息和dev->of_node
|