Linux在内核中注册了一个虚拟总线,(当然了,我们自己也可以去创建总线,一般没必要),这样我们就可以吧不属于任何总线的设备挂接在这条虚拟总线上。
struct device platform_bus = {
.bus_id = "platform",
};
Platform_device_系列函数,实际上是注册了一个叫platform的虚拟总线。使用约定是如果一个不属于任何总线的设备,例如蓝牙,串口等设备,都需要挂在虚拟总线上。简单地说,platform_driver_register要将向内核注册一个platform设备的驱动。
为了向内核添加一个platform设备,程序员应该填写两个数据结构:platform_device和platform_driver,这两个数据结构的定义都可以在include/linux/platform_device.h文件中找到。
Platform_device相关内容
首先要确认的就是设备的资源信息,例如设备的地址,中断号等。
在2.6内核中platform设备用结构体platform_device来描述,该结构体定义在kernel\include\linux\platform_device.h中,
struct platform_device {
const char * name; //定义平台设备的名称
u32 id; //表示设备编号,ID的值为-1表示只有一个这样的设备
struct device dev;
u32 num_resources;
struct resource * resource; //定义平台设备的资源
};
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,
struct resource {
const char *name; //定义资源的名称
unsigned long start, end; 定义资源的起始地址
unsigned long flags; //定义资源的类型,比如:MEM,IO,IRQ,DMA类型
struct resource *parent, *sibling, *child; . //资源链表指针
};
下面举s3c2410平台的i2c驱动作为例子来说明:
/* arch/arm/mach-s3c2410/devs.c */
/* I2C */
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC, //S3C2410_IRQ(27)
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
这里定义了两组resource,它描述了一个I2C设备的资源,第1组描述了这个I2C设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个I2C设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。
有了resource信息,就可以定义platform_device了:
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
定义好了platform_device结构体后就可以调用函数platform_add_devices向系统中添加该设备了,之后可以调用platform_device_register()进行设备注册。要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用,即执行platform_driver_register之前,原因是因为驱动注册时需要匹配内核中所以已注册的设备名。
Platform_driver相关内容
驱动程序需要实现结构体struct platform_driver,参考drivers/i2c/busses
/* device driver for platform bus bits */
static struct platform_driver s3c2410_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c",
},
};
在驱动初始化函数中调用函数platform_driver_register()注册platform_driver,需要注意的是s3c_device_i2c结构中name元素和s3c2410_i2c_driver结构中driver.name必须是相同的,这样在platform_driver_register()注册时会对所有已注册的所有platform_device中的name和当前注册的platform_driver的driver.name进行比较,只有找到相同的名称的platfomr_device才能注册成功,当注册成功时会调用platform_driver结构元素probe函数指针,这里就是s3c24xx_i2c_probe,当进入probe函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num);
获取资源中的中断号。
下面举s3c24xx_i2c_probe函数分析,看看这些接口是怎么用的。
前面已经讲了,s3c2410_i2c_driver注册成功后会调用s3c24xx_i2c_probe执行,下面看代码:
/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/
/* drivers/i2c/busses/i2c-s3c2410.c */
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
struct resource *res;
int ret;
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto out;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* 获取设备的IO资源地址 */
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto out;
}
i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1, pdev->name); /* 申请这块IO Region */
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto out;
}
i2c->regs = ioremap(res->start, (res->end-res->start)+1); /* 映射至内核虚拟空间 */
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto out;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto out;
/* find the IRQ for this unit (note, this relies on the init call to ensure no current IRQs pending */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); /* 获取设备IRQ中断号 */
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IRQ\n");
ret = -ENOENT;
goto out;
}
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED, /* 申请IRQ */
pdev->name, i2c);
……
return ret;
}
device中的资源再讲讲
struct resource 是linux对挂接在4G总线空间上的设备实体的管理方式。
一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,linux是怎么管理所有的这些外部"物理地址范围段",进而给用户和linux自身一个比较好的观察4G总线上挂接的一个个设备实体的简洁、统一级联视图的呢?
linux采用struct resource结构体来描述一个挂接在cpu总线上的设备实体(32位cpu的总线地址范围是0~4G):
resource->start 描述设备实体在cpu总线上的线性起始物理地址;
resource->end 描述设备实体在cpu总线上的线性结尾物理地址;
resource->name 描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切;
resource->flag 描述这个设备实体的一些共性和特性的标志位;
只需要了解一个设备实体的以上4项,linux就能够知晓这个挂接在cpu总线的上的设备实体的基本使用情况,也就是 [resource->start, resource->end]这段物理地址现在是空闲着呢,还是被什么设备占用着呢?
linux会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段[resource->start, resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体,进而避免设备之间出现对同一总线物理地址段的重复引用,而造成对唯一物理地址的设备实体二义性.
以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不是linux所想的,linux需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,这就需要链在一起,因此resource结构体提供了另外3个成员:指针parent、sibling和 child:分别为指向父亲、兄弟和子资源的指针,它们的设置是为了以一种树的形式来管理各种I/O资源,以root source为例,root->child(*pchild)指向root所有孩子中地址空间最小的一个;pchild->sibling是兄弟链表的开头,指向比自己地址空间大的兄弟。
属性flags是一个unsigned long类型的32位标志值,用以描述资源的属性。比如:资源的类型、是否只读、是否可缓存,以及是否已被占用等。下面是一部分常用属性标志位的定义
// include/linux/ioport.h:
29/*
30 * IO resources have these defined flags.
31 */
32#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
33
34#define IORESOURCE_IO 0x00000100 /* Resource type */
35#define IORESOURCE_MEM 0x00000200
36#define IORESOURCE_IRQ 0x00000400
37#define IORESOURCE_DMA 0x00000800
38
39#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */
40#define IORESOURCE_READONLY 0x00002000
41#define IORESOURCE_CACHEABLE 0x00004000
42#define IORESOURCE_RANGELENGTH 0x00008000
43#define IORESOURCE_SHADOWABLE 0x00010000
44#define IORESOURCE_BUS_HAS_VGA 0x00080000
45
46#define IORESOURCE_DISABLED 0x10000000
47#define IORESOURCE_UNSET 0x20000000
48#define IORESOURCE_AUTO 0x40000000
49#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
下面来看我们所使用的LCD所占用的资源,如下所示:
// arch/arm/mach-pxa/generic.c
static struct resource pxafb_resources[] = {
[0] = {
.start = 0x44000000,
.end = 0x4400ffff,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
},
};
由上可知LCD占用的资源包括两类,一类是MEM类型,一类是IRQ类型。MEME类型资源对应的物理地址范围是 0x44000000 - 0x4400ffff;IRQ类型资源对应的物理地址范围是IRQ_LCD,