Linux設備樹相關操作

簡介

最近在學習設備樹以及設備基於設備樹的驅動開發,其實我是一直在糾結要不要寫這篇博客的,因爲自己瞭解的僅僅是設備樹的一些定義,一些操作,還沒有去看源碼,這樣寫出來的博客並不是太有深度的,但是還是想記錄一下自己的學習歷程,對自己這一週多的學習大概做一些輸出,所以還是寫了這篇博客,講解一下Linux設備樹的一些相關操作以及基於設備樹的驅動開發流程。
設備樹的引入主要是爲了減少重用率低的代碼,隨着Linux內核以及ARM的發展,arch/arm文件夾的規模是日益龐大,同時裏面的代碼大部分是芯片廠商對自己的芯片描述和板卡廠商對自己產品的板級描述,一大堆xx-mach文件,這樣的文件是難以複用的,它們各自實現了IO操作以及板級的控制,爲了改變這樣的局面引入了設備樹機制,設備樹機制最先是PowerPC引入了,設備樹用一個樹狀結構來描述了支持體系,一款芯片可以看做是一個樹幹,基於它所開發的板卡可以看做是樹枝,他們有共同的部分,也有板級的區別,同時也引入了pinctrl子系統改進了GPIO子系統,全部基於設備樹,這樣內核的代碼複用率大大提高,同時也大大降低了驅動的移植難度,很多的驅動僅僅只需要修改設備樹即可完成驅動的移植,所以設備樹的加入雖然略微提高了學習成本但是帶來的回報也是豐厚的。

相關文件組成和介紹

dts和bingings

文件分爲dts和bingings
bindings包含設備樹用到的所有宏定義,都放到bindings目錄下
dts分爲dts和dtsi文件,dts是板級文件,dtsi是“平臺文件”,另外還有使用文檔在Documentation/devicetree
.dts描述板級信息(有哪些IIC設備、SPI設備等)
.dtsi描述SOC級信息
DTS是設備樹源碼文件
DTB是將DTS編譯後得到的二進制文件
將.dts編譯爲.dtb需要DTC文件 工具源碼在scripts/dtc目錄下
在源碼文件夾中執行make dtbs就可以進行設備樹的編譯
4412開發板的設備樹文件:arch/arm/boot/dts/exynos4412-itop-elite.dts
這裏的平臺文件是指支持的不止一塊板子而是一類板子

設備樹文件之間的關係

dts文件包含的頭文件

#include <dt-bindings/sound/samsung-i2s.h>
#include <dt-bindings/pwm/pwm.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>

#include "exynos4412-itop-scp-core.dtsi"

dtsi文件

----dt-bindings/clock/exynos4.h
----dt-bindings/clock/exynos-audss-clk.h
----dt-bindings/interrupt-controller/arm-gic.h
----dt-bindings/interrupt-controller/irq.h
----exynos-syscon-restart.dtsi
--exynos4412-pinctrl.dtsi
----dt-bindings/pinctrl/samsung.h
--exynos4-cpu-thermal.dtsi
----include <dt-bindings/thermal/thermal.h>

官方文檔

設備樹的文檔資料十分詳盡,基本上看着文檔就可以進行配置,設備樹文檔對每一個需要配置的地方都有詳細的解釋以及示例
比如下面兩個文檔
Documentation/devicetree/usage-model.txt
Documentation/devicetree/bindings/gpio/gpio-samsung.txt

設備樹dts的基本構造

節點和根節點

{}框起來的,稱爲節點
/{}在dts的最開頭,稱爲根節點

節點的標準結構是xxx@yyy{ … }
xxx是節點的名字,yyy則不是必須的,其值爲節點的地址(寄存器地址或其他地址)
label:node-name@unit-address
引入label的目的是爲了方便訪問節點,可以直接通過&label來訪問這個節點
支持幾種數據形式:

  • 字符串
  • 32位無符號整數
  • 字符串列表

節點可以包含屬性和子節點

屬性

設備樹學習的主要部分:設備樹文件中的屬性的配置,驅動文件中調用設備樹中的屬性

  • compatible
    類似設備名稱,兼容性屬性,字符串列表,用於將設備和驅動綁定起來
    格式:“manufacturer,model”
    其中manufacturer表示廠商,model一般是模塊對應的驅動名字
    一般的驅動程序文件都會有一個OF匹配表,此OF匹配表保存着一些compatible值,如果設備節點的compatible屬性值和OF匹配表中的任何一個值相等,那麼就表示設備可以使用這個驅動
  • model
    字符串
    描述設備模塊信息,比如名字
  • status
    字符串,設備的狀態
    okey:可操作
    disable:當前不可操作,但是在未來可以變爲可操作,如熱插拔設備插入後
    fail:不可操作,檢測到了一系列錯誤,也不大可能變得可操作
    fail-sss:同fail,後面的sss部分是檢測到的錯誤內容
  • #address-cells和#size-cells
    都是無符號32位整型,可以用在任何擁有子節點的設備中
    用於描述子節點的地址信息
    #address-cells決定子節點reg屬性地址信息所佔用的字長(32位)
    #size-cells決定了子節點reg屬性中長度信息所佔用的字長(32位)
    一般這兩個都是1
    這兩個屬性表明了子節點應該如何編寫reg屬性值。一般reg屬性都是和地址相關的內容
    reg=<address1 length1 address2 length2 address3 length3……>
  • reg
    用於描述設備地址空間資源信息
    一般都是某個外設的寄存器地址範圍信息
  • ranges
    可以爲空或者按照(child-bus-address,parent-bus-address,length)格式編寫的數字矩陣
    是一個地址映射/轉移表,每個項目由字地址、父地址和地址空間長度三部分組成
    child-bus-address:子總線地址空間的物理地址,由父節點的#address-cells確定所佔用字長
    parent-bus-address:父總線空間的物理地址,同樣由父節點的#address-cells確定所佔用字長
    length:子地址空間的長度,由父節點的#size-cells確定所佔用字長
  • name
    字符串,記錄節點名字,已被啓用不推薦
  • device_type
    字符串,IEEE 1275會用到此屬性,用於描述設備的FCode,設備樹沒有FCode,被棄用
    只能用於cpu節點或者memory節點
  • label—標籤
  • gpios—IO
  • pwms—PWM

在設備樹上添加節點

在.dts文件中添加

設備樹在系統中的體現

/proc/device-tree/ 目錄下是根據節點名字創建的不同文件夾
就是將設備樹分級存儲
每個文件夾就是一個節點,裏面包含這個節點的屬性以及它所包含的子節點

特殊子節點

  • aliases子節點
    別名 &label
  • chosen子節點
    不是一個真正的設備,主要是爲了uboot向linux內核傳遞數據
    重點是bootargs
    stdout-path 設置debug串口
    uboot會將bootargs填入chosen節點
    在這裏插入圖片描述

內核如何識別設備樹

匹配方式

使用設備樹之前:
uboot傳遞machine id,MACHINE_START、MACHINE_END
使用設備樹之後:
machine_desc
DT_MACHINE_START、DT_MACHINE_END
machine_desc結構體的.dt_compat成員變量保存着本設備兼容屬性
Linux內核調用start_kernel函數來啓動內核
會調用setup_arch函數來匹配machine_desc
setup_arch中調用setup_machine_fdt來獲取machine_desc,其參數是uboot傳遞給linux內核的dtb文件首地址,返回值是最匹配的machine_desc
在這裏插入圖片描述

內核解析DTB文件

Linux內核會在啓動的時候解析DTB文件,然後在/proc/device-tree目錄下生成相應的設備樹節點文件
在這裏插入圖片描述

設備樹常用OF操作函數

of是open firmware的縮寫,是定義計算機固件系統接口的標準,以前由電氣和電子工程師協會認可。它起源於Sun,已被Sun,Apple,IBM,ARM和大多數其他非x86 PCI芯片組供應商使用。
ARM的設備樹操作就遵守open firmware標準
OF操作函數是編寫驅動的時獲取設備樹信息調用的函數
定義在include/linux/of.h文件中

查找節點的OF函數

linux內核使用device_node結構體來描述一個節點

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 properties */
	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
}
  • 1、of_find_node_by_name
    通過子節點名字查找子節點
  • 2、of_find_node_by_type
    通過子節點類型查找子節點,device_type
  • 3、of_find_compatible_node
    根據device_type和compatible查找子節點,device_type可以設置爲NULL
  • 4、of_find_matching_node_and_match
    通過of_device_id匹配表來查找指定的節點
  • 5、of_find_node_by_path
    通過路徑來查找指定節點

查找父/子節點的OF函數

  • 1、of_get_parent
    父節點
  • 2、of_get_next_child
    迭代的查找子節點

提取屬性值

節點的屬性信息裏面保存了驅動所需要的內容,因此對於屬性值的提取非常重要, Linux內核中使用結構體 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; 
			};
  • 1 、of_find_property
    用於查找指定的屬性,函數原型如下:
struct property *of_find_property(const struct device_node *np,
					 const char *name,
					 int *lenp);
  • 2、of_property_count_elems_of_size
    函數用於獲取屬性中元素的數量,比如 reg屬性值是一個數組,那麼使用此函數可以獲取到這個數組的大小,此函數原型如下:
int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size);
  • 3、of_property_read_u32_index
    函數用於從屬性中獲取指定標號的 u32類型數據值 (無符號 32位 ),比如某個屬性有多個 u32類型的值,那麼就可以使用此函數來獲取指定標號的數據值,原型如下:
int of_property_read_u32_index(const struct device_node *np,
				       const char *propname,
				       u32 index, u32 *out_value);
  • 4、讀取數組數據的函數
    of_property_read_u8_array
    of_property_read_u16_array
    of_property_read_u32_array
    of_property_read_u64_array
    這 4個函數分別是讀取屬性中 u8、 u16、 u32和 u64類型的數組數據,比如大多數的 reg屬性都是數組數據,可以使用這 4個函數一次讀取出 reg屬性中的所有數據。
  • 5 、讀取整形值屬性的函數
    of_property_read_u8
    of_property_read_u16
    of_property_read_u32
    of_property_read_u64
  • 6、of_property_read_string
    用於讀取屬性中字符串值,函數原型如下:
int of_property_read_string(const struct device_node *np,
				   const char *propname,
				   const char **out_string);
  • 7、of_n_addr_cells
    函數用於獲取 #address-cells屬性值,函數原型如下:
int of_n_addr_cells(struct device_node *np);
  • 8、 of_n_size_cells
    函數用於獲取 #size-cells屬性值,函數原型如下:
int of_n_size_cells(struct device_node *np) 函數

其他常用的OF函數

  • 1、of_device_is_compatible
    函數用於查看節點的 compatible屬性是否有包含 compat指定的字符串,也就是檢查設備節點的兼容性,函數原型如下:
int of_device_is_compatible(const struct device_node *device,
				   const char *);
  • 2、of_get_address
    函數用於獲取地址相關屬性,主要是“ reg”或者 assigned-addresses”屬性值,函數屬性如下:
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);
  • 3、of_translate_address
    函數負責將從設備樹讀取到的地址轉換爲物理地址,函數原型如下:
u64 of_translate_address(struct device_node *dev,const __be32 *in_addr)
  • 4、of_address_to_resource
    IIC、 SPI、 GPIO等這些外設都有對應的寄存器,這些寄存器其實就是一組內存空間, Linux內核使用 resource結構體來描述一段內存空間,“ resource”翻譯出來就是“資源”,因此用 resource結構體描述的都是設備資源信息, resource結構體定義在文件 include/linux/ioport.h中,定義如下:
struct resource {resource_size_t start; 
				resource_size_t end; 
				const char *name; 
				unsigned long flags; 
				struct resource *parent, *sibling, *child; 
				};

對於 32位的 SOC來說, resource_size_t是 u32類型的。其中 start表示開始地址, end表示結束地址, name是這個資源的名字, flags是資源標誌位,一般表示資源類型,可選的資源標誌定義在文件 include/linux/ioport.h中,如下所示:

#define IORESOURCE_BITS		0x000000ff	/* Bus-specific bits */

#define IORESOURCE_TYPE_BITS	0x00001f00	/* Resource type */
#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000

#define IORESOURCE_PREFETCH	0x00002000	/* No side effects */
#define IORESOURCE_READONLY	0x00004000
#define IORESOURCE_CACHEABLE	0x00008000
#define IORESOURCE_RANGELENGTH	0x00010000
#define IORESOURCE_SHADOWABLE	0x00020000

#define IORESOURCE_SIZEALIGN	0x00040000	/* size indicates alignment */
#define IORESOURCE_STARTALIGN	0x00080000	/* start field is alignment */

#define IORESOURCE_MEM_64	0x00100000
#define IORESOURCE_WINDOW	0x00200000	/* forwarded by bridge */
#define IORESOURCE_MUXED	0x00400000	/* Resource is software muxed */

#define IORESOURCE_EXT_TYPE_BITS 0x01000000	/* Resource extended types */
#define IORESOURCE_SYSRAM	0x01000000	/* System RAM (modifier) */

#define IORESOURCE_EXCLUSIVE	0x08000000	/* Userland may not map this resource */

#define IORESOURCE_DISABLED	0x10000000
#define IORESOURCE_UNSET	0x20000000	/* No address assigned yet */
#define IORESOURCE_AUTO		0x40000000
#define IORESOURCE_BUSY		0x80000000	/* Driver has marked this resource busy */

一般最常見的資源標誌是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等
of_address_to_resource函數是從設備樹中提取資源值,本質上就是取reg屬性值然後將其轉換爲resource結構體類型,原型如下:

int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
  • 5、of_iomap
    函數用於直接內存映射,以前我們會通過 ioremap函數來完成物理地址到虛擬地址的映射,採用設備樹以後就可以直接通過 of_iomap函數來獲取內存地址所對應的虛擬地址,不需要使用 ioremap函數了。
    當然ioremap函數也是可以使用的,只是在採用了設備樹後,大部分的驅動都使用了of_iomap函數
    函數原型如下:
    void __iomem *of_iomap(struct device_node *np, int index)

總結

這個博客講解的都是設備樹很表面的東西,它的由來、語法以及驅動中如何進行調用,後面如果自己瞭解深入了也會對設備樹關鍵部分的代碼進行分析。
我們可以總結設備樹的一些套路
三個主要目標:
1、platform identification,平臺描述
2、runtime configuration,運行環境配置
3、device population.描述所有設備

  • platform identification
    compatible = “ti,omap3-beagleboard”, “ti,omap3450”, “ti,omap3”;
    compatible = “ti,omap3-beagleboard-xm”, “ti,omap3450”, “ti,omap3”;
    可以知道這是TI omap3平臺,omap3450芯片,omap3-beagleboard開發板
    compatible = “topeet,itop4412-elite”, “samsung,exynos4412”, “samsung,exynos4”;
  • runtime configuration
    大多通過chosen節點
    例如:
    chosen { bootargs = “console=ttyS0,115200 loglevel=8”;
    initrd-start = <0xc8000000>;
    initrd-end = <0xc8200000>; };
    4412例子
    chosen {
    bootargs = “root=/dev/mmcblk0p2 rw rootfstype=ext4 rootdelay=1 rootwait”;
    stdout-path = “serial2:115200n8”;
    };
  • device population
    這個就很常見了,設備樹其他的就是做這個的

理解它的主要目標以及使用方法我們先熟練的使用然後再去研究它的核心。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章