当前位置:   article > 正文

linux设备树-linux内核对设备树的处理

cannot find mu entry in device tree

----------------------------------------------------------------------------------------------------------------------------内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-boot:2016.05----------------------------------------------------------------------------------------------------------------------------

前面几节内容我们介绍了设备树的结构,以及在linux内核中移植设备树。这一节将对linux内核源码进行介绍,分析内核对设备的处理逻辑。

一、汇编阶段

1.1 uboot引导内核启动

linux设备树-linux内核设备树移植(一)中我们介绍了uboot引导linux内核的启动流程。uboot会为内核设置启动参数,最终并跳转到内核地址、启动内核。

uboot在启动内核时会设置三个参数,这三个参数会依次赋值给寄存器r0、r1、r2:

  • r0一般设置为0;
  • r1一般设置为machine id,在使用设备树的时候,这个参数就没有意义了;
  • r2一般设置为tag的开始地址,或者设置为dtb文件的开始地址;

当我们使用了设备树的时候,我们主要关注r2参数即可。

1.2 head.S

内核启动,首先执行的是arch/arm/kernel/head.S处的汇编代码,内核head.S主要做了以下工作:

  • 执行子程序__lookup_processor_type,使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息);
  • 执行子程序__vet_atags,判断是否存在可用的tag或dtb;
  • 执行子程序__create_page_tables:创建页表, 即创建虚拟地址和物理地址的映射关系;
  • 执行子程序__enable_mmu:使能MMU, 以后就要使用虚拟地址了;
  • 执行子程序__mmap_switched:__enable_mmu内部会调转到这里执行:

__mmap_switched子程序定义在是arch/arm/kernel/head-common.S文件中,该段子程序会调用C函数start_kernel。

以上的内核大概了解一下即可,这里不去分析具体源码了。head.S和head-common.S最终效果:

  • 把uboot传来的r1值,赋给了C变量 __machine_arch_type;
  • 把uboot传来的r2值,赋给了C变量__atags_pointer;
  • 然后执行C函数start_kernel。

二、内核启动

start_kernel函数位于init/main.c文件:

  1. asmlinkage __visible void __init start_kernel(void)
  2. {
  3. char *command_line;
  4. char *after_dashes;
  5. set_task_stack_end_magic(&init_task);
  6. smp_setup_processor_id();
  7. debug_objects_early_init();
  8. cgroup_init_early();
  9. local_irq_disable();
  10. early_boot_irqs_disabled = true;
  11. /*
  12. * Interrupts are still disabled. Do necessary setups, then
  13. * enable them.
  14. */
  15. boot_cpu_init();
  16. page_address_init();
  17. pr_notice("%s", linux_banner);
  18. setup_arch(&command_line);
  19. mm_init_cpumask(&init_mm);
  20. setup_command_line(command_line);
  21. setup_nr_cpu_ids();
  22. setup_per_cpu_areas();
  23. smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
  24. boot_cpu_hotplug_init();
  25. build_all_zonelists(NULL);
  26. page_alloc_init();
  27. pr_notice("Kernel command line: %s\n", boot_command_line);
  28. /* parameters may set static keys */
  29. jump_label_init();
  30. parse_early_param();
  31. after_dashes = parse_args("Booting kernel",
  32. static_command_line, __start___param,
  33. __stop___param - __start___param,
  34. -1, -1, NULL, &unknown_bootoption);
  35. if (!IS_ERR_OR_NULL(after_dashes))
  36. parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
  37. NULL, set_init_arg);
  38. /*
  39. * These use large bootmem allocations and must precede
  40. * kmem_cache_init()
  41. */
  42. setup_log_buf(0);
  43. vfs_caches_init_early();
  44. sort_main_extable();
  45. trap_init();
  46. mm_init();
  47. ftrace_init();
  48. /* trace_printk can be enabled here */
  49. early_trace_init();
  50. /*
  51. * Set up the scheduler prior starting any interrupts (such as the
  52. * timer interrupt). Full topology setup happens at smp_init()
  53. * time - but meanwhile we still have a functioning scheduler.
  54. */
  55. sched_init();
  56. /*
  57. * Disable preemption - early bootup scheduling is extremely
  58. * fragile until we cpu_idle() for the first time.
  59. */
  60. preempt_disable();
  61. if (WARN(!irqs_disabled(),
  62. "Interrupts were enabled *very* early, fixing it\n"))
  63. local_irq_disable();
  64. radix_tree_init();
  65. /*
  66. * Set up housekeeping before setting up workqueues to allow the unbound
  67. * workqueue to take non-housekeeping into account.
  68. */
  69. housekeeping_init();
  70. /*
  71. * Allow workqueue creation and work item queueing/cancelling
  72. * early. Work item execution depends on kthreads and starts after
  73. * workqueue_init().
  74. */
  75. workqueue_init_early();
  76. rcu_init();
  77. /* Trace events are available after this */
  78. trace_init();
  79. if (initcall_debug)
  80. initcall_debug_enable();
  81. context_tracking_init();
  82. /* init some links before init_ISA_irqs() */
  83. early_irq_init();
  84. init_IRQ();
  85. tick_init();
  86. rcu_init_nohz();
  87. init_timers();
  88. hrtimers_init();
  89. softirq_init();
  90. timekeeping_init();
  91. /*
  92. * For best initial stack canary entropy, prepare it after:
  93. * - setup_arch() for any UEFI RNG entropy and boot cmdline access
  94. * - timekeeping_init() for ktime entropy used in rand_initialize()
  95. * - rand_initialize() to get any arch-specific entropy like RDRAND
  96. * - add_latent_entropy() to get any latent entropy
  97. * - adding command line entropy
  98. */
  99. rand_initialize();
  100. add_latent_entropy();
  101. add_device_randomness(command_line, strlen(command_line));
  102. boot_init_stack_canary();
  103. time_init();
  104. printk_safe_init();
  105. perf_event_init();
  106. profile_init();
  107. call_function_init();
  108. WARN(!irqs_disabled(), "Interrupts were enabled early\n");
  109. early_boot_irqs_disabled = false;
  110. local_irq_enable();
  111. kmem_cache_init_late();
  112. /*
  113. * HACK ALERT! This is early. We're enabling the console before
  114. * we've done PCI setups etc, and console_init() must be aware of
  115. * this. But we do want output early, in case something goes wrong.
  116. */
  117. console_init();
  118. if (panic_later)
  119. panic("Too many boot %s vars at `%s'", panic_later,
  120. panic_param);
  121. lockdep_init();
  122. /*
  123. * Need to run this when irqs are enabled, because it wants
  124. * to self-test [hard/soft]-irqs on/off lock inversion bugs
  125. * too:
  126. */
  127. locking_selftest();
  128. /*
  129. * This needs to be called before any devices perform DMA
  130. * operations that might use the SWIOTLB bounce buffers. It will
  131. * mark the bounce buffers as decrypted so that their usage will
  132. * not cause "plain-text" data to be decrypted when accessed.
  133. */
  134. mem_encrypt_init();
  135. #ifdef CONFIG_BLK_DEV_INITRD
  136. if (initrd_start && !initrd_below_start_ok &&
  137. page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
  138. pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
  139. page_to_pfn(virt_to_page((void *)initrd_start)),
  140. min_low_pfn);
  141. initrd_start = 0;
  142. }
  143. #endif
  144. kmemleak_init();
  145. setup_per_cpu_pageset();
  146. numa_policy_init();
  147. acpi_early_init();
  148. if (late_time_init)
  149. late_time_init();
  150. sched_clock_init();
  151. calibrate_delay();
  152. pid_idr_init();
  153. anon_vma_init();
  154. #ifdef CONFIG_X86
  155. if (efi_enabled(EFI_RUNTIME_SERVICES))
  156. efi_enter_virtual_mode();
  157. #endif
  158. thread_stack_cache_init();
  159. cred_init();
  160. fork_init();
  161. proc_caches_init();
  162. uts_ns_init();
  163. buffer_init();
  164. key_init();
  165. security_init();
  166. dbg_late_init();
  167. vfs_caches_init();
  168. pagecache_init();
  169. signals_init();
  170. seq_file_init();
  171. proc_root_init();
  172. nsfs_init();
  173. cpuset_init();
  174. cgroup_init();
  175. taskstats_init_early();
  176. delayacct_init();
  177. poking_init();
  178. check_bugs();
  179. acpi_subsystem_init();
  180. arch_post_acpi_subsys_init();
  181. sfi_init_late();
  182. /* Do the rest non-__init'ed, we're now alive */
  183. arch_call_rest_init();
  184. }
View Code

可以看出,start_kernel函数是整个Linux内核启动过程中的一个关键函数,它负责调用其他函数来逐步初始化各个子系统,最终启动用户空间。

start_kernel的调用过程如下:

2.1 setup_arch

start_kernel内部调用setup_arch函数,用于在内核启动期间对硬件进行初始化,包括设置 CPU、内存、设备树等等。该函数的实现是针对特定架构的,因此不同的架构会有不同的 setup_arch实现。在内核启动期间,setup_arch函数是被显式调用的,它的返回值是 0 或者一个错误码,用于指示初始化是否成功。

  1. setup_arch(&command_line); // arch/arm/kernel/setup.c
  2. mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
  3. early_init_dt_verify(phys_to_virt(dt_phys) // 判断是否有效的dtb文件, drivers/of/fdt.c
  4. initial_boot_params = params;
  5. mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/fdt.c
  6. early_init_dt_scan_nodes();
  7. machine_desc = mdesc;
  8. ......
2.1.1 DT_MACHINE_START

一个编译成uImage的内核镜像文件,可以支持多个单板,比如支持SMDK2440、SMDK2443、以及我们在linux设备树-linux内核设备树移植中加入的MINI2440 using devicetree。

这些板子的配置稍有不同,需要做一些单独的初始化,在内核里面,对于这些单板,都构造了一个machine_desc结构体。

比如我们新增的arch/arm/mach-s3c24xx/mach-smdk2440-dt.c文件:

  1. static const char *const s3c2440_dt_compat[] __initconst = {
  2. "samsung,s3c2440",
  3. "samsung,mini2440",
  4. NULL
  5. };
  6. DT_MACHINE_START(S3C2440_DT, "Samsung S3C2440 (Flattened Device Tree)")
  7. /* Maintainer: Heiko Stuebner <heiko@sntech.de> */
  8. .dt_compat = s3c2440_dt_compat,
  9. .map_io = s3c2440_dt_map_io,
  10. .init_irq = irqchip_init,
  11. .init_machine = s3c2440_dt_machine_init,
  12. MACHINE_END

宏DT_MACHINE_START定义在arch/arm/include/asm/mach/arch.h,如下:

  1. #define DT_MACHINE_START(_name, _namestr) \
  2. static const struct machine_desc __mach_desc_##_name \
  3. __used \
  4. __attribute__((__section__(".arch.info.init"))) = { \
  5. .nr = ~0, \
  6. .name = _namestr,
  7. #endif

这里attribute((section(“.arch.info.init”)))就是利用了编译器的特性,把machine_desc放到了.arch.info.init段。

2.1.2 of_flat_dt_match_machine

当我们的uboot不使用tag传参数,而使用dtb文件时,那么这时内核是如何选择对应的machine_desc呢?

在设备树文件的根节点里,有如下两行:

  1. model = "Samsung S3C2440 SoC";
  2. compatible = "samsung,s3c2440","samsung,mini2440";

这里的compatible属性声明想要什么machine_desc,属性值可以是一系列字符串,依次与machine_desc匹配。

内核最好支持samsung,s3c2440,如果不支持,再尝试是否支持samsung,mini2440。

of_flat_dt_match_machine函数就是通过遍历所有的machine_desc,找到与设备树中compatible最匹配的一个machine_desc,如果没有找到匹配的机器,则返回默认的机器指针;函数定义在drivers/of/fdt.c中;

  1. /**
  2. * of_flat_dt_match_machine - Iterate match tables to find matching machine.
  3. *
  4. * @default_match: A machine specific ptr to return in case of no match.
  5. * @get_next_compat: callback function to return next compatible match table.
  6. *
  7. * Iterate through machine match tables to find the best match for the machine
  8. * compatible string in the FDT.
  9. */
  10. const void * __init of_flat_dt_match_machine(const void *default_match,
  11. const void * (*get_next_compat)(const char * const**))
  12. {
  13. const void *data = NULL;
  14. const void *best_data = default_match;
  15. const char *const *compat;
  16. unsigned long dt_root;
  17. unsigned int best_score = ~1, score = 0;
  18. dt_root = of_get_flat_dt_root();
  19. while ((data = get_next_compat(&compat))) {
  20. score = of_flat_dt_match(dt_root, compat);
  21. if (score > 0 && score < best_score) {
  22. best_data = data;
  23. best_score = score;
  24. }
  25. }
  26. if (!best_data) {
  27. const char *prop;
  28. int size;
  29. pr_err("\n unrecognized device tree list:\n[ ");
  30. prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
  31. if (prop) {
  32. while (size > 0) {
  33. printk("'%s' ", prop);
  34. size -= strlen(prop) + 1;
  35. prop += strlen(prop) + 1;
  36. }
  37. }
  38. printk("]\n\n");
  39. return NULL;
  40. }
  41. pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());
  42. return best_data;
  43. }

总结如下:

  • 设备树根节点的compatible属性列出了一系列的字符串,表示它兼容的单板名,从"最兼容"到次之;
  • 内核中有多个machine_desc,其中有dt_compat成员,,它指向一个字符串数组,,里面表示该machine_desc支持哪些单板;
2.1.3 early_init_dt_scan_nodes

early_init_dt_scan_nodes函数位于drivers/of/fdt.c文件:

  1. void __init early_init_dt_scan_nodes(void)
  2. {
  3. int rc = 0;
  4. /* Retrieve various information from the /chosen node */
  5. rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
  6. if (!rc)
  7. pr_warn("No chosen node found, continuing without\n");
  8. /* Initialize {size,address}-cells info */
  9. of_scan_flat_dt(early_init_dt_scan_root, NULL);
  10. /* Setup memory, calling early_init_dt_add_memory_arch */
  11. of_scan_flat_dt(early_init_dt_scan_memory, NULL);
  12. }

里面主要对三种类型的信息进行处理,分别是:

  • /chosen节点中bootargs属性:/chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来;这里将 /chosen节点中bootargs属性的值, 存入全局变量boot_command_line
  • 根节点的 #address-cells 和 #size-cells属性:根节点的#address-cells和#size-cells属性指定属性参数的位数;存入全局变量: dt_root_addr_cells, dt_root_size_cells;
  • /memory中的reg属性:/memory中的reg属性指定了不同板子内存的大小和起始地址;
2.1.4 设备节点转换为device_node

我们先想一个问题,我们的uboot把设备树dtb文件随便放到内存的某一个地方就可以使用,为什么内核运行中,他不会去覆盖dtb所占用的那块内存呢?

在前面我们讲解设备树结构时,我们知道,在设备树文件中,可以使用/memreserve/指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来。

如下就是函数调用过程:

  1. start_kernel // init/main.c
  2. setup_arch(&command_line); // arch/arm/kernel/setup.c
  3. arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
  4. early_init_fdt_reserve_self();
  5. /* Reserve the dtb region */
  6. // 把dtb所占区域保留下来, 即调用: memblock_reserve
  7. early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
  8. fdt_totalsize(initial_boot_params),
  9. 0);
  10. early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
  11. unflatten_device_tree(); // arch/arm/kernel/setup.c
  12. __unflatten_device_tree(initial_boot_params, NULL, &of_root,
  13. early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c
  14. /* First pass, scan for size */
  15. size = unflatten_dt_nodes(blob, NULL, dad, NULL);
  16. /* Allocate memory for the expanded device tree */
  17. mem = dt_alloc(size + 4, __alignof__(struct device_node));
  18. /* Second pass, do actual unflattening */
  19. unflatten_dt_nodes(blob, mem, dad, mynodes);
  20. populate_node
  21. np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
  22. __alignof__(struct device_node));
  23. np->full_name = fn = ((char *)np) + sizeof(*np);
  24. populate_properties
  25. pp = unflatten_dt_alloc(mem, sizeof(struct property),
  26. __alignof__(struct property));
  27. pp->name = (char *)pname;
  28. pp->length = sz;
  29. pp->value = (__be32 *)val;

可以看到,先把dtb中的memreserve信息告诉内核,把这块内存区域保留下来,不占用它。

然后解析dtb文件,转换成节点是device_node的树状结构,这里涉及两个结构体:

  • struct device_node:Linux内核使用device_node结构体来描述一个设备节点,此结构体定义在文件 include/linux/of.h 中;
  • struct property: Linux内核中使用结构体property表示节点属性,此结构体同样定义在文件include/linux/of.h中;

在dts文件里,每个大括号{}代表一个节点,比如根节点里有个大括号,对应一个device_node结构体。节点里面有各种属性,也可能里面还有子节点,所以它们还有一些父子关系。

根节点下的memory、chosen、uart、rtc等节点是并列关系,兄弟关系。对于父子关系、兄弟关系,在device_node结构体里有成员来描述这些关系。

2.2 arch_call_rest_init

start_kernel函数最后调用arch_call_rest_init函数,arch_call_rest_init函数是定义在init/main.c中的一个函数,用于调用没有被内核启动代码显式调用的初始化函数。

在内核启动过程中,当调用完所有的显式初始化函数后,arch_call_rest_init函数会被调用,以便调用其它的初始化函数,例如驱动程序注册、文件系统初始化、网络初始化等等。这些函数都由模块或子系统定义,并且它们的调用顺序可以在模块或子系统的 Makefile文件中进行配置。

需要注意的是,setup_arch主要负责硬件初始化,而arch_call_rest_init则主要负责软件初始化。它们的调用时机和作用不同,但都是Linux内核启动过程中不可或缺的环节。

在具体实现中,arch_call_rest_init函数会调用名为rest_init 的函数。而在 rest_init 函数中,会使用 fork系统调用创建两个新进程,分别为进程 1 和进程 2。其中:

  • 进程1会执行内核的初始化工作,比如初始化文件系统、内存管理等子系统;
  • 进程2则会负责启动 idle 进程并运行调度器;

在内核初始化完毕后,进程 1 会退出,而进程 2 则会成为系统中唯一的用户进程。

rest_init 函数位于init/main.c文件:

  1. noinline void __ref rest_init(void)
  2. {
  3. struct task_struct *tsk;
  4. int pid;
  5. rcu_scheduler_starting();
  6. /*
  7. * We need to spawn init first so that it obtains pid 1, however
  8. * the init task will end up wanting to create kthreads, which, if
  9. * we schedule it before we create kthreadd, will OOPS.
  10. */
  11. pid = kernel_thread(kernel_init, NULL, CLONE_FS);
  12. /*
  13. * Pin init on the boot CPU. Task migration is not properly working
  14. * until sched_init_smp() has been run. It will set the allowed
  15. * CPUs for init to the non isolated CPUs.
  16. */
  17. rcu_read_lock();
  18. tsk = find_task_by_pid_ns(pid, &init_pid_ns);
  19. set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
  20. rcu_read_unlock();
  21. numa_default_policy();
  22. pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  23. rcu_read_lock();
  24. kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  25. rcu_read_unlock();
  26. /*
  27. * Enable might_sleep() and smp_processor_id() checks.
  28. * They cannot be enabled earlier because with CONFIG_PREEMPT=y
  29. * kernel_thread() would trigger might_sleep() splats. With
  30. * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
  31. * already, but it's stuck on the kthreadd_done completion.
  32. */
  33. system_state = SYSTEM_SCHEDULING;
  34. complete(&kthreadd_done);
  35. /*
  36. * The boot idle thread must execute schedule()
  37. * at least once to get things moving:
  38. */
  39. schedule_preempt_disabled();
  40. /* Call into cpu_idle with preempt disabled */
  41. cpu_startup_entry(CPUHP_ONLINE);
  42. }
View Code
2.2.1 kernel_init 

进程1执行的是kernel_init函数,kernel_init函数位于init/main.c文件:

  1. static int __ref kernel_init(void *unused)
  2. {
  3. int ret;
  4. kernel_init_freeable();
  5. /* need to finish all async __init code before freeing the memory */
  6. async_synchronize_full();
  7. ftrace_free_init_mem();
  8. free_initmem();
  9. mark_readonly();
  10. /*
  11. * Kernel mappings are now finalized - update the userspace page-table
  12. * to finalize PTI.
  13. */
  14. pti_finalize();
  15. system_state = SYSTEM_RUNNING;
  16. numa_default_policy();
  17. rcu_end_inkernel_boot();
  18. if (ramdisk_execute_command) {
  19. ret = run_init_process(ramdisk_execute_command);
  20. if (!ret)
  21. return 0;
  22. pr_err("Failed to execute %s (error %d)\n",
  23. ramdisk_execute_command, ret);
  24. }
  25. /*
  26. * We try each of these until one succeeds.
  27. *
  28. * The Bourne shell can be used instead of init if we are
  29. * trying to recover a really broken machine.
  30. */
  31. if (execute_command) {
  32. ret = run_init_process(execute_command);
  33. if (!ret)
  34. return 0;
  35. panic("Requested init %s failed (error %d).",
  36. execute_command, ret);
  37. }
  38. if (!try_to_run_init_process("/sbin/init") ||
  39. !try_to_run_init_process("/etc/init") ||
  40. !try_to_run_init_process("/bin/init") ||
  41. !try_to_run_init_process("/bin/sh"))
  42. return 0;
  43. panic("No working init found. Try passing init= option to kernel. "
  44. "See Linux Documentation/admin-guide/init.rst for guidance.");
  45. }
View Code

函数调用流程:

  1. kernel_init
  2. kernel_init_freeable();
  3. do_basic_setup();
  4. do_initcalls();
  5. for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
  6. do_initcall_level(level); // 比如 do_initcall_level(3)
  7. for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
  8. do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数

do_initcall_level 是 Linux 内核中负责执行注册的初始化函数的函数之一。在内核启动过程中,各个子系统需要进行初始化,而这些初始化函数是通过调用 module_init、subsys_initcall、device_initcall 等宏定义的函数实现的。

这些宏实际上是向内核注册相关的初始化函数,然后将其存放到一个特定的段中,例如 __initcall_start 到 __initcall_end。

在内核初始化期间,do_initcall_level 函数按照预设的顺序(由参数 level 指定)执行这些初始化函数:

  1. leveltatic initcall_entry_t *initcall_levels[] __initdata = {
  2. __initcall0_start,
  3. __initcall1_start,
  4. __initcall2_start,
  5. __initcall3_start,
  6. __initcall4_start,
  7. __initcall5_start,
  8. __initcall6_start,
  9. __initcall7_start,
  10. __initcall_end,
  11. };
  12. /* Keep these in sync with initcalls in include/linux/init.h */
  13. static const char *initcall_level_names[] __initdata = {
  14. "pure",
  15. "core",
  16. "postcore",
  17. "arch",
  18. "subsys",
  19. "fs",
  20. "device",
  21. "late",
  22. };
  23. static void __init do_initcall_level(int level)
  24. {
  25. initcall_entry_t *fn;
  26. strcpy(initcall_command_line, saved_command_line);
  27. parse_args(initcall_level_names[level],
  28. initcall_command_line, __start___param,
  29. __stop___param - __start___param,
  30. level, level,
  31. NULL, &repair_env_string);
  32. trace_initcall_level(initcall_level_names[level]);
  33. for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
  34. do_one_initcall(initcall_from_entry(fn));
  35. }
View Code

该函数会检查 __initcall_start 到 __initcall_end 这个段中的所有函数,并将标记了 __initcall 的函数按照指定的级别(level)依次执行。

每个级别都以 __initcall[0-7]_start 标记开头,并且在 System.map 文件中可以看到相应的条目,比如:

  1. c06e687c T __initcall0_start
  2. c06e687c t __initcall_ipc_ns_init0
  3. c06e6880 t __initcall_init_mmap_min_addr0
  4. c06e6884 t __initcall_net_ns_init0
  5. c06e6888 T __initcall1_start
  6. c06e6888 t __initcall_ptrace_break_init1
  7. c06e688c t __initcall_s3c2442_core_init1
  8. c06e6890 t __initcall_s3c2440_core_init1
  9. c06e6894 t __initcall_samsung_gpiolib_init1
  10. c06e6898 t __initcall_wq_sysfs_init1
  11. c06e689c t __initcall_ksysfs_init1
  12. ......
  13. c06e6940 t __initcall_of_platform_default_populate_init3s
  14. c06e6944 T __initcall4_start
  15. c06e6944 t __initcall_topology_init4
  16. ......

System.map 是在 Linux 内核编译过程中生成的一个文件。它是内核的一个符号表,用于记录内核二进制映像中各个符号(变量、函数等)的地址,以便调试时能够快速定位到这些符号。通过 System.map 文件,开发人员可以查询内核二进制映像中每个符号的地址,以及符号所属的模块或函数名等信息。 

2..2.2 of_platform_default_populate_init

在System.map中可以找到__initcall_of_platform_default_populate_init3s,也就是在执行do_initcall_level(3)时,会执行到of_platform_default_populate_init函数。

of_platform_default_populate_init函数定义在drivers/of/platform.c:

  1. static int __init of_platform_default_populate_init(void)
  2. {
  3. struct device_node *node;
  4. if (!of_have_populated_dt())
  5. return -ENODEV;
  6. /*
  7. * Handle certain compatibles explicitly, since we don't want to create
  8. * platform_devices for every node in /reserved-memory with a
  9. * "compatible",
  10. */
  11. for_each_matching_node(node, reserved_mem_matches)
  12. of_platform_device_create(node, NULL, NULL);
  13. node = of_find_node_by_path("/firmware");
  14. if (node) {
  15. of_platform_populate(node, NULL, NULL, NULL);
  16. of_node_put(node);
  17. }
  18. /* Populate everything else. */
  19. of_platform_default_populate(NULL, NULL, NULL);
  20. return 0;
  21. }
  22. arch_initcall_sync(of_platform_default_populate_init);
View Code

可以看到of_platform_default_populate_init初始化函数是通过arch_initcall宏定义的实现的。

在这个函数内部,它会遍历设备节点树,检查设备节点上是否存在compatible属性,如果存在,就会为这个设备节点分配一个platform_device结构体,并将这些platform_device注册到Linux内核中,以便与其它驱动程序进行匹配和绑定。

并非所有的device_node都会转换为platform_device,只有以下的device_node会转换;

(1) 该节点必须含有compatible属性;根节点是例外的,生成platfrom_device时,即使有compatible属性也不会处理。

(2) 根节点的子节点(节点必须含有compatible属性);

(3) 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性),这些特殊的compatible属性为:

"simple-bus","simple-mfd","isa","arm,amba-bus "

比如dts文件配置如下:

  1. / {
  2. mytest {
  3. compatible = "mytest", "simple-bus";
  4. mytest@0 {
  5. compatible = "mytest_0";
  6. };
  7. };
  8. i2c {
  9. compatible = "samsung,i2c";
  10. at24c02 {
  11. compatible = "at24c02";
  12. };
  13. };
  14. spi {
  15. compatible = "samsung,spi";
  16. flash@0 {
  17. compatible = "winbond,w25q32dw";
  18. spi-max-frequency = <25000000>;
  19. reg = <0>;
  20. };
  21. };
  22. };

其中:

  • /mytest会被转换为platform_device, 因为它兼容"simple-bus",它的子节点/mytest/mytest@0 也会被转换为platform_device;
  • /i2c节点一般表示I2C控制器,,它会被转换为platform_device, 在内核中有对应的platform_driver;/i2c/at24c02节点不会被转换为platform_device,它被如何处理完全由父节点的platform_driver决定,一般是被创建为一个i2c_client;

  • 类似的也有/spi节点,,它一般也是用来表示SPI控制器,它会被转换为platform_device,在内核中有对应的platform_driver;/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device;

参考文章

[1]linux设备树学习笔记(二、linux内核对设备树的处理)(部分转载)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/904562
推荐阅读
相关标签
  

闽ICP备14008679号