Linux系统驱动(十六)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");
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值