天天看點

linux驅動之裝置樹

文章目錄

    • linux驅動之裝置樹
      • 裝置樹的由來---什麼是裝置樹
      • Linux裝置樹的由來---為什麼會有裝置樹
      • 快速編譯裝置樹---DTC(device tree compiler)
      • 快速了解裝置樹---編譯裝置樹檔案
        • dtb使用的過程
      • 裝置樹文法及内部構成
        • 裝置樹文法
      • 裝置樹實戰

linux驅動之裝置樹

裝置樹的由來—什麼是裝置樹

  • Open Firmware Device Tree 開發固件裝置樹

    (1)Device Tree可以描述的資訊包括 CPU 的數量和類别、記憶體基位址和大小、總線和橋、外設連接配接、中斷控制器和中斷的使用情況、GPIO 控制器和GPIO 的使用情況、Clock 控制器和 Clock 使用情況。

    (2)裝置樹資訊被儲存再一個 ASCII文本檔案中,适合人類的閱讀習慣、類似于 xml 檔案,在 ARM Linux 中,一個 .dts 檔案對應一個ARM的 machine ,放置在核心的 arch/arm/boot/dts/ 目錄(2.6版本之前沒有)。

    (3)裝置樹是一種資料結構,用于描述裝置資訊的語言,具體而言,是用于作業系統中描述硬體,使得不需要對裝置的資訊進行寫死(hard code)

    (4)Device Tree 由一系列被命名的節點(node)和屬性(property)組成,而節點本身可包含子結點。所謂屬性,其實就是成對出現的 name 和 value。

    (5)裝置樹源檔案 dts 被編譯成 dtb 二進制檔案,在 bootloader 運作時傳遞給作業系統,作業系統對其進行解析展開(Flattened),進而産生一個硬體裝置的拓撲圖,有了這個拓撲圖,在程式設計的過程中可以直接通過系統提供的接口擷取到裝置樹中的節點和屬性資訊。

裝置樹其實是一個檔案,這個檔案包含很多的節點,這些節點是專用來描述裝置的資訊,包括CPU的資訊,GPIO的資訊等。資訊裡面包括很多的屬性。屬性中包括各種值 value,這些 value 是傳遞給核心使用的。核心可以解析出這些檔案資訊,然後給程式員使用。

Linux裝置樹的由來—為什麼會有裝置樹

  • Linux 2.6 中,arch/arm/plat-xxx 和 arch/arm/mach-xxx 中充斥着大量的垃圾代碼,相當多數的代碼隻是在描述闆級細節,而這些闆級細節對于核心來講,不過隻是垃圾,如闆上的 platform 裝置、resource、i2c_board_info、spi_board_info 以及各種硬體 platform_data.常見的 s3c2410、s3c6410 等闆級目錄、代碼量在數萬行。是以會使核心代碼非常龐大,管理麻煩,這就叫寫死方式。

    2.6核心描述裝置配置 類似 arch/arm/plat-samsung/dev-i2c0.c

    linux驅動之裝置樹
    arch/arm/mach-s5vp210/mach-smdkv210.c
    linux驅動之裝置樹
  • Linus Torvalds 對于此種情況大發雷霆,在2011年 ARM Linux郵件清單宣稱 this whole ARM thing is a fucking pain in the ass
  • 是以 Linux開發社群開始整改,裝置樹最早用于PowerPC等其他體系架構,ARM架構開發社群就開始采用裝置樹來描述裝置的資訊。

快速編譯裝置樹—DTC(device tree compiler)

  • 将 .dts 編譯為 .dtb 的工具
  • DTC 的源代碼位于核心的 scripts/dtc 目錄,在Linux 核心使能了 Device Tree 的情況下,編譯核心的時候主機工具 dtc 會被編譯出來。
  • 在 Linux 核心 arch/arm/boot/dts/Makefile 中,描述了當某種 soc 被選中後,哪些 .dtb 檔案會被編譯出來,如與 EXYNOS 對應的 .dtb 包括:
dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ 
 				exynos4210-sndkv310.dtb \
 				exynos4412-origen.dtb \
           
  • 我們可以單獨編譯 Device Tree 檔案。當我們在Linux 核心下運作 make dtbs 時,若我們之前選擇了 ARCH_EXYNOS ,上述 .dtb 都會由對應的 .dts編譯出來

快速了解裝置樹—編譯裝置樹檔案

  • 參考闆 origen 的裝置樹檔案做參考
cp arch/arm/boot/dts/exynos4410-origen.dts  arch/arm/boot/dts/exynos4412-fs4412.dts
           
  • 添加新檔案需修改 Makefile 才能編譯
vim arch/arm/boot/dts/Makefile ,在 exynos4410-origen.dtb \ 下添加如下内容
exynos4412-fs4412.dtb
           
  • 編譯裝置樹檔案
make dtbs
           
  • 拷貝核心和裝置樹檔案到 /tftpboot 目錄下
  • 設定啟動參數
set bootcmd tftp 0x41000000 uImage \; tftp 0x42000000 exynos4410-fs4412.dtb \; bootm 0x41000000 - 0x42000000
           

dtb使用的過程

linux驅動之裝置樹

裝置樹文法及内部構成

  • 部分名詞
DT: Device Tree
FDT: Flattened Device Tree
OF: Open Firmware
DTS: device tree source
DTSI: device tree source include
DTB: device tree blob
DTC: device tree compiler
           

裝置樹文法

(1)節點

(2)屬性

(3)根節點

(4)compatible 屬性

(5)reg 屬性

(6)#address-cells 和 #address-siz 屬性

(7)中斷資訊屬性—interrupts 和 interrupts

  • 簡單的裝置樹内容
/ {
	node1{
		a-string-property = "A string";
		a-string-list-property = "first string","second string";
		a-byte-data-property = [0x01 0x23 0x34 0x56];
		child-node1{
			first-child-property;
			second-child-property = <1>;
			a-string-property = "Hello,world";
			};
			child-node2{
				
			};		
	};
	
	node2{
		an-empty-property;
		a-cell-property = <1 2 3 4>;  /*each number (cell) is a uint32*/
		child-node1{
		};
	};
};
           
  • 節點(node)

    節點名稱:每個節點必須有一個 “<名稱>[@<裝置位址>]” 形式的名字

    <名稱> 就是一個不超過31位的簡單 ascii 字元串,節點的命名應該根據它所展現的是什麼樣的裝置。比如一個 3 com 以太網擴充卡的節點應該命名為 ethernet,而不應該是 3com509

    <裝置位址>用來通路該裝置的主位址,并且該位址也在節點的reg屬性中列出,同級節點命名必須是唯一的,但隻要位址不同,多個節點也可以使用一樣的通用名稱,當然裝置位址也是可選的,可以有也可以沒有。

    樹中每個表示一個裝置的節點都需要一個 compatible 屬性

  • 屬性 property
    linux驅動之裝置樹
  • 常見的屬性 — compatible 屬性
    linux驅動之裝置樹
  • 常見屬性—#address-cells 和 #size-cells
    linux驅動之裝置樹
  • 常見屬性 —reg屬性
    linux驅動之裝置樹
  • 常見屬性—中斷資訊
    linux驅動之裝置樹
    舉例:類似
/ {
	compatible = "acme,coyotes-revenge";
	#address-cells = <1>;
	#size-cells = <1>;
	interrupt-parent = <&intc>;
	
	[email protected] {
		compatible = "arm,pl011";
		reg = <0x10170000,0x1000>;
		interrupts = <1 0>;
	};

	intc:[email protected]{
		compatible = "arm,pl190";
		reg = <0x10140000,0x1000>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
}
           

對于arm架構,标志為具體含義 Documentation/devicetree/bindings/arm/gic.txt

linux驅動之裝置樹

裝置樹實戰

  • 在 對應的闆子上添加如下資訊:(隻是測試用)
[email protected] {
            compatible = "farsight,test";
             reg = <0x114001E0 0x24
                    0x11400c20 0x24>;
            testprop,mytest;
            test_list_string = "read fish","blue fish";
            interrupt-parent = <&gpx1>;   //因為按鍵接到了 gpx1_1
            interrupts = <2 2>;
            //因為按鍵接到了gpx1_1,如果接到了gpx2_1,那麼就是 <2 2>

    };  
           

之後 make dtbs

下載下傳到開發闆後 在 /proc/device_tree/device_tree 可以看到很多節點,可以看到有[email protected] 節點的檔案夾

  • 常用的 OF API

    OF 提供的函數主要集中在 /drivers/of/ 目錄下

    linux驅動之裝置樹
  • 解析裝置樹過程程式設計
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>


#define U32_DATA_LEN 4

static int is_good;
static int irqno;

irqreturn_t key_irq_handler(int irqno,void *devid)
{
	printk("key pressed\n");
	return IRQ_HANDLED;
}

static int __init dt_drv_init(void)
{
	/*
		 [email protected] {
            compatible = "farsight,test";
            reg = <0x12345678 0x24
                    0x87654321 0x24>;
            testprop,mytest;
            test_list_string = "read fish","blue fish";
    };  
	*/
	//在代碼中擷取節點的所有資訊
	//先把節點擷取到
	struct device_node *np = NULL;
	struct property *prop = NULL;
	
	np = of_find_node_by_path("/[email protected]");
	if(np)
	{
		printk("find test node ok\n");
		printk("find name = %s\n",np->name);
		printk("find full_name = %s\n",np->full_name);
	}
	else
	{
		printk("find test node failed\n");
	}


	//擷取到節點中的屬性
	prop = of_find_property(np,"compatible",NULL);
	if(prop)
	{
		printk("find compatible ok\n");
		printk("compatible value = %s\n",prop->value);
		printk("compatible name = %s\n",prop->name);
	}
	else
	{
		printk("find compatible failed\n");
	}

	if(of_device_is_compatible(np,"farsight,test"))
	{
		printk("we have a compatible name called farsight,test\n");
	}
	else
	{
		printk("This compatible is none\n");
	}
	

	//擷取到屬性中整數的數組
	u32 regdata[U32_DATA_LEN];
	int ret;
	
	ret = of_property_read_u32_array(np,"reg",regdata,U32_DATA_LEN);
	if(!ret)
	{
		printk("get reg data succeed\n");
		int i;
		for(i = 0; i < U32_DATA_LEN; i++)
		{
			printk("regdata[%d] = 0x%x\n",i,regdata[i]);
		}
	}
	else
	{
		printk("get reg data failed\n");
	}


	//讀取屬性中的字元串數組
	const char *pstr[3];
	int i;
	for(i = 0; i < 3; i++)
	{
		ret = of_property_read_string_index(np,"test_list_string",i,&pstr[i]);
		
		if(!ret)
		{
			printk("pstr[%d] = %s\n",i,pstr[i]);
		}
		else
		{
			printk("get pstr data failed\n");
		}
	}


	//屬性值為空,可以用于設定标志
	if(of_find_property(np," testprop,mytest",NULL))
	{
		is_good = 1;
		printk("is good = %d\n",is_good);
	}

	//擷取到中斷的号碼
	irqno = irq_of_parse_and_map(np,0);
	printk("irqno = %d\n",irqno);
	
	//驗證中斷号碼是否有效
	ret = request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"key_irq",NULL);
	if(ret)
	{
		printk("request_irq error\n");
		return -EBUSY;
	}

	
	
	return 0;
}

static void __exit dt_drv_exit(void)
{
	free_irq(irqno,NULL);
}

module_init(dt_drv_init);
module_exit(dt_drv_exit);
MODULE_LICENSE("GPL");

           

繼續閱讀