1. 程式人生 > 其它 >Linux 核心:裝置樹中的特殊節點

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 節點通常為空或者內容很少, imx6ull-alientekemmc.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\n", 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.\n",
                   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\n", 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\n", (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();