一,平台总线的概念和作用
1,概念
总线通信都是有协议的。类似一条告诉公路
分为物理总线和虚拟总线
物理总线(现实中看的见的): i2c总线,spi总线,usb总线等,连接两个设备
虚拟总线(内核中):平台总线,连接两个对象
给两个对象提供一个匹配的平台,一个驱动可以匹配多个设备,也就是一夫多妻制。
平台总线:
一个驱动的组成:
1》实例化全局设备对象 ----kzalloc() //与内核相关
2》申请设备号 ----register_chrdev() //与内核相关
3》创建类 ----class_create() //与内核相关
4》创建设备节点 ----device_create() //与内核相关
5》实现设备操作接口 ---- //与硬件相关
6》硬件初始化 ----ioremap() // 与硬件相关
利用平台总线:
在编写驱动时, 将驱动中相似操作与硬件差异化数据分离
在加载驱动时,利用平台总线将相似的操作和硬件差异化数据匹配,组成一个完整的驱动
2,作用
在编写驱动时,使用分离的思想实现驱动。
1》可以减少编写重复的代码
2》也便于对平台的升级或设备升级
也有利于对设备的升级,比方说:板子上的芯片不变,外设时用的这个串口,然后又换了一个串口,此时我们也只需要改一些硬件的资源。所以就减少了我们重复的工作。
二,平台总线编程
面向对象思想:框架
怎么匹配:
采用面向对象的编程思想。
1、在pdrv平台驱动模块中创建这个pdrv对象;
2、利用平台总线platform_bus将pdrv对象通过相关函数注册到总线上去;
3、写pdev.c来描述每一个设备,if有多个设备,就可以写多个pdev.c,同时也是都要注册到平台总线上,此时平台总线会通过id_table去帮你匹配,每注册一个pdev.c就和pdrv.c进行匹配,if匹配不上,就会提示不对,或则有问题;if匹配成功,就会调用 pdrv中的probe() 函数。
pdrv.c怎么体现他可以匹配多个pdev.c呢?
platform_driver里面有一个id_table(用于匹配pdev对象的列表),会与platform_device中的name(用于和pdrv匹配的名称)进行配对。
比如:
每个pdev.c都有一个名字,此时拿到pdev的名字,然后去与pdrv里面的id_table名字列表进行匹配,if匹配成功,就会调用proble()函数,然后实现对硬件的操作,失败是不会调用proble()函数的。
此时上面pdrv.c里面的操作就不用重复写了
这只是在编写的时候是采用分离思想,但是加载到内核中去后,内核会帮我们合成一个驱动程序,内核会利用平台总线的这个匹配函数帮我们把pdrv和pdev合成一个驱动,也就是说,只是开发方式变了
1,平台总线相关的几个结构体
1.1 总线类型(内核中每个结构体都是这样的一个对象)-----自己可以利用这个结构体写一个自己的总线
struct bus_type {
const char *name; //总线名称
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
//if内核里面要实现这个平台总线的功能,内核就会实现这个接口
int (*match)(struct device *dev, struct device_driver *drv); //用于匹配的接口
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
//注册和注销总线
extern int __must_check bus_register(struct bus_type *bus);
extern void bus_unregister(struct bus_type *bus);
1.2 平台设备驱动对象类型(pdrv)
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 of_device_id *of_match_table; //用于匹配设备树中节点的列表
const struct platform_device_id *id_table; //用于匹配pdev对象的列表
bool prevent_deferred_probe;
};
//注册和注销平台驱动
extern int __must_check driver_register(struct device_driver *drv);
extern void driver_unregister(struct device_driver *drv);
1.3 平台设备对象类型(pdev)
struct platform_device {
const char *name; //用于和pdrv匹配的名称
int id; //表示不同的设备编号,一般为-1
bool id_auto;
struct device dev; //父类
|
void *platform_data; /* 平台自定义数据 */
u64 platform_dma_mask;
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;
};
//注册和注销平台设备
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);
1.4 平台资源
资源分为两种:地址(内存) 资源, 中断资源
struct resource {
resource_size_t start; //如果是地址资源,表示起始地址,如果是中断资源,表示中断号
resource_size_t end; //如果是地址资源,表示最后一个字节的地址,如果是中断资源,表示中断号
const char *name; //字符串,表示描述信息,自定义
unsigned long flags; //表示资源的种类:地址资源(IORESOURCE_MEM)和中断资源(IORESOURCE_IRQ)
unsigned long desc;
struct resource *parent, *sibling, *child;
};
1.4.1 在pdrv中获取pdev中封装的资源
1.4.1.1 获取资源platform_get_resource
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
//参数1 ----- pdev对象指针
//参数2 ----- 资源的类型
//参数3 ----- 同类型资源的编号,每一种类型的资源编号都是从0开始
//返回值 ----- 成功:资源对象指针,失败:NULL
1.4.1.2 获取中断资源platform_get_irq
int platform_get_irq(struct platform_device *, unsigned int);
//参数1 ----- pdev对象指针
//参数2 ----- 资源的编号,从0开始
//返回值 ----- 成功:中断号,失败:错误码
例如:
int led_pdrv_probe(struct platform_device * pdev)
{
struct resource *res1,*res2,*res3;
unsigned int irqno;
printk("------------^_^ %s-----------------\n",__FUNCTION__);
//第一个地址资源
res1 = platform_get_resource(pdev,IORESOURCE_MEM,0);
printk("res1->start = 0x%x\n",res1->start);
//第二个地址资源
res3 = platform_get_resource(pdev,IORESOURCE_MEM,1);
printk("res3->start = 0x%x\n",res3->start);
//第一个中断资源 ----方法一
res2 = platform_get_resource(pdev,IORESOURCE_IRQ,0);
printk("res2->start = 0x%x\n",res2->start);
//获取第一个中断资源,方法二
irqno = platform_get_irq(pdev, 0);
printk("irqno = 0x%x\n",irqno);
return 0;
}
2、代码的实现思路:
xxx_pdev.c
//代码先写模块
1、入口函数、出口函数
static int __init led_pdev_init(void)
static void __exit led_pdev_exit(void)
2、函数声明
module_init(led_pdev_init);
module_exit(led_pdev_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y");
3、实例化pdev对象 //面向对象的编程
struct platform_device //平台设备对象类型
3_1、定义资源:
struct resource //以数组形式
例:
[0] = {
.start = GPIOZ, //其实地址
.end = GPIOZ + sizeof(gpio_t)-1, //末尾字节地址
.flags = IORESOURCE_MEM, //内存资源
},
[1] = {
.start = 0x120, //中断号
.end = 0x120, //中断号
.flags = IORESOURCE_IRQ, //中断资源
},
4、注册平台设备 //在入口函数
extern int platform_device_register(struct platform_device *);
5、注销平台设备 //在出口函数
extern void platform_device_unregister(struct platform_device *);
xxx_pdrv.c
1、入口函数、出口函数
static int __init led_pdrv_init(void)
static void __exit led_pdrv_exit(void)
2、函数声明
module_init(led_pdrv_init);
module_exit(led_pdrv_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(intree,"Y");
3、实例化pdrv对象 //面向对象的编程
struct platform_driver led_pdrv
//主要是实现函数接口
int (*probe)(struct platform_device *);
//封装加载函数中的代码:申请设备号-创建类--创建设备节点等
int (*remove)(struct platform_device *);
//封装卸载函数中的代码
4、注册平台驱动 //在入口函数
return platform_driver_register(&led_pdrv);
5、注销平台驱动 //在出口函数
platform_driver_unregister(&led_pdrv);
注: pdrv与pdev匹配成功会调用probe函数