首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

基于设备树的嵌入式系统硬件平台识别与参数传递流程解析

  • 25-03-04 14:02
  • 2906
  • 7375
blog.csdn.net

往期内容

总线:

  1. 驱动中的device和device_driver结构体-CSDN博客
  2. bus总线的相关结构体和注册逻辑-CSDN博客
  3. bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
  4. platform bus平台总线详解-CSDN博客

设备树:

  1. 设备树语法规则讲解-CSDN博客

前言

主要讲解的是嵌入式系统中如何通过设备树(Device Tree)进行扫描,完成硬件平台的识别和运行时参数的传递过程。内容涵盖了ARM架构下bootloader如何通过寄存器传递设备树地址,内核启动时如何解析设备树中的compatible字符串找到对应的硬件平台描述符(machine descriptor),以及如何通过设备树的/chosen节点获取启动参数(如命令行参数、内存布局)并初始化内核的内存管理。这种流程帮助内核自动适应不同硬件平台,提升可移植性。

1. 汇编部分的启动参数传递

在ARM架构上,内核的启动依赖于bootloader传递的几个关键寄存器值。汇编代码中的启动要求:

  • r0 = 0:保留,必须为0。
  • r1 = machine type ID:标识具体的硬件平台。
  • r2 = atags(旧方法)或 device tree binary(DTB) 的内存地址。

这里 r2 可以是指向传统的 atags 数据结构(旧的方式),也可以是指向设备树文件(DTB)的地址。设备树是以二进制文件的形式存储在内存中的,bootloader 将其加载并通过 r2 传递给内核。

汇编代码的作用

启动时,bootloader将这些寄存器设置好后,把控制权交给内核的启动代码,这些代码位于 linux/arch/arm/kernel/head.S 和 head-common.S 文件中。关键的任务是将 r1 和 r2 的值保存到内核的变量中,方便后续的C代码处理。这两个值会被保存在以下两个变量中:

  • __machine_arch_type:保存 r1,即 machine type ID。
  • __atags_pointer:保存 r2,即指向DTB或atags的指针。

在汇编阶段,这些信息只是简单地保存下来,实际的解析和使用会在C代码部分进行。

2. 内核 C 代码中 setup_arch 的处理

setup_arch 是启动过程中内核进行硬件平台初始化的入口函数。它的主要任务包括:

  • 确定硬件平台的类型(即确定用哪个 machine descriptor 描述符)。
  • 解析从bootloader传递的参数(例如命令行参数、内存布局等)。

下面看具体的C代码:Linux-4.9.88\arch\arm\kernel\setup.csetup.c

void __init setup_arch(char **cmdline_p)
{
    const struct machine_desc *mdesc;

    // 尝试通过设备树(DTB)查找合适的 machine 描述符
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
        // 如果没有找到合适的 machine,使用传统的 tags 方法查找
        mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);

    // 保存找到的 machine 描述符
    machine_desc = mdesc;
    machine_name = mdesc->name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

步骤解析:

  • 尝试通过设备树匹配平台:内核首先调用 setup_machine_fdt 函数,根据 __atags_pointer 指向的DTB文件解析设备树,并找到一个合适的 machine descriptor(平台描述符)。
  • 如果DTB无法匹配:如果通过DTB文件找不到合适的平台描述符,则退回到传统的方式 setup_machine_tags,使用 machine type ID 和 atags 查找平台信息。
  • 保存匹配的结果:无论通过DTB还是传统方式,最终都会得到一个 machine descriptor,保存到全局变量 machine_desc 中,用来标识当前系统运行在哪个硬件平台上。

3. 平台识别(Machine Descriptor匹配)

setup_machine_fdt 函数的任务是通过DTB文件的 compatible 字符串,找到合适的硬件平台描述符。代码如下:Linux-4.9.88\arch\arm\kernel\devtree.cdevtree.c

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

    // 如果DTB指针无效,或者DTB扫描失败,返回NULL
    if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
        return NULL;

    // 根据 compatible 字符串匹配最合适的 machine descriptor
    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

    if (!mdesc) { 
        // 错误处理
    }

    // 修改 machine type ID 以匹配找到的 machine 描述符
    __machine_arch_type = mdesc->nr;

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

解析:

  • early_init_dt_scan:这是解析DTB文件的核心函数之一,负责扫描DTB文件中的节点信息,包括 compatible 字符串。
  • of_flat_dt_match_machine:该函数负责将从DTB解析出来的 compatible 字符串与内核中静态定义的 machine descriptor 列表中的 compatible 字符串进行匹配。machine descriptor 是内核用来描述不同硬件平台的结构体,包含了每个平台的 compatible 字符串和其他硬件信息。

例子:

假设你的硬件平台在设备树中定义的 compatible 字符串为 "samsung,s3c2410", 内核会去扫描所有定义了 samsung,s3c2410 作为 compatible 字符串的平台描述符,找到最匹配的一个。

一旦找到匹配的平台描述符,内核会保存该描述符,并更新 machine type ID 为找到的描述符的编号。

4. 运行时参数传递

除了识别硬件平台之外,设备树还承担了传递运行时参数的任务。例如,bootloader会将启动时的命令行参数(bootargs)和内存布局等信息通过DTB传递给内核。

设备树的 /chosen 节点 是专门用来存储这些启动信息的。内核在启动时会扫描DTB中的 /chosen 节点,提取运行时参数,并保存到相应的全局变量中。以下是相关代码:\Linux-4.9.88\drivers\of\fdt.c fdt.c

bool __init early_init_dt_scan(void *params)
{
    bool status;

    // 验证设备树的有效性
    status = early_init_dt_verify(params);
    if (!status)
        return false;

    // 扫描设备树的所有节点
    early_init_dt_scan_nodes();
    return true;
}


bool __init early_init_dt_verify(void *params) 
{
    if (!params)
        return false;

    // 检查设备树头部的有效性
    if (fdt_check_header(params))
        return false;

    // 保存设备树的指针
    initial_boot_params = params;

    // 计算设备树的 CRC32 校验码,用于数据完整性检查
    of_fdt_crc32 = crc32_be(~0, initial_boot_params,
                            fdt_totalsize(initial_boot_params));
    return true;
}

void __init early_init_dt_scan_nodes(void)
{
    // 从 /chosen 节点中提取启动参数(如 bootargs)
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

    // 初始化设备树的 {size,address}-cells 属性
    of_scan_flat_dt(early_init_dt_scan_root, NULL);

    // 扫描 memory 节点,设定内存区域信息
    of_scan_flat_dt(early_init_dt_scan_memory, 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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

解析:

early_init_dt_verify: 这个函数负责验证设备树是否有效,并将其指针保存为全局变量。 保存DTB的指针,以便后续访问DTB文件中的节点。

  • fdt_check_header(params):这是一个标准函数,用来检查设备树头部的魔数和格式是否正确,确保传递的 params 符合设备树的格式。如果设备树无效,函数返回 false,停止后续处理。
  • *initial_boot_params = params:验证成功后,将 params 保存到全局变量 initial_boot_params,以便在其他地方可以访问设备树内容。
  • crc32_be():计算设备树的校验和 of_fdt_crc32,这在后续处理中可能会用来验证设备树数据的完整性。

early_init_dt_scan_nodes: 该函数的作用是扫描设备树中的各个节点,提取与运行时参数、内存布局等相关的重要信息。

  • of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line):
    • 该调用扫描设备树的 /chosen 节点,提取引导参数(如 bootargs)。
    • bootargs 是运行时传递给内核的命令行参数,如根文件系统位置、内核启动参数等。
    • 提取的信息会保存到全局变量 boot_command_line 中,供后续内核启动使用。
  • of_scan_flat_dt(early_init_dt_scan_root, NULL):
    • 这一步扫描设备树根节点,提取与 size-cells 和 address-cells 相关的信息。
    • size-cells 和 address-cells 是设备树中的两个重要属性,决定了地址和尺寸的表示方式(例如,使用几个字节表示内存的地址和大小)。
  • of_scan_flat_dt(early_init_dt_scan_memory, NULL):
    • 该调用扫描 memory 节点,提取系统的物理内存布局,并调用 early_init_dt_add_memory_arch 来设置内存区域。
    • 这一步是设定物理内存信息的关键步骤。

整个流程的核心工作包括设备树验证和节点扫描,其中 /chosen 节点负责传递引导参数(如 bootargs),/memory 节点负责定义系统的内存布局。

运行时参数传递流程总结:

  1. 引导参数:通过扫描 /chosen 节点,从中提取 bootargs 等关键的启动参数(例如内核启动时的命令行参数),并保存到全局变量 boot_command_line 中。
  2. 内存布局:通过扫描 /memory 节点,提取物理内存信息,并设置内存区域。这一步确保内核知道物理内存的分布,为后续内存管理做准备。
  3. 数据验证:通过 early_init_dt_verify 对设备树的头部进行检查,并通过 crc32_be 验证设备树数据的完整性。

5. 内存布局的传递

在ARM架构上,内核需要知道系统的物理内存布局(即内存的起始地址和大小)。通过Device Tree,bootloader可以在设备树中的 /memory 节点传递这些信息。内核通过解析这个节点,将内存信息保存到全局变量 meminfo 中。

/* 扫描设备树中的 /memory 节点,提取内存信息 */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
  • 1
  • 2

这一步是获取系统内存布局信息的核心,确保内核知道系统中的物理内存位置和大小。

6. 大概

  1. 汇编部分:bootloader通过寄存器传递硬件平台的 machine type ID 和设备树的地址。
  2. 内核C代码的setup_arch:通过DTB的 compatible 字符串匹配平台描述符,识别当前运行的硬件平台。
  3. 运行时参数传递:通过扫描设备树的 /chosen 节点,内核提取启动命令行参数 bootargs,并保存内存布局信息。

通过这种方式,Linux内核能够使用Device Tree文件在启动时自动识别硬件平台,并传递运行时参数,从而减少对硬件平台的依赖,提升了内核的可移植性。

注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/142798586"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top