devicetree和启动参数解析流程

devicetree和启动参数解析流程 Hilbert 2023-12-01 13:43:49 442

devicetree和启动参数解析流程



本文基于以下软硬件假定:
架构:AARCH64
内核版本:5.14.0-rc5

一、设备树解析概述 

AARCH64架构下内核可以通过设备树或acpi方式获取设备信息,其中acpi主要用于服务器领域,而设备树用于嵌入式领域。在设备树方式中,bootloader启动之前将设备树拷贝到内存中,并将地址通过x2寄存器传递给kernel,kernel启动时从设备树中读取启动参数和设备配置节点。

由于内存配置信息是由device tree传入的,而将device tree解析为device node的流程中需要为node和property分配内存。因此在device node创建之前需要先从device tree中解析出memory信息,故内核通过early_init_dt_scan接口实现了early的设备树信息扫描接口,以解析memory以及bootargs等一些启动早期需要使用的信息。  

在memory节点解析完成并被加入memblock之后,即可通过memblock为device node分配内存,从而可将完整的device tree信息解析到device node结构中。由于device node包含了设备树的所有节点以及它们之间的连接关系,因此此后内核就可以通过device node快速地索引设备节点。

二、early device tree 解析流程  

在AARCH64架构下,early device tree解析流程如下图:

其主要包括:

  1. 解析chosen节点中的initrd、bootargs和rng-seed属性,其中initrd包含其地址和size信息,rng-seed作为随机数种子添加到内核的随机数池中
  2. 获取root节点的size-cells和address-cells值
  3. 遍历memory节点的内存region,并将合法的region加入memblock中。若设备树中含有hotplug属性,则在内存flag中加上hotplug标志,以标识该内存是可热插拔的

三、device node节点创建流程  

内核通过unflatten_device_tree接口解析device tree中的node和property信息,并为解析到的每个节点创建device node,以及将解析到的属性添加到对应node中。其主要流程如下图:

  1. __unflatten_device_tree:解析设备树的所有节点和属性,根据解析到的信息创建和填充device node信息。device node通过父节点、子节点和兄弟节点三个指针来维护各节点之间的关系。如以下是一个含有六个节点的节点关系示意图:

  1. of_alias_scan:扫描device tree中的所有别名并填充别名信息,如名字,其对应的device node等。内核通过一个名为aliases_lookup全局链表维护所有扫描到的别名
  2. unittest_unflatten_overlay_base:解析unit test的device tree overlay,其解析完成的root device node保存在overlay_base_root中,并被用于其后的device tree 测试流程。其目的是为了尽量模拟实际设备树的行为,以测试功能的正确性。该测试流程由late_initcall(of_unittest)完成

四、bootargs参数解析

4.1 bootargs参数配置  

bootargs用于向内核传递启动参数,内核启动时解析这些参数并从特定的section中查找并执行参数处理函数,以实现对相关功能的配置。AARCH64架构的bootargs配置方式较灵活,主要有以下几种:

  1. 通过devicetree配置
  2. 通过内核配置选项配置
  3. 通过bootconfig方式配置  

通过devicetree配置是使用较多的方式,若对于使用uboot引导的内核,它又可以有以下配置方法:

  1. 直接在devicetree的chosen节点中定义bootargs
  2. 在uboot代码中通过环境变量的方式定义默认的bootargs
  3. 通过uboot的命令行修改bootargs环境变量。其中后两种方式在启动内核之前,uboot都会将环境变量中的bootargs值设置到devicetree中  

内核也可以通过CONFIG_CMDLINE配置选项设置bootargs,由该选项指定的bootargs参数在内核启动时有三种处理方式:

  1. 若设置了CONFIG_CMDLINE_EXTEND选项,则将该参数拼接到由device tree中解析出的参数后面
  2. 若设置了CONFIG_CMDLINE_FORCE选项,则用该参数替换从device tree中解析出的参数
  3. 未设置以上两个选项,则在device tree未设置bootargs时将该参数作为默认的bootargs,否则丢弃该参数  

bootconfig方式将启动参数以key=value的形式写到xbc(extra boot config)文件中,然后通过bootconfig工具将该文件追加到initrd的末尾,并随initrd一起被加载到内核。内核启动时通过bootconfig.c解析这些参数,并将其拼接到上面解析出参数的前面

4.2 early param参数解析

内核bootargs参数解析也包含两个阶段,eraly参数解析和常规参数解析。顾名思义,early参数解析位于常规参数解析之前,其流程如下:

  1. 遍历bootargs中的所有参数,并逐一处理
  2. 对特定参数判断其是属于kernel param类型还是setup类型参数
  3. early参数解析中不处理kernel param参数,因此不会执行这个分支
  4. 该流程执行实际的early param解析,其代码如下:
static int __init do_early_param(char *param, char *val,
                                 const char *unused, void *arg)
{
        const struct obs_kernel_param *p;

        for (p = __setup_start; p < __setup_end; p++) {
                if ((p->early && parameq(param, p->str)) ||
                    (strcmp(param, "console") == 0 &&
                     strcmp(p->str, "earlycon") == 0)
                ) {
                        if (p->setup_func(val) != 0)
                                pr_warn("Malformed early option '%s'\n", param);
                }
        }
        /* We accept everything at this stage. */
        return 0;
}

其会遍历setup section,查找与给定bootargs参数匹配且带有eraly标志,以及参数名为console或earlycon的entry,然后执行其对应的setup函数

4.3 常规参数解析  

常规参数解析流程与early参数解析类似,也是通过调用parse args接口解析相关参数。其解析流程如下:

由于带early标志的参数已经在early参数解析中处理,因此不需要再次处理。但其需要处理所有剩余的参数,参数匹配方式有两种:

  1. 内核通过setup方式注册的参数处理接口
  2. 内核通过kernel param方式注册的参数处理接口

4.4 kernel param注册  

kernel param用于设置或获取模块的参数,其主要是向系统注册一个参数相关的变量,并提供获取和修改该变量的get和set回调函数。参数可以通过module_param或module_param_cb宏定义,其定义如下:

#define module_param(name, type, perm)                               \
        module_param_named(name, name, type, perm)

其中参数含义如下:

  1. name:参数名
  2. type:参数类型
  3. perm:由sysfs使用的权限 

对于module param接口,使用内核定义的通用set和get方法操作该参数的值。而module_param_cb可以为用户提供自定义的set和get回调函数,其定义如下:

#define module_param_cb(name, ops, arg, perm)                                    \
        __module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)

  它们最终都会调用到以下所示的module_param_call宏,即定义并初始化一个struct kernel_param结构,并将其链接到param section中。内核中定义的所有module param都会被链接进该section中,并保存在代码段start_param到stop_param之间的位置。

  #define __module_param_call(prefix, name, ops, arg, perm, level, flags)     \
        /* Default value instead of permissions? */                     \
        static const char __param_str_##name[] = prefix #name;          \
        static struct kernel_param __moduleparam_const __param_##name   \
        __used __section("__param")                                     \
        __aligned(__alignof__(struct kernel_param))                     \
        = { __param_str_##name, THIS_MODULE, ops,                       \
            VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }

  上面的常规参数解析流程即是遍历start_param和stop_param之间的所有kernel param,并执行相应的set处理

4.5 setup接口注册  

__setup的原理与kernel param类似,也是通过定义并初始化一个链接到特殊段中的结构体,在内核启动处理常规参数解析时遍历该段中定义的entry,逐个与bootargs中解析到的参数比较,若找到匹配的entry则执行其相应的回调函数。  

与kernel param不同的是,kernel param回调的目的是设置或获取一个变量的值,而__setup宏的回调是可以任意定义的,它可以根据bootargs的输入参数执行用户自定义的操作。该宏的定义如下:

  #define __setup_param(str, unique_id, fn, early)                       \
        static const char __setup_str_##unique_id[] __initconst         \
                __aligned(1) = str;                                     \
        static struct obs_kernel_param __setup_##unique_id              \
                __used __section(".init.setup")                         \
                __aligned(__alignof__(struct obs_kernel_param))         \
                = { __setup_str_##unique_id, fn, early }

#define __setup(str, fn)                                                \
        __setup_param(str, fn, fn, 0)

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Hilbert
红包 1 1 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Hilbert
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区