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、节点命名

     花些时间谈谈命名习惯是值得的。每个节点都必须有一个[@]格式的名称。是一个简单的ascii字符串,最长为31个字符,总的来说,节点命名是根据它代表什么设备。比如说,一个代表3com以太网适配器的节点应该命名为ethernet,而不是3com509。

    如果节点描述的设备有地址的话,就应该加上unit-address,unit-address通常是用来访问设备的主地址,并在节点的reg属性中被列出。后面我们将谈到reg属性。


2、设备

      接下来将为设备树添加设备节点:

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

	cpus {
		cpu@0 {
			compatible = "arm,cortex-a9";
		};
		cpu@1 {
            		compatible = "arm,cortex-a9";
        	};
    	};

	serial@101F0000 {
		compatible = "arm,pl011";
	};

	serial@101F2000 {
		compatible = "arm,pl011";
	};

	gpio@101F3000 {
		compatible = "arm,pl061";
	};

	interrupt-controller@10140000 {
		compatible = "arm,pl190";
	};

	spi@10115000 {
		compatible = "arm,pl022";
	};
	
	external-bus {
		ethernet@0,0 {
			compatible = "smc,smc91c111";
		};
	
		i2c@1,0 {
			compatible = "acme,a1234-i2c-bus";
			rtc@58 {
				compatible = "maxim,ds1338";
			};
        	};

		flash@2,0 {
			compatible = "samsung,k8f1315ebm", "cfi-flash";
       		 };
   	 };
};

        在上面的设备树中,系统中的设备节点已经添加进来,树的层次结构反映了设备如何连到系统中。外部总线上的设备就是外部总线节点的子节点,i2c设备是i2c总线控制节点的子节点。总的来说,层次结构表现的是从CPU视角来看的系统视图。在这里这棵树是依然是无效的。它缺少关于设备之间的连接信息。稍后将添加这些数据。

      设备树中应当注意:每个设备节点有一个compatible属性。flash节点的compatible属性有两个字符串。请阅读下一节以了解更多内容。 之前提到的,节点命名应当反映设备的类型,而不是特定型号。请参考ePAPR规范2.2.2节的通用节点命名,应优先使用这些命名。


3、compatible 属性

      树中的每一个代表了一个设备的节点都要有一个compatible属性。compatible是OS用来决定绑定到设备的设备驱动的关键。

      compatible是字符串的列表。列表中的第一个字符串指定了","格式的节点代表的确切设备,第二个字符串代表了与该设备兼容的其他设备。例如,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 =  

     每个元组,。每个地址值由一个或多个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位寻址。

serial@101f2000 {
	compatible = "arm,pl011";
	reg = <0x101f2000 0x1000 >;
};

gpio@101f3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
};


interrupt-controller@10140000 {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
};

      每个设备都被分配了一个基地址及该区域大小。本例中的GPIO设备地址被分成两个地址范围:0x101f3000~0x101f3fff和0x101f4000~0x101f400f。


三、设备树在platform设备驱动开发中的使用解析

         我们仍以 Linux 设备驱动开发 —— platform设备驱动应用实例解析 文中的例子来解析设备树在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, 测试下驱动。




你可能感兴趣的:(Linux,驱动开发进阶,Linux,字符设备驱动开发,Linux,系统,Linux,驱动开发,Linux,驱动开发,platform设备,设备树)