12.2.3 platform设备资源和数据
在linux/platform_device.h 中结构体platform_device的定义:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
其中结构体struct resource描述platform_device的资源,其定义如代码清单12.8所示。
代码清单12.8 resource结构体定义
linux/ioport.h
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
备注:通常关心start、end和flags这3个成员属性,分别标明了资源的开始值、结束值和类型。其中flags可以是IORESOURCE_IO(IO)、IORESOURCE_MEM(内存资源)、IORESOURCE_IRQ(中断号)、IORESOURCE_DMA(DMA)等。
start、end的含义会随着flags而变更,解释如下:
当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;
当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用1个中断号,开始和结束值相同。
对于同种类型的资源,可以有多份,例如某设备占据两个内存区域,则可以定义两个IORESOURCE_MEM资源。
对resource的定义通常在BSP的板级文件中进行,在具体的设备驱动中通过platform_get_resource()API来获取,此API的原型为:
<linux/platform_device.h>
extern struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
drivers/base/platform.c
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
int i;
for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i];
if (type == resource_type(r) && num-- == 0)
return r;
}
return NULL;
}
EXPORT_SYMBOL_GPL(platform_get_resource);
例如在arch/arm/mach-at91/board-sam9261ek.c板级文件中为DM9000网卡定义了如下resouce:
static struct resource dm9000_resource[] = {
[0] = {
.start = AT91_CHIPSELECT_2,
.end = AT91_CHIPSELECT_2 + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = AT91_CHIPSELECT_2 + 0x44,
.end = AT91_CHIPSELECT_2 + 0xFF,
.flags = IORESOURCE_MEM
},
[2] = {
.flags = IORESOURCE_IRQ
| IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE,
}
};
在DM9000网卡的驱动中则是通过如下方法获取这3份资源:
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
对于IRQ,platform_get_resource()还有一个进行了封装的变体platform_get_irq(),其原型:
<linux/platform_device.h>
extern int platform_get_irq(struct platform_device *, unsigned int);
drivers/base/platform.c
/**
* platform_get_irq - get an IRQ for a device
* @dev: platform device
* @num: IRQ number index
*/
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
return r ? r->start : -ENXIO;
}
EXPORT_SYMBOL_GPL(platform_get_irq);
分析:platform_get_irq()实际上调用了“platform_get_resource(dev,IORESOURCE_IRQ,num);”。
设备除了在BSP中定义资源外,还可附加一些数据信息,因为对设备的硬件描述除中断、内存等标准资源外,可能还会有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动上。因此,platform也提供了platform_data的支持,platform_data的形式是由每个驱动自定义的,如对于DM9000网卡,platform_data为一个dm9000_plat_data结构体,完成定义后,就可以将MAC地址、总线宽度、板上有无EEPROM信息等放入platform_data中,如代码清单12.9所示。
static struct dm9000_plat_data dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
};
static struct platform_device dm9000_device = {
.name = "dm9000",
.id = 0,
.num_resources = ARRAY_SIZE(dm9000_resource),
.resource = dm9000_resource,
.dev = {
.platform_data = &dm9000_platdata, //平台数据
}
};
在DM9000网卡的驱动drivers/net/dm9000.c的probe()中,通过如下方式获取platform_data:
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
其中,pdev为platform_device的指针。
<linux/device.h>
static inline void *dev_get_platdata(const struct device *dev)
{
return dev->platform_data;
}
总结:在设备驱动中引入platform的概念好处:
1)使得设备被挂接在一个总线上,符合Linux 2.6以后内核的设备模型。其结果是使配套的sysfs节点、设备电源管理都成为可能。
2)隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,在驱动中,只需通过通用API去获取资源和数据,做到了板级相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
3)让一个驱动支持多个设备实例。譬如DM9000的驱动只有一份,但是可以在板级添加多份DM9000的platform_device,它们都可以与唯一的驱动匹配。
在Linux 3.x之后的内核中,DM9000驱动实际上已经可以通过设备树的方法被枚举。
#include <linux/spinlock.h>
#include <linux/crc32.h>
#include <linux/mii.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
#include <linux/ethtool.h>
#include <linux/dm9000.h>
#include <linux/delay.h>
@@ -1351,6+1353,31@@ static const struct net_device_ops dm9000_netdev_ops = {
#endif
};
+static struct dm9000_plat_data *dm9000_parse_dt(struct device *dev)//解析设备树
+{
+ …
+}
+
/*
* Search DM9000board, allocate space and register it
*/
@@ -1366,6+1393,12@@ dm9000_probe(struct platform_device *pdev)
int i;
u32id_val;
+ if (!pdata) {
+ pdata = dm9000_parse_dt(&pdev->dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ }
+
/* Init network device */
ndev = alloc_etherdev(sizeof(struct board_info));
if (!ndev)
@@ -1676,11+1709,20@@ dm9000_drv_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_OF
+static const struct of_device_id dm9000_of_matches[] = {
+ { .compatible = "davicom,dm9000", },//兼容性属性
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dm9000_of_matches);
+#endif
+
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,//电源管理操作
+ .of_match_table = of_match_ptr(dm9000_of_matches),
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
改为设备树后,在板级上添加DM9000网卡的动作就变成了简单地修改dts文件,如
arch/arm/boot/dts/s3c6410-mini6410.dts中:
srom-cs1@18000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x18000000 0x8000000>;
ranges;
ethernet@18000000{
compatible = "davicom,dm9000";
reg = <0x18000000 0x2 0x18000004 0x2>;
interrupt-parent = <&gpn>;
interrupts = <7IRQ_TYPE_LEVEL_HIGH>;
davicom,no-eeprom;
};
};
备注:
关于设备树情况下驱动与设备的匹配,以及驱动如何获取平台属性,在后续章节介绍。