1. 程式人生 > >Linux 裝置驅動開發 —— 裝置樹在platform裝置驅動中的使用

Linux 裝置驅動開發 —— 裝置樹在platform裝置驅動中的使用

         關與裝置樹的概念,我們在Exynos4412 核心移植(六)—— 裝置樹解析 裡面已經學習過,下面看一下裝置樹在裝置驅動開發中起到的作用

         Device Tree是一種描述硬體的資料結構,裝置樹源(Device Tree Source)檔案(以.dts結尾)就是用來描述目標板硬體資訊的。Device Tree由一系列被命名的結點(node)和屬性(property)組成,而結點本身可包含子結點。所謂屬性,其實就是成對出現的name和value。在Device Tree中,可描述的資訊包括(原先這些資訊大多被hard code到kernel中)。

一、裝置樹基礎概念

1、基本資料格式

      device tree是一個簡單的節點和屬性樹,屬性是鍵值對,節點可以包含屬性和子節點。下面是一個.dts格式的簡單裝置樹。

/ {
    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 {
        };
    };
};

      該樹並未描述任何東西,也不具備任何實際意義,但它卻揭示了節點和屬性的結構。即:

a -- 一個的根節點:'/',兩個子節點:node1和node2;node1的子節點:child-node1和child-node2,一些屬性分散在樹之間。

b -- 屬性是一些簡單的鍵值對(key-value pairs):value可以為空也可以包含任意的位元組流。而資料型別並沒有編碼成資料結構,有一些基本資料表示可以在device tree原始檔中表示。

c -- 文字字串(null 終止)用雙引號來表示:string-property = "a string"

d -- “Cells”是由尖括號分隔的32位無符號整數:cell-property = <0xbeef 123 0xabcd1234>

e -- 二進位制資料是用方括號分隔:binary-property = [0x01 0x23 0x45 0x67];

f -- 不同格式的資料可以用逗號連線在一起:mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;

g -- 逗號也可以用來建立字串列表:string-list = "red fish", "blue fish";

二、裝置在device tree 中的描述

系統中的每個裝置由device tree的一個節點來表示

1、節點命名

     花些時間談談命名習慣是值得的。每個節點都必須有一個<name>[@<unit-address>]格式的名稱。<name>是一個簡單的ascii字串,最長為31個字元,總的來說,節點命名是根據它代表什麼裝置。比如說,一個代表3com乙太網介面卡的節點應該命名為ethernet,而不是3com509。

    如果節點描述的裝置有地址的話,就應該加上unit-address,unit-address通常是用來訪問裝置的主地址,並在節點的reg屬性中被列出。後面我們將談到reg屬性。

2、裝置

      接下來將為裝置樹新增裝置節點:

/ {
	compatible = "acme,coyotes-revenge";

	cpus {
		[email protected] {
			compatible = "arm,cortex-a9";
		};
		[email protected] {
            		compatible = "arm,cortex-a9";
        	};
    	};

	[email protected] {
		compatible = "arm,pl011";
	};

	[email protected] {
		compatible = "arm,pl011";
	};

	[email protected] {
		compatible = "arm,pl061";
	};

	[email protected] {
		compatible = "arm,pl190";
	};

	[email protected] {
		compatible = "arm,pl022";
	};
	
	external-bus {
		[email protected],0 {
			compatible = "smc,smc91c111";
		};
	
		[email protected],0 {
			compatible = "acme,a1234-i2c-bus";
			[email protected] {
				compatible = "maxim,ds1338";
			};
        	};

		[email protected],0 {
			compatible = "samsung,k8f1315ebm", "cfi-flash";
       		 };
   	 };
};

        在上面的裝置樹中,系統中的裝置節點已經新增進來,樹的層次結構反映了裝置如何連到系統中。外部總線上的裝置就是外部匯流排節點的子節點,i2c裝置是i2c匯流排控制節點的子節點。總的來說,層次結構表現的是從CPU視角來看的系統檢視。在這裡這棵樹是依然是無效的。它缺少關於裝置之間的連線資訊。稍後將新增這些資料。

      裝置樹中應當注意:每個裝置節點有一個compatible屬性。flash節點的compatible屬性有兩個字串。請閱讀下一節以瞭解更多內容。 之前提到的,節點命名應當反映裝置的型別,而不是特定型號。請參考ePAPR規範2.2.2節的通用節點命名,應優先使用這些命名。

3、compatible 屬性

      樹中的每一個代表了一個裝置的節點都要有一個compatible屬性。compatible是OS用來決定繫結到裝置的裝置驅動的關鍵。

      compatible是字串的列表。列表中的第一個字串指定了"<manufacturer>,<model>"格式的節點代表的確切裝置,第二個字串代表了與該裝置相容的其他裝置。例如,Freescale MPC8349 SoC有一個串列埠裝置實現了National Semiconductor ns16550暫存器介面。因此MPC8349串列埠裝置的compatible屬性為:compatible = "fsl,mpc8349-uart", "ns16550"。在這裡,fsl,mpc8349-uart指定了確切的裝置,ns16550表明它與National Semiconductor 16550 UART是暫存器級相容的。

     注:由於歷史原因,ns16550沒有製造商字首,所有新的compatible值都應使用製造商的字首這種做法使得現有的裝置驅動程式可以繫結到一個新裝置上,同時仍能唯一準確的識別硬體

4、編址

      可編址的裝置使用下列屬性來將地址資訊編碼進裝置樹:

reg

#address-cells

#size-cells

       每個可定址的裝置有一個reg屬性,即以下面形式表示的元組列表:

      reg = <address1 length1 [address2 length2] [address3 length3] ... > 

     每個元組,。每個地址值由一個或多個32位整數列表組成,被稱做cells。同樣地,長度值可以是cells列表,也可以為空。

     既然address和length欄位是大小可變的變數,父節點的#address-cells和#size-cells屬性用來說明各個子節點有多少個cells。換句話說,正確解釋一個子節點的reg屬性需要父節點的#address-cells#size-cells值

5、記憶體對映裝置

      與CPU節點中的單一地址值不同,記憶體對映裝置會被分配一個它能響應的地址範圍。#size-cells用來說明每個子節點種reg元組的長度大小。

     在下面的示例中,每個地址值是1 cell (32位) ,並且每個的長度值也為1 cell,這在32位系統中是非常典型的。64位計算機可以在裝置樹中使用2作為#address-cells和#size-cells的值來實現64位定址。

[email protected] {
	compatible = "arm,pl011";
	reg = <0x101f2000 0x1000 >;
};

[email protected] {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
};


[email protected] {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
};

      每個裝置都被分配了一個基地址及該區域大小。本例中的GPIO裝置地址被分成兩個地址範圍:0x101f3000~0x101f3fff和0x101f4000~0x101f400f。

三、裝置樹在platform裝置驅動開發中的使用解析

1、裝置樹對platform中platform_device的替換

         其實我們可以看到,Device Tree 是用來描述裝置資訊的,每一個裝置在裝置樹中是以節點的形式表現出來;而在上面的 platform 裝置中,我們利用platform_device 來描述一個裝置,我們可以看一下二者的對比

fs4412-beep{          compatible = "fs4412,beep";          reg = <0x114000a0 0x4 0x139D0000 0x14>; }; a -- fs4412-beep 為節點名,符合咱們前面提到的節點命名規範;       我們通過名字可以知道,該節點描述的裝置是beep, 裝置名是fs4412-beep; b -- compatible = "fs4412,beep"; compatible 屬性, 即一個字串; 前面提到,所有新的compatible值都應使用製造商的字首,這裡是 fs4412; c -- reg = <0x114000a0 0x4 0x139D0000 0x14>;        reg屬性來將地址資訊編碼進裝置樹,表示該裝置的地址範圍;這裡是我們用到的暫存器及偏移量; static struct  resource beep_resource[] = {     [0] = {         .start = 0x114000a0,         .end = 0x114000a0+0x4,         .flags = IORESOURCE_MEM,     },     [1] = {         .start = 0x139D0000,         .end = 0x139D0000+0x14,         .flags = IORESOURCE_MEM,     }, }; static struct platform_device hello_device= {     .name = "bigbang",//沒用了     .id = -1,     .dev.release = hello_release,     .num_resources = ARRAY_SIZE(beep_resource ),     .resource = beep_resource, };

      可以看到裝置樹中的裝置節點完全可以替代掉platform_device。

2、有了裝置樹,如何實現device 與 driver 的匹配?

我們在上一篇還有 platform_device 中,是利用 .name 來實現device與driver的匹配的,但現在裝置樹替換掉了device,那我們將如何實現二者的匹配呢?有了裝置樹後,platform比較的名字存在哪?

     我們先看一下原來是如何匹配的 ,platform_bus_type 下有個match成員,platform_match 定義如下

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}
其中又呼叫了of_driver_match_device(dev, drv) ,其定義如下:
static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}
其呼叫of_match_device(drv->of_match_table, dev) ,繼續追蹤下去,注意這裡的引數drv->of_match_table
const struct of_device_id *of_match_device(const struct of_device_id *matches,
					   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
又呼叫 of_match_node(matches, dev->of_node)  ,其中matches 是struct of_device_id 型別
/**
 * of_match_node - Tell if an device_node has a matching of_match structure
 *	@matches:	array of of device match structures to search in
 *	@node:		the of device structure to match against
 *
 *	Low level utility function used by device matching.
 */
const struct of_device_id *of_match_node(const struct of_device_id *matches,
					 const struct device_node *node)
{
	const struct of_device_id *match;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	match = __of_match_node(matches, node);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return match;
}
EXPORT_SYMBOL(of_match_node);
找到 match = __of_match_node(matches, node); 注意著裡的node是struct device_node 型別的
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}
繼續追蹤下去
static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!device->type || of_node_cmp(type, device->type))
			return 0;
		score += 2;
	}

	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!device->name || of_node_cmp(name, device->name))
			return 0;
		score++;
	}

	return score;
}
看這句 prop = __of_find_property(device, "compatible", NULL);

可以發先追溯到底,是利用"compatible"來匹配的,即裝置樹載入之後,核心會自動把裝置樹節點轉換成 platform_device這種格式,同時把名字放到of_node這個地方
   

platform_driver 部分

    可以看到原來是利用platform_driver 下的 struct driver 結構體中的 name 成員來匹配的,看一下 struct driver 結構體的定義:
struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
}
      成員中有const struct of_device_id*of_match_table; 是struct of_device_id 型別,定義如下:
/*
 * Struct used for matching a device
 */
struct of_device_id
{
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};
      可以看到其作用就是為了匹配一個裝置。我們所要做的就是對 char compatible[128] 的填充;裝置樹載入之後,核心會自動把裝置樹節點轉換成 platform_device這種格式,同時把名字放到of_node這個地方。 3、基於裝置樹的driver的結構體的填充

      匹配的方式發生了改變,那我們的platform_driver 也要修改了

基於裝置樹的driver的結構體的填充:

static struct of_device_id beep_table[] = {
    {.compatible = "fs4412,beep"},
};
static struct platform_driver beep_driver=
{
    .probe = beep_probe,
    .remove = beep_remove,
    .driver={
        .name = "bigbang",
        .of_match_table = beep_table,
    },
};
原來的driver是這樣的,可以對比一下
static struct platform_driver beep_driver=
{
    .driver.name = "bigbang",
    .probe = beep_probe,
    .remove = beep_remove,
};

4、裝置樹編譯

      我們在 arch/arm/boot/dts/exynos4412-fs4412.dts 中新增

fs4412-beep{
         compatible = "fs4412,beep";
         reg = <0x114000a0 0x4 0x139D0000 0x14>;
};
      就可以編譯裝置樹了
make dtbs  在核心根目錄 vim arch/arm/boot/dts/exynos4412-fs4412.dts
sudo cp  arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/

     然後,將裝置樹下載到0x42000000處,並載入驅動 insmod driver.ko, 測試下驅動。