Linux设备树6(基于Linux6.6)---设备树运行时配置信息
一、前情回顾
上节在linux启动期间如何找到的最佳匹配的单板machine_desc;‘
asmlinkage __visible void __init start_kernel(void)
setup_arch(&command_line);
mdesc = setup_machine_fdt(__atags_pointer);
setup_machine_fdt-->early_init_dt_scan_nodes
二、运行配置信息
在setup_machine_fdt函数中找到的最佳匹配的machine_desc单板,在这个函数的后半部分的early_init_dt_scan_nodes()函数中,从dtb文件中取出了运行时参数。drivers/of/fdt.c
void __init early_init_dt_scan_nodes(void)
{
int rc;
/* Initialize {size,address}-cells info */
early_init_dt_scan_root();
/* Retrieve various information from the /chosen node,从chosen中取出启动命令参数 */
rc = early_init_dt_scan_chosen(boot_command_line);
if (rc)
pr_warn("No chosen node found, continuing without\n");
/* Setup memory, calling early_init_dt_add_memory_arch */
early_init_dt_scan_memory();
/* Handle linux,usable-memory-range property */
early_init_dt_check_for_usable_mem_range();
}
扫描获取chosen节点,把bootargs属性保存在boot_command_line数组中。drivers/of/fdt.c
int __init early_init_dt_scan_chosen(char *cmdline)
{
int l, node;
const char *p;
const void *rng_seed;
const void *fdt = initial_boot_params;
node = fdt_path_offset(fdt, "/chosen");
if (node < 0)
node = fdt_path_offset(fdt, "/chosen@0");
if (node < 0)
/* Handle the cmdline config options even if no /chosen node */
goto handle_cmdline;
chosen_node_offset = node;
early_init_dt_check_for_initrd(node);
early_init_dt_check_for_elfcorehdr(node);
rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
if (rng_seed && l > 0) {
add_bootloader_randomness(rng_seed, l);
/* try to clear seed so it won't be found. */
fdt_nop_property(initial_boot_params, node, "rng-seed");
/* update CRC check value */
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));
}
/* Retrieve command line */
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
strscpy(cmdline, p, min(l, COMMAND_LINE_SIZE));
handle_cmdline:
/*
* CONFIG_CMDLINE is meant to be a default in case nothing else
* managed to set the command line, unless CONFIG_CMDLINE_FORCE
* is set in which case we override whatever was found earlier.
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
strlcat(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* No arguments from boot loader, use kernel's cmdl*/
if (!((char *)cmdline)[0])
strscpy(cmdline, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */
pr_debug("Command line is: %s\n", (char *)cmdline);
return 0;
}
获取根接待中的size_cells和addr_cells属性drivers/of/fdt.c
/**
* early_init_dt_scan_root - fetch the top level address and size cells
*/
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
const __be32 *prop;
if (depth != 0)
return 0;
//给个默认参数,都是1
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
//从根节点获取#size-cells属性
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
dt_root_size_cells = be32_to_cpup(prop); //大端转小端字序
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
//从根节点获取#address-cells属性
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
/* break now */
return 1;
}
从根节点获取memory节点的属性drivers/of/fdt.c
/**
* early_init_dt_scan_memory - Look for and parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
/* 获取该节点的device_type属性 */
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;
/* We are scanning "memory" nodes only */确认是memory节点,不是的话不要,直接返回
if (type == NULL || strcmp(type, "memory") != 0)
return 0;
//获取可用的memory节点中的"linux,usable-memory"属性,没有则返回0
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL) //上面那种没找到,则查号reg属性,reg属性的值是address,size数组
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32)); //总的属性的长度
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL); //获取热插拔的内存节点
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
/*reg属性的值是address,size数组,那么如何来取出一个个的address/size呢?
*由于memory node一定是root node的child,因此dt_root_addr_cells(root node的#address-cells属性值)
*和dt_root_size_cells(root node的#size-cells属性值)之和就是address,size数组的entry size。*/
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
//获取reg属性中的addr和size属性的值,且reg属性也跟随增长
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
//添加内存块大小到内核内存池中
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable) //有热插拔内存的话继续,没有则查找reg属性列表的下一个值
continue;
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
return 0;
}
void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
const u64 phys_offset = MIN_MEMBLOCK_ADDR; //基本偏移0x80000000
//确认新加的内存块以页对齐
if (!PAGE_ALIGNED(base)) {
if (size < PAGE_SIZE - (base & ~PAGE_MASK)) { //内存块太小
pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
//大小足够,但没对齐的再强制对齐一次
size -= PAGE_SIZE - (base & ~PAGE_MASK);
base = PAGE_ALIGN(base);
}
size &= PAGE_MASK;
//起始不能超出寻址范围
if (base > MAX_MEMBLOCK_ADDR) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
//结束不能超出寻址范围,超出的部分扔掉
if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
size = MAX_MEMBLOCK_ADDR - base + 1;
}
//结束地址小于最小内存块范围
if (base + size < phys_offset) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
//起始地址小于最小内存块范围,那把小于最小内存块范围的部分扔掉
if (base < phys_offset) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
base, phys_offset);
size -= phys_offset - base;
base = phys_offset;
}
memblock_add(base, size); //加入内存池
}
可见运行时参数,基本都是保存在全局变量或者增加到内核中去。
三、总结
3.1、设备树的运行时处理流程
以下是设备树在 Linux 内核中的运行时处理流程的简化图示及每个步骤的说明:
+-------------------------+
| Bootloader 解析设备树 |
| (加载设备树到内存) |
+-----------+-------------+
|
v
+-------------------------+
| 内核启动加载设备树 |
| (设备树从内存或存储加载)|
+-----------+-------------+
|
v
+-------------------------+
| 设备树解析 |
| (内核解析设备树文件) |
+-----------+-------------+
|
v
+-------------------------+
| 内核平台初始化 |
| (硬件平台相关的设置) |
+-----------+-------------+
|
v
+-------------------------+
| 驱动程序初始化 |
| (驱动程序根据设备树信息|
| 配置硬件) |
+-----------+-------------+
|
v
+-------------------------+
| 设备注册到内核 |
| (通过设备模型和sysfs) |
+-----------+-------------+
|
v
+-------------------------+
| 驱动程序绑定设备 |
| (驱动程序根据设备树 |
| 配置硬件和管理设备) |
+-------------------------+
3.2、流程说明
-
Bootloader 解析设备树
- 在启动过程的早期,Bootloader(如 U-Boot)负责加载设备树(通常是一个
.dtb文件,二进制格式)到内存。此时,设备树会被放置到某个特定的内存位置,等待内核加载。
- 在启动过程的早期,Bootloader(如 U-Boot)负责加载设备树(通常是一个
-
内核启动加载设备树
- 当 Linux 内核启动时,它会从 Bootloader 传递给它的内存位置读取设备树数据。设备树数据在内核启动时就会被加载并准备好供内核使用。
-
设备树解析
- 内核在启动时会对设备树进行解析。Linux 内核使用设备树解析函数(如
of_fdt)来读取设备树中的信息,并将其转换成内核内部的数据结构(struct device_node)。
- 内核在启动时会对设备树进行解析。Linux 内核使用设备树解析函数(如
-
内核平台初始化
- 一旦设备树被解析,内核会根据设备树中描述的硬件信息初始化硬件平台,主要通过
platform_device和platform_driver机制进行。
- 一旦设备树被解析,内核会根据设备树中描述的硬件信息初始化硬件平台,主要通过
-
驱动程序初始化
- 内核的设备驱动程序会根据设备树中的信息来配置硬件设备。例如,设备树会描述各个硬件设备的地址、IRQ号、DMA配置、时钟信息等,驱动程序会根据这些信息来正确地初始化硬件。
-
设备注册到内核
- 驱动程序会创建相应的设备节点(通过
device_register()函数),并将设备信息注册到内核的设备模型中(通过sysfs接口向用户空间暴露设备)。
- 驱动程序会创建相应的设备节点(通过
-
驱动程序绑定设备
- 最后,设备驱动程序会绑定到已注册的硬件设备。绑定过程使得驱动程序可以通过设备树提供的硬件信息管理设备的生命周期,确保设备的初始化、操作和管理能够顺利进行。
3.3、核心组件
- Device Tree Blob (DTB):这是 Bootloader 加载的设备树文件。它包含了平台硬件信息(如中断、内存映射、时钟等),并以二进制格式存储。
- OF (Open Firmware) 解析:内核使用 Open Firmware(OF)接口来解析设备树,提供访问硬件信息的统一接口。
- 设备模型:内核的设备模型(Device Model)用于管理内核中的设备,并为每个设备分配
struct device结构,驱动程序通过这些设备进行操作。 - 驱动程序绑定:通过设备树描述的硬件信息,内核能够将设备与相应的驱动程序绑定。
447

被折叠的 条评论
为什么被折叠?



