platform平台总线驱动的编写主要分为两个部分:
一个是platform_device部分,主要是提供设备本身对驱动处理所要求的参数。
另一个是platform_driver部分,主要是利用platform_device这边传递过来的参数提供对硬件的初始化,以及构建sys文件系统接口,方便应用程序操作驱动。
首先我们看platform_device部分要提供的数据封装和传递。
platform驱动模型抽象出了一个通用的结构体struct platform_device。
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
name:则是这个设备的名称,将来的驱动会和设备通过名字做匹配。
id:因为可能会有同一类型的多个设备需要注册,但他们的驱动可以共用,可以理解为,只靠名字并不能完全区分到底是哪一个,所以有了id,默认id是从0开始增长的。填成-1表示需要系统自动分配。
dev:这是所有设备所都具有的一个结构体,用来存放设备相关的信息。所有驱动模型都会有这个设备结构体。
resource :主要是资源,包括寄存器,中断,DMA,内存等都属于资源。该设备用到哪些,都要在platform_device里面填充好。
platform_device_id :主要是放置两个数据,一个name字符串,一个ulong类型的driver_data数据,这两个三星平台在name做设备名称区分,和处理器名称区分。driver_data主要是通过一个枚举来区分。主要是为了驱动的通用性。
pdev_archdata :从注释上也可以看到,这个是为特殊的硬件架构增加的,目前用不到。
我们先来看一下struct resource结构体
/*
* 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; //指针用来构成链表
};
struct device 结构体
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
#ifdef CONFIG_OF
struct device_node *of_node;
#endif
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
device结构体是为所有的设备设计的通用结构体,通常我们根据自己需要填充和使用,不一定需要全部填充初始化。
比如我们使用了dma,那么初始化的时候,就需要填写dma相关的参数。
作为通用性的,我们更多的使用是void *platform_data; 这个指针。
因为它是一个通用接口,所以我们根据我们具体的设备,定义一个自己的和具体设备用到数据相关的结构体,填充好后,绑定到platform_data,待将来驱动初始化等使用。
platform_device这边主要是对struct platform_device这个结构体的填充,通常不需要写复杂的代码。
带填充完后,调用platform_add_devices函数使其加入设备这边即可。
接下来则是platform_driver部分的编写:
首先是编写整体的入口函数和LICENSE
static int __init xxxx_register(void)
{
platform_driver_register(&xxxx_driver);
return 0;
}
static void __exit xxxx_unregister(void)
{
platform_driver_unregister(&xxxx_driver);
}
module_init(xxxx_register);
module_exit(xxxx_unregister);
MODULE_LICENSE("GPL");
上面这个是每个platform_driver驱动编写的固定格式。
接下来要编写platform_driver,首先我们看一下这个结构体
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
通常必须要写的有 probe,remove,以及driver。
其它的则根据驱动 需要编写。
基本实现方式如下。
而xxxx_driver则是我们要实现的重点。
static struct platform_driver xxxx_driver = {
.probe = xxxx_probe,
.remove = __devexit_p(xxxx_remove),
.driver = {
.name = "xxxx",
.owner = THIS_MODULE,
},
};
其中probe通常是作为初始化函数,同时也要在它里面使用platform_device传过来的参数。主要是void *platform_data参数。
因为这个参数是我们根据具体设备定义的。所以在初始化时我们就可以很方便的使用了。
同时,驱动里面还有其特有的一些参数,驱动 程序可以自定义一个结构体对他们进行封装(比如需要用到的虚拟文件系统的接口函数集合struct file_operations 或struct fb_ops等,申请的内存的虚拟地址,寄存器通过动态映射的地址、范围,以及为各种设备alloc的设备信息)。
以上这些都需要进行统一管理,这个时候最好的方式就是通过自定义一个包含上面所有说到的信息的结构体,用来封装这些信息。
而把这个结构体如何放在那里呢?
当初设计device结构体架构的大神们早就想好了。
struct device的第二个变量struct device_private *p;就是用来设计放置驱动私有数据的
/**
* struct device_private - structure to hold the private to the driver core portions of the device structure.
*
* @klist_children - klist containing all children of this device
* @knode_parent - node in sibling list
* @knode_driver - node in driver list
* @knode_bus - node in bus list
* @driver_data - private pointer for driver specific info. Will turn into a
* list soon.
* @device - pointer back to the struct class that this structure is
* associated with.
*
* Nothing outside of the driver core should ever touch these fields.
*/
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
void *driver_data;
struct device *device;
};
无论是从名字上,还是上面的注释中 我们都可以看到 void *driver_data;就是为设计用来放置不同驱动数据的。
为什么一定要放置在这里面,放置成全局变量那不行吗?
理论上是可以的,在驱动程序比较简单,在一个文件中编写,只需要把这个结构体定义为为本文件可见的静态的。但实际上,一些驱动程序的实现是比较复杂的,需要使用多个文件编写,这样如果写成全局变量,将会成为软件开发的噩梦。
probe函数编写的步骤
static int __devinit xxxx_probe(struct platform_device *pdev)
{
/* 申请内存,用来存放驱动程序用到的数据结构pdata */
/* 把申请到的数据结构pdata 的地址绑定到platform_device->dev->p->driver_data上 */
/* 填充申请到的数据结构pdata ,可能用到 platform_device->dev.platform_data 传过来的数据,经行动态映射等,传参,换算等等*/
/* 通过allo函数申请的设备资源,继续填充完数据结构pdata */
/* 填充和应用层交互的sysfs文件系统的接口函数到,数据结构pdata */
/* register_xxxx注册设备 */
/* 初始化设备 */
/* 填充完所有的数据 */
/* device_create_file创建和应用层交互的sysfs虚拟文件系统 */
}
其中 /* 填充和应用层交互的sysfs文件系统的接口函数到,数据结构pdata */
需要驱动层根据具体的设备情况编写,应用层用到的open,read等函数。
probe做完了主要的大多数工作。remove则处理的和probe刚好相反。
static int __devexit xxxx_remove(struct platform_device *pdev)
{
/* device_remove_file卸载sysfs虚拟文件系统 */
/* 取消初始化设备(如果设备开着的话要先关掉[最简单的是关掉其时钟]再取消初始化) */
/* unregister_xxxx卸载设备 */
/* 通过allo函数申请的设备资源,release掉 */
/* 申请的资源如io,中断,内存,dma等释放掉 */
/* 释放申请的内存pdata */
}
platform_deiver驱动编写部分经常用到下面几个宏,我对其进行简要分析:
#define to_platform_device(x) container_of((x), struct platform_device, dev)
#define platform_get_drvdata(_dev) dev_get_drvdata(&(_dev)->dev)
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))
第一个宏很简单,已知 struct platform_device结构体中的struct device结构体的地址,找到对应的struct platform_device结构体的地址。
使用的是linux内核应用最广的数据结构推算宏来实现的
/* 该宏有连个参数,第一个是结构体类型,第二个是其成员变量的名称
* 改宏大的作用是的得到结构体成员地址相对于结构体起始地址的偏移
* 方法也很经典,即假设该结构体位于0地址处,而成员变量的地址则就是该成员变量相对该结构体的偏移地址
*/
define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
/* 作用和参数上面都有解释,typeof是一个关键字,其作用是通过一个变量得到其类型 */
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \ /* 该句的作用是防止编译器出现告警 */
(type *)((char *)__mptr - offsetof(type, member)); }) /* offsetof宏看懂的话,这里面的减法就很简单了。 要注意的是(char *)__mptr 必须要把__mptr 强转为char*,否指针相减不是以字节为单位*/
第2、3个宏是一组宏。其中platform_set_drvdata是把参数data放进某个位置,platform_get_drvdata是从某个位置取出data.
#define platform_get_drvdata(_dev) dev_get_drvdata(&(_dev)->dev)
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))
先看platform_set_drvdata的实现
void dev_set_drvdata(struct device *dev, void *data)
{
int error;
if (!dev)
return;
if (!dev->p) {
error = device_private_init(dev);
if (error)
return;
}
dev->p->driver_data = data;
}
可以看到,这个宏前面主要是做参数有效性检查,如果给p没有分配内存的话,这里也会为p分配内存的。如果分配了的话,直接把data放到p里面的driver_data.
看到这里,有没有想起来driver_data是什么鬼啊?
就是probe函数刚进来,申请的自定义类型结构体的一个数据结构,这个数据结构超级重要,什么open,read之类都要使用里面的信息,所以platform驱动框架直接给我们把这两个给做成标准接口了,方便我们使用。
既然platform_set_drvdata已经清楚了,那dev_get_drvdata就一目了然了。
void *dev_get_drvdata(const struct device *dev)
{
if (dev && dev->p)
return dev->p->driver_data;
return NULL;
}