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();