• Linux 内核:设备树中的特殊节点


    Linux 内核:设备树中的特殊节点

    背景

    在解析设备树dtb格式的时候,发现了这个,学习一下。

    参考:

    介绍

    常见的特殊节点有

    • aliases:用于定义别名,目的就是为了方便访问节点
    • chosen :chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少

    以我之前调试过的zynq平台为例。

    / {
        model = "ZynqMP ZCU104 RevA";
        compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
    
        aliases {
            ethernet0 = &gem3;
            gpio0 = &gpio;
            i2c0 = &i2c1;
            mmc0 = &sdhci1;
            rtc0 = &rtc;
            serial0 = &uart0;
            serial1 = &uart1;
            serial2 = &dcc;
            spi0 = &qspi;
            usb0 = &usb0;
        };
    
        chosen {
            bootargs = "earlycon";
            stdout-path = "serial0:115200n8";
        };
    
        memory@0 {
            device_type = "memory";
            reg = <0x0 0x0 0x0 0x80000000>;
        };
    };
    

    aliases 子节点

    单词 aliases 的意思是“别名”,因此 aliases 节点用于定义别名,目的就是为了方便访问节点。

    不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。

    / {
        model = "ZynqMP ZCU104 RevA";
        compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
    
        aliases {
            // ...
            spi0 = &qspi;
        };
    
        // ...
    };
    
    // ...
    &qspi {
        status = "okay";
        flash@0 {
            compatible = "m25p80", "spi-flash"; /* n25q512a 128MiB */
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x0>;
            spi-tx-bus-width = <1>;
            spi-rx-bus-width = <4>;
            spi-max-frequency = <108000000>; /* Based on DC1 spec */
            partition@qspi-fsbl-uboot { /* for testing purpose */
                label = "qspi-fsbl-uboot";
                reg = <0x0 0x100000>;
            };
            partition@qspi-linux { /* for testing purpose */
                label = "qspi-linux";
                reg = <0x100000 0x500000>;
            };
            partition@qspi-device-tree { /* for testing purpose */
                label = "qspi-device-tree";
                reg = <0x600000 0x20000>;
            };
            partition@qspi-rootfs { /* for testing purpose */
                label = "qspi-rootfs";
                reg = <0x620000 0x5E0000>;
            };
        };
    };
    

    chosen 子节点

    chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。

    一般.dts 文件中 chosen 节点通常为空或者内容很少。

    / {
        model = "ZynqMP ZCU104 RevA";
        compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
    
        chosen {
            bootargs = "earlycon";
            stdout-path = "serial0:115200n8";
        };
    
        // ...
    };
    

    从上面中可以看出, chosen 节点设置了

    • stdout-path”,表示标准输出使用 serial0
    • bootargs,表示用于Linux的启动参数

    uboot、linux与bootargs

    在支持设备树的嵌入式系统中,实际上:

    • uboot基本上可以不通过显式的bootargs=xxx来传递给内核,而是在env拿出,并存放进设备树中的chosen节点中
    • Linux也开始在设备树中的chosen节点中获取出来,

    这样子就可以做到针对uboot与Linux在bootargs传递上的统一。

    uboot 与 chosen

    结论:uboot 会自己在chosen 节点里面添加了 bootargs 属性!并且设置 bootargs 属性的值为 bootargs环境变量的值。

    因为在启动 Linux 内核之前,只有 uboot 知道 bootargs 环境变量的值,并且 uboot也知道.dtb 设备树文件在 DRAM 中的位置,所以uboot可以这样子做。

    // common/fdt_support.c 
    int fdt_chosen(void *fdt)
    {
        int   nodeoffset;
        int   err;
        char  *str;     /* used to set string properties */
    
        err = fdt_check_header(fdt);
        if (err < 0) {
            printf("fdt_chosen: %s
    ", fdt_strerror(err));
            return err;
        }
    
        /* find or create "/chosen" node. */
        // 从设备树(.dtb)中找到 chosen 节点,
        // 如果没有找到的话就会自己创建一个 chosen 节点
        nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
        if (nodeoffset < 0)
            return nodeoffset;
    
        // 读取 uboot 中 bootargs 环境变量的内容。
        str = getenv("bootargs");
        if (str) {
            // 向 chosen 节点添加 bootargs 属性,并且 bootargs 属性的值就是环境变量 bootargs 的内容
            err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
                      strlen(str) + 1);
            if (err < 0) {
                printf("WARNING: could not set bootargs %s.
    ",
                       fdt_strerror(err));
                return err;
            }
        }
    
        return fdt_fixup_stdout(fdt, nodeoffset);
    }
    

    调用流程:

    bootz
        do_bootz()
            do_bootm_states()
                boot_selected_os()
                    boot_fn() -> do_bootm_linux
                        // 准备启动Linux之前的一些工作
                        boot_prep_linux()
                            image_setup_linux()
                                image_setup_libfdt()
                                    fdt_chosen()
    

    上图中框起来的部分就是函数 do_bootm_linux 函数的执行流程,也就是说do_bootm_linux 函数会通过一系列复杂的调用,最终通过 fdt_chosen 函数在 chosen 节点中加入了 bootargs 属性。

    这样子,Linux内核在启动的时候,就可以根据bootargs来做自己要做的事情。

    linux与 chosen

    以arm架构为例。

    Linux会根据dtb中的chosen中的bootargs属性来重写cmd_lines

    int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                         int depth, void *data)
    {
        unsigned long l;
        char *p;
    
        pr_debug("search "chosen", depth: %d, uname: %s
    ", depth, uname);
    
        if (depth != 1 || !data ||
            (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
            return 0;
    
        early_init_dt_check_for_initrd(node);
    
        /* Retrieve command line */
        // 找到设备树中的的chosen节点中的bootargs,并作为cmd_line
        p = of_get_flat_dt_prop(node, "bootargs", &l);
        if (p != NULL && l > 0)
            strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
    
       // ...
    
        pr_debug("Command line is: %s
    ", (char*)data);
    
        /* break now */
        return 1;
    }
    

    流程如下:

    start_kernel
        setup_arch(&command_line);
            setup_machine_fdt();
                early_init_dt_scan_nodes();
                    early_init_dt_scan_chosen();
    
    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    Linux常用命令(5)--SSH访问远程服务器、SCP服务器间文件拷贝
    【转载】善用工具(1)--Mac版UltraEdit编辑器破解方法
    Linux常用命令(4)--善用"help"、"man在线帮助文档",轻松搞定系统命令
    Linux常用命令(3)--文件管理(查看文件大小权限信息、修改文件所属用户和操作权限、压缩解压文件)
    Linux常用命令(2)--vi (vim)文本编辑工具
    Linux常用命令(1)--用户管理(添加用户、修改密码、授予root权限)
    30分钟掌握ES6/ES2015核心内容(下)
    30分钟掌握ES6/ES2015核心内容(上)
    99%的人都理解错了HTTP中GET与POST的区别
    js中const,var,let区别
  • 原文地址:https://www.cnblogs.com/schips/p/special_node_of_device_tree.html
Copyright © 2020-2023  润新知