文章目录
- 一、简介
- (一)device、bus、driver模型
- (二)platform驱动原理
- 二、platform总线驱动的API
- (一)设备信息端
- (二)设备驱动端
- (三)驱动中获取设备信息函数
- (四)idtable
- 三、platform的设备树匹配方式
- (一)填充匹配项
- (二)platform设备树匹配实例
- (三)使用示例
一、简介
(一)device、bus、driver模型
在Linux内核中所有总线驱动都遵从设备驱动的模型,总线驱动的模型如下图:
内核在设计这些总线驱动模型的时候将一个驱动分为了三个部分device、bus、driver。
device是用来描述硬件设备的,bus是总线用来链接device和driver,driver是用来描述驱动的对象。
在内核中所有的device放在内核的klist_devices的链表中管理,而内核中所有的driver放在klist_drivers中管理。内核中的device和driver通过bus完成关联。
当device和driver匹配成功之后执行驱动的probe函数,在probe函数中就可以完成操作硬件了。当卸载任何一方驱动的时候都会执行驱动中的remove函数。
(二)platform驱动原理
platform总线驱动遵从devices、bus、driver模型。platform总线驱动的思想就是要将设备信息和设备驱动进行分离。
platform是Linux内核抽象出来的软件代码,并没有真实的总线协议与之对应
platform_device和platform_driver通过总线匹配成功之后会执行驱动中probe函数,在probe函数中驱动就能够拿到设备信息。
二、platform总线驱动的API
(一)设备信息端
1.分配并初始化对象
struct platform_device {
const char *name; //用于匹配的名字
int id; //总线号,PLATFORM_DEVID_AUTO
struct device dev; //父类
u32 num_resources; //设备信息的个数
struct resource *resource; //设备信息结构体指针
};
--------------------------------
struct device {
void (*release)(struct device *dev); //释放资源的函数
};
---------------------------------
//设备信息结构体
struct resource {
resource_size_t start; //资源的起始值
//0x50006000 0x12345678 73
resource_size_t end; //资源的结束值
//0x50006000+3 0x12345678+49 73
unsigned long flags; //资源类型
//IORESOURCE_IO IORESOURCE_MEM IORESOURCE_IRQ
};
3.注册
int platform_device_register(struct platform_device *pdev)
4.注销
void platform_device_unregister(struct platform_device *pdev)
(二)设备驱动端
1.分配并初始化对象
struct platform_driver {
int (*probe)(struct platform_device *);
//匹配成功执行的函数
int (*remove)(struct platform_device *);
//分离的时候执行的函数
struct device_driver driver;
//父类
const struct platform_device_id *id_table;
//2.idtable匹配方式
};
struct device_driver {
const char *name; //1.按照名字匹配
const struct of_device_id *of_match_table; //3.设备树匹配
};
2.注册
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
3.注销
void platform_driver_unregister(struct platform_driver *);
4.一键注册注销的宏
#define module_platform_driver(pdrv) \
module_driver(pdrv, platform_driver_register, \
platform_driver_unregister)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init pdrv_init(void)
{
return platform_driver_register(&(pdrv));
}
module_init(pdrv_init);
static void __exit pdrv_exit(void)
{
platform_driver_unregister(&(pdrv));
}
module_exit(pdrv_exit);
- 注:在注册过程去完成匹配,但是哪一方没有匹配上都不会报错,而是作为一个节点放在列表,等待对方匹配
- 设备驱动端可以直接调用宏函数,完成入口和出口的指定,无需再单独写
- 无论卸载pdev还是pdrv,remove函数都会执行
(三)驱动中获取设备信息函数
(四)idtable
在编写驱动时,有时一个驱动可以通过兼容多个设备,在驱动中就需要填写可以匹配的所有名字,将这个名字填写到一个表里,这个表就是idtable
匹配顺序是:设备树、idtable、name,依次进行匹配
当使用idtable进行匹配时,匹配上的id会保存在struct platform_device结构体中的id_entry字段中
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
- 注:
- idtable中必须写一个空的{},表示结束,否则内核可能会越界访问
- pdrv驱动运行时会以name创建文件夹,即使不以name匹配,但是name必须填充,否则可能出现空指针,导致内核崩溃
三、platform的设备树匹配方式
platform_device结构体中的resource是用来描述设备信息的结构体,在linux-3.10版本之后
在linux内核启动时会根据设备树节点(compatible)自动创建platform_device结构体,并完成注册
(一)填充匹配项
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
struct of_device_id oftable[] = {
{.compatible = "hqyj,plat_node",},
{} //必须填写一个空的括号,防止内存越界
};
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "duang", //会以name创建文件夹,如果没有填写,会出现内核空指针
//虽然不按照name匹配,但name不能缺省
.of_match_table = oftable,
},
};
(二)platform设备树匹配实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
struct resource* res;
int irqno;
int pdrv_probe(struct platform_device* pdev)
{
//pdev->dev.of_node //设备树节点
//platform_get_resouce的参数填写为IORESOURCE_MEM只能解析
// 设备树中reg属性
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
pr_err("platform_get_resource error");
return -ENODATA;
}
// platform_get_irq只能用来解析设备树中的中断属性
irqno = platform_get_irq(pdev, 0);
if (irqno < 0) {
pr_err("platform_get_irq error");
return irqno;
}
printk("addr=%#x,irqno=%d\n", res->start, irqno);
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct of_device_id oftable[] = {
{.compatible = "hqyj,plat_node",},
{}
};
struct platform_driver pdrv = {
.probe = pdrv_probe,
.remove = pdrv_remove,
.driver = {
.name = "duang duang duang", //会以name创建文件夹,如果没有填写,会出现内核空指针
//虽然不按照name匹配,但name不能缺省
.of_match_table = oftable
},
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL
- 注:与原有方式的差异,原有方式在没有设备树节点时会直接返回报错,无法安装驱动,但是基于platform的方式可以安装,当有驱动时可以立即执行
- 不用自己去获取节点
(三)使用示例
功能需求:
使用驱动代码实现如下要求:
a.应用程序通过阻塞的io模型来读取status变量的值
b.status是内核驱动中的一个变量,代表LED1的状态
c.status的值随着按键按下而改变(按键中断)
例如status=0 按下按键status=1 ,再次按下按键status=0
d.在按下按键的时候需要同时将led1的状态取反
e.驱动中需要编写字符设备驱动
f.驱动中需要自动创建设备节点
g.这个驱动需要的所有设备信息放在设备树的同一个节点中
h.通过platform驱动实现
需求分析:
代码实现:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h> //设备树文件相关头文件
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#define CNAME "plat_led"
struct cdev *led_cdev;
struct class *led_class;
struct device *led_device;
int major = 0; //主设备号
int minor = 0;
dev_t led_dev_num;
int led_no; //led的gpio号
int irqno; //中断号
int status=0; //LED灯状态
int kbuf[8]={0};
wait_queue_head_t wait_head; // 定义等待队列头
int condition = 0;
int led_open(struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t led_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset){
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if(file->f_flags & O_NONBLOCK){
return -EINVAL;
}
ret = wait_event_interruptible(wait_head, condition);
if (ret) {
pr_err("interrupt by signal...\n");
return ret;
}
if(size>sizeof(status)){
size = status;
}
kbuf[0]=status;
copy_to_user(ubuf,kbuf,sizeof(kbuf));
// 将condition清零
condition = 0;
return 0;
}
int led_close(struct inode *inode, struct file *file){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
struct file_operations ledfops={
.open=led_open,
.read=led_read,
.release=led_close,
};
irqreturn_t key_irq_handler(int irqno, void *arg){
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
status=!status;
gpio_set_value(led_no,status);
condition=1;
wake_up_interruptible(&wait_head);
printk("key_irq_handler status=%d;condition=%d\n",status,condition);
return IRQ_HANDLED;
}
int my_drv_probe(struct platform_device *dev){
struct device_node *plat_node;
int ret=0;
/***gpio初始化***/
//1. 获取节点
plat_node = dev->dev.of_node;
//2.获取gpio号
led_no=of_get_named_gpio(plat_node,"led",0);
if(led_no < 0){
pr_err("of_get_named_gpio error");
return led_no;
}
//3. 申请gpio
ret=gpio_request(led_no,NULL);
if(ret){
pr_err("gpio_request error");
goto err0;
}
//4.设置方向为输出
ret = gpio_direction_output(led_no, 0);
if (ret) {
pr_err("gpio_direction_output error\n");
goto err1;
}
printk("**************gpio init over\n");
/***注册字符设备驱动***/
//1. 分配对象
led_cdev = cdev_alloc();
if(NULL == led_cdev){ //成功返回结构体指针,失败返回NULL
pr_err("cdv_err error");
ret = -ENOMEM;
goto err1;
}
//初始化对象:部分成员初始化
cdev_init(led_cdev,&ledfops);
//申请设备号:如果major为0,则动态申请,否则就静态指定
if(major > 0){
ret = register_chrdev_region(MKDEV(major,minor),1,CNAME);
if (ret) {
pr_err("register_chrdev_region error\n");
goto err2;
}
}else if(major == 0){
ret = alloc_chrdev_region(&led_dev_num,0,1,CNAME);
if (ret) {
pr_err("alloc_chrdev_region error\n");
goto err2;
}
major=MAJOR(led_dev_num);
minor=MINOR(led_dev_num);
}
//注册
ret = cdev_add(led_cdev,MKDEV(major,minor),1);
if (ret) {
pr_err("cdev_add error\n");
goto err3;
}
//自动创建设备节点
led_class=class_create(THIS_MODULE,CNAME);
if(IS_ERR(led_class)){
pr_err("class_create error");
ret = PTR_ERR(led_class);
goto err4;
}
led_device = device_create(led_class,NULL,MKDEV(major,minor),NULL,CNAME);
if(IS_ERR(led_device)){
pr_err("device_create error");
ret = PTR_ERR(led_device);
goto err5;
}
printk("**************cdev init over\n");
/***初始化中断***/
//2.获取软中断号
irqno=platform_get_irq(dev,0);
if(irqno < 0){
pr_err("platform_get_irq error");
ret = irqno;
goto err5;
}
//3.注册中断号
ret = request_irq(irqno,key_irq_handler,IRQF_TRIGGER_FALLING,"my_IRQ_test",NULL);
if(ret){
pr_err("request_irq error");
goto err5;
}
printk("**************irq init over\n");
/***阻塞IO初始化***/
//初始化等待队列头
init_waitqueue_head(&wait_head);
printk("**************wait_head init over\n");
return 0;
err5:
class_destroy(led_class);
err4:
cdev_del(led_cdev);
err3:
unregister_chrdev_region(MKDEV(major,minor),1);
err2:
kfree(led_cdev);
err1:
gpio_free(led_no);
err0:
return ret;
}
int my_drv_remove(struct platform_device *dev){
printk("my_drv_remove is run...\n");
free_irq(irqno,NULL);
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major,"mypdrv");
gpio_free(led_no);
return 0;
}
//设备树匹配
struct of_device_id my_of_match[]={
{.compatible="zyx,myplatform"},
{},
};
struct platform_driver myplatform={
.probe=my_drv_probe,
.remove=my_drv_remove,
.driver={
.name="platformName",
.of_match_table=my_of_match,
},
};
module_platform_driver(myplatform);
MODULE_LICENSE("GPL");