1. 什么是設備樹
設備樹的本質也是操作寄存器,只不過寄存器的相關信息放在了設備樹中,配置寄存器時需要使用OF函數從設備樹中讀取寄存器數據后再進行配置
Linux 3.x之前是沒有設備樹的,Linux通過內核源碼中arch/arm/mach-xxx和arch/arm/plat-xxx文件夾里的板級描述文件來描述ARM架構中的板級信息。 隨著ARM硬件種類增多,與板子相關的設備文件也越來越多,導致內核越來越大,而實際上這些硬件板級信息與內核并無相關關系
2011年,Linus發現該問題后,引入了PowerPC等架構已經采用的設備樹機制,將板級信息從內核中分離開來,用一個專屬的文件格式(.dts文件)來描述。 設備樹的作用就是描述硬件平臺的硬件資源,它可被bootloader傳遞到內核,內核可以從設備樹中獲取硬件信息。 設備樹描述硬件資源時有兩個特點:
- 以樹狀結構描述硬件資源
- 可以像頭文件那樣,一個設備樹文件可以引用另一個設備樹文件,實現代碼重用
DTS、DTSI、DTB、DTC文件的區別及定義:
DTC工具源碼在Linux內核的scripts/dtc/Makefile文件中:
hostprogs-y:= dtc
always:= $(hostprogs-y)
dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o srcpos.o checks.o util.o
dtc-objs+= dtc-lexer.lex.o dtc-parser.tab.o
......
由上可見DTC工具依賴于dtc.c, flattree.c, fstree.c等文件,最終編譯并鏈接出DTC這個主機文件。 若要編譯DTS文件,只需要進入到Linux源碼根目錄下,然后執行如下命令:
make all #編譯Linux源碼中的所有東西,包括zImage、.ko驅動模塊以及設備樹
make dtbs #只是編譯設備樹
基于ARM架構的SOC有很多,一種SOC又可制作出多款板子,每個板子都有對應的DTS文件,那么如何確定編譯哪一個DTS文件呢? 以I.MX6ULL芯片的板子為例,打開arch/arm/boot/dts/Makefile,有如下內容:
dtb-$(CONFIG_SOC_IMX6UL) += \\
imx6ul-14x14-ddr3-arm2.dtb \\
imx6ul-14x14-ddr3-arm2-emmc.dtb \\
......
dtb-$(CONFIG_SOC_IMX6ULL) += \\
imx6ull-14x14-ddr3-arm2.dtb \\
imx6ull-14x14-ddr3-arm2-adc.dtb \\
......
選中I.MX6ULL后(即CONFIG_SOC_IMX6ULL=y),所有使用該SOC的板子對應的.dts文件都會被編譯為.dtb。 若新做一個板子,只需要新建一個對應的.dts文件,再將對應的.dtb文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,這樣在編譯設備樹時就會將對應的.dts編譯為二進制的.dtb文件
2. 設備樹語法介紹
2.1 設備樹代碼分析
關于i. MX6ULL已有的設備樹文件,大致有以下幾種
imx6ull-14x14-evk-emmc.dts文件:在/arch/arm/boot/dts/文件夾中,描述了emmc板子的usdhc信息。 該文件通過頭文件的形式包含了另一個設備樹文件
#include "imx6ull-14x14-evk.dts"
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};
imx6ull-14x14-evk.dts文件:在/arch/arm/boot/dts/文件夾中,包含了根節點、子節點及其他追加內容
#include
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory {
reg = <0x80000000 0x20000000>;
};
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x14000000>;
linux,cma-default;
};
};
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
};
pxp_v4l2 {
compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
status = "okay";
};
regulators {
compatible = "simple-bus";
......
};
......
};
&cpu0 {
arm-supply = <®_arm>;
soc-supply = <®_soc>;
dc-supply = <®_gpio_dvfs>;
};
&clks {
assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <786432000>;
};
......
&wdog1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_wdog>;
fsl,wdog_b;
};
imx6ull.dtsi文件:是設備樹的頭文件,其格式與設備樹基本相同
#include
#include "imx6dl-pinfunc.h"
#include "imx6qdl.dtsi"
/ {
aliases {
i2c3 = &i2c4;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a9";
device_type = "cpu";
......
};
cpu@1 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <1>;
next-level-cache = <&L2>;
};
};
reserved-memory {
......
};
soc {
......
ocram: sram@00905000 {
compatible = "mmio-sram";
reg = <0x00905000 0x1B000>;
clocks = <&clks IMX6QDL_CLK_OCRAM>;
};
......
};
};
......
&vpu_fsl {
iramsize = <0>;
};
2.2 DTS語法介紹
設備樹采用樹形結構來描述板子上的設備信息,每個設備都是一個節點,叫做設備節點,每個節點都通過一些屬性信息來描述節點信息,屬性就是鍵-值對
node-name@unit-address{
屬性1 = ...
屬性2 = ...
子節點...
}
節點名稱:node-name用于指定節點名稱,應使用字母開頭,并能描述設備類別(根節點用斜杠表示)
單元地址:@unit-address用于指定單元地址,@為分隔符,后面是實際的單元地址,它的值與節點reg屬性的第一個地址一致,若沒有reg屬性值,則可以省略單元地址
節點屬性:節點的大括號{ }中包含的內容是節點屬性, 一個節點可以包含多個屬性信息,設備樹最主要的內容就是編寫節點的屬性。 屬性包括自定義屬性和標準屬性
- model屬性:用于指定設備的制造商和型號,多個字符串使用“,”分隔開
- compatible屬性:由一個或多個字符串組成,是用來查找節點的方法之一
- status屬性:用于指示設備的操作狀態,通過status可以禁用或啟用設備
- reg屬性:描述設備資源在其父總線定義的地址空間內的地址,通常用于表示一塊寄存器的起始地址和長度
- #address-cells 和 #size-cells:這兩個屬性同時存在,前者決定了子節點reg屬性中地址信息所占用的字長,后者決定了長度信息所占的字長
- ranges屬性:是一個地址映射/轉換表,由子地址、父地址和地址空間長度這三部分組成
- child-bus-address:子總線地址空間的物理地址, 由父節點的#address-cells 確定此物理地址所占用的字長
- parent-bus-address:父總線地址空間的物理地址,同樣由父節點的#address-cells 確定此物理地址所占用的字長
- length:子地址空間的長度,由父節點的#size-cells 確定此地址長度所占用的字長
特殊節點:aliases子節點、chosen子節點
//為其他節點起一個別名
aliases {
i2c3 = &i2c4;
};
//該節點位于根節點下,它不代表實際硬件,主要用于給內核傳遞參數
//下面代碼表示系統標準輸出stdout使用串口uart1
chosen {
stdout-path = &uart1;
};
3. 設備樹OF函數
內核提供了一系列函數用于從設備節點獲取節點中定義的屬性,這些函數以of_開頭,稱為OF函數。 在編寫設備樹版的驅動時,在進行硬件配置方面,就是要用這些OF函數,將寄存器地址等信息從設備樹文件中獲取出來,然后進行配置
3.1 查找節點的OF函數
設備都是以節點的形式掛到設備樹上的,因此要獲取這個設備的其他屬性信息,必須先獲取到這個設備的節點。 內核使用device_node結構體來描述一個節點,此結構體定義在文件include/linux/of.h中,定義如下:
struct device_node {
const char *name; /* 節點名字 */
const char *type; /* 設備類型 */
phandle phandle;
const char *full_name; /* 節點全名 */
struct fwnode_handle fwnode;
struct property *properties; /* 屬性 */
struct property *deadprops; /* removed 屬性 */
struct device_node *parent; /* 父節點 */
struct device_node *child; /* 子節點 */
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
of_find_node_by_name:通過節點名字查找指定的節點
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
//from:開始查找的節點,NULL表示從根節點開始查找整個設備樹
//name:要查找的節點名字
//返回值:找到的節點,NULL表示查找失敗
of_find_node_by_type:通過device_type屬性查找指定的節點
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
//from:開始查找的節點,NULL表示從根節點開始查找整個設備樹
//type:要查找的節點對應的type字符串,即device_type屬性值
//返回值:找到的節點,NULL表示查找失敗
of_find_compatible_node:根據device_type和compatible這兩個屬性查找指定的節點
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
//from:開始查找的節點,NULL表示從根節點開始查找整個設備樹
//type:要查找的節點對應的device_type屬性值,為NULL時表示忽略掉該屬性
//compatible:要查找的節點所對應的compatible屬性列表
//返回值:找到的節點,NULL表示查找失敗
of_find_matching_node_and_match:通過of_device_id匹配表來查找指定的節點
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
//from:開始查找的節點,NULL表示從根節點開始查找整個設備樹
//matches:of_device_id匹配表,也就是在此匹配表里面查找節點
//match:找到的匹配的of_device_id
//返回值:找到的節點,NULL表示查找失敗
of_find_node_by_path:通過路徑來查找指定的節點
inline struct device_node *of_find_node_by_path(const char *path)
//path:帶有全路徑的節點名
//返回值:找到的節點,NULL表示查找失敗
3.2 查找父/子節點的OF函數
of_get_parent:用于查找父節點
struct device_node *of_get_parent(const struct device_node *node)
//node:要查找的父節點的節點
//返回值:找到的父節點
of_get_next_child:用迭代的方式查找子節點
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
//node:父節點
//prev:前一個子節點,即從哪一個子節點開始迭代的查找下一個子節點,若為NULL,表示從第一個子節點開始
//返回值: 找到的下一個子節點
3.3 提取屬性值的OF函數
節點的屬性信息里面保存了驅動所需要的內容,因此對于屬性值的提取非常重要,內核中使用結構體property表示屬性,定義在include/linux/of.h中,內容如下:
struct property {
char *name; /* 屬性名字 */
int length; /* 屬性長度 */
void *value; /* 屬性值 */
struct property *next; /* 下一個屬性 */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
of_find_property:查找指定的屬性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
//np:設備節點
//name:屬性名字
//lenp:屬性值的字節數
//返回值:找到的屬性
of_property_count_elems_of_size:獲取屬性中元素的數量
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
//np:設備節點
//proname:需要統計元素數量的屬性名字
//elem_size:元素長度
//返回值:得到的屬性元素數量
of_property_read_u32_index:從屬性中獲取指定標號的u32類型數據值
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
//np:設備節點
//proname:要讀取的屬性名字
//index:要讀取的值標號
//out_value:讀取到的值
//返回值:0 讀取成功,負值,讀取失敗,-EINVAL 表示屬性不存在,
// -ENODATA 表示沒有要讀取的數據,-EOVERFLOW 表示屬性值列表大小
of_property_read_u8_array:讀取屬性中u8類型的數組數據(類似的還有u16、u32 和 u64)
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//np:設備節點
//proname:要讀取的屬性名字
//out_value:讀取到的數組值
//sz:要讀取的數組元素數量
//返回值:0 讀取成功,負值,讀取失敗,-EINVAL 表示屬性不存在,
// -ENODATA 表示沒有要讀取的數據,-EOVERFLOW 表示屬性值列表太
of_property_read_u8:讀取只有一個整形值的屬性(類似的還有u16、u32和u64)
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
//np:設備節點
//proname:要讀取的屬性名字
//out_value:讀取到的數組值
//返回值:0 讀取成功,負值,讀取失敗,-EINVAL 表示屬性不存在,
// -ENODATA 表示沒有要讀取的數據,-EOVERFLOW 表示屬性值列表太小
of_property_read_string:讀取屬性中字符串值
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
//np:設備節點
//proname:要讀取的屬性名字
//out_string:讀取到的字符串值
//返回值:0,讀取成功,負值,讀取失敗
of_n_addr_cells:獲取#address-cells 屬性值
int of_n_addr_cells(struct device_node *np)
//np:設備節點
//返回值:獲取到的#address-cells屬性值
of_n_size_cells:獲取#size-cells 屬性值
int of_n_size_cells(struct device_node *np)
//np:設備節點
//返回值:獲取到的#size-cells屬性值
3.4 其他常用OF函數
of_device_is_compatible:查看節點的compatible屬性是否有包含compat指定的字符串,也就是檢查設備節點的兼容性
int of_device_is_compatible(const struct device_node *device, const char *compat)
//device:設備節點
//compat:要查看的字符串
//返回值: 0,節點的 compatible 屬性中不包含 compat 指定的字符串;
// 正數,節點的 compatible 屬性中包含 compat 指定的字符串
of_get_address:獲取地址相關屬性
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
//dev:設備節點
//index:要讀取的地址標號
//size:地址長度
//flags:參數,比如IORESOURCE_IO、IORESOURCE_MEM等
//返回值:讀取到的地址數據首地址,表示讀取失敗
of_translate_address:將設備樹讀取到的地址轉換為物理地址
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
//dev:設備節點
//in_addr:要轉換的地址
//返回值:得到的物理地址,為OF_BAD_ADDR表示轉換失敗
of_address_to_resource:將reg屬性值,轉換為resource結構體類型
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
//dev:設備節點
//index:地址資源標號
//r:得到的 resource 類型的資源值
//返回值: 0,成功;負值,失敗
of_iomap:用于直接內存映射
void __iomem *of_iomap(struct device_node *np, int index)
//np:設備節點
//index:reg屬性中要完成內存映射的段,如果reg屬性只有一段的話index就設置為0
//返回值:經過內存映射后的虛擬內存首地址,NULL表示內存映射失敗
-
ARM
+關注
關注
134文章
9054瀏覽量
366834 -
寄存器
+關注
關注
31文章
5322瀏覽量
120022 -
源碼
+關注
關注
8文章
633瀏覽量
29143 -
Linu
+關注
關注
0文章
26瀏覽量
19809 -
設備樹
+關注
關注
0文章
38瀏覽量
3110
發布評論請先 登錄
相關推薦
評論