Linux 下 platform 设备驱动

在Linux2.6 以后的设备驱动模型中,需要关心总线、设备和驱动3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反  ,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SOC内存空间的外设等不依附于此类总线。在linux中实现了一种虚拟总线,称为platform总线,相应的设备称为platform_device ,而驱动称为 platform_driver。   所谓的platform_device 并不是与字符设备、块设备和网络设备并列的概念,而是linux 系统提供的一种附加手段,例如,我们通常把在SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而它们本身是字符设备。

platform_device结构体的定义如下

struct platform_device {           //  platform总线设备
    const char    * name;          //  平台设备的名字
    int        id;                 //   ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
    boo id_auto;
    struct device    dev;          //   内置的device结构体
    u32        num_resources;      //   资源结构体数量
    struct resource    * resource; //   指向一个资源结构体数组

    const struct platform_device_id    *id_entry; //  用来进行与设备驱动匹配用的id_table表
    
    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;
    /* arch specific additions */
    struct pdev_archdata    archdata;             //  自留地    添加自己的东西
};

 其中struct resource结构体的定义如下,描述了platform_device 的资源,本身由一个结构体表示

struct resource {      // 资源结构体
    resource_size_t start;      // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
    resource_size_t end;        // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
    const char *name;           // 资源名
    unsigned long flags;        // 资源的标示,用来识别不同的资源
    struct resource *parent, *sibling, *child;   // 资源指针,可以构成链表
};

 我们通常关心的是start,end,flags 三个参数,它们分别标明了资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORE-SOURCE_DMA等。start、end的 含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的 内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的 中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可 以有多份,例如说某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。

对resource的定义也通常在BSP的板文件中进行,而在具体的设备驱动中通过platform_get_resource() 这样的API来获取,此API的原型为

struct resource *platform_get_resource(struct platform_device *, unsigned int,    unsigned int);

platform_driver 结构体的定义如下

struct platform_driver {
    int (*probe)(struct platform_device *);     //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    int (*remove)(struct platform_device *);    //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                //   内置的device_driver 结构体 
    const struct platform_device_id *id_table;  //  该设备驱动支持的设备的列表  他是通过这个指针去指向  platform_device_id 类型的数组
    bool prevent_deferred_probe;
};

其中struct device_driver结构体的定义为

struct device_driver {
    const char              *name;
    struct bus_type         *bus;
    struct module           *owner;
    const char              *mod_name;  /* used for built-in modules *
    bool suppress_bind_attrs;           /* disables bind/unbind via sysfs */
    const struct of_device_id           *of_match_table;
    const struct acpi_device_id         *acpi_match_table;
    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;
    const struct dev_pm_ops *pm;
    struct driver_private *p;
};

 platform的接口函数

int platform_driver_register(struct platform_driver *);       // 用来注册我们的设备驱动    

void platform_driver_unregister(struct platform_driver *);  // 用来卸载我们的设备驱动

int platform_device_register(struct platform_device *);      // 用来注册我们的设备      

void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备

系统为platform总线定义一个bus_type的实例platform_bus_type

struct bus_type platform_bus_type = {
    .name           = "platform"
    .dev_groups     = platform_dev_groups,
    .match          = platform_match,
    .uevent         = platform_uevent,
    .pm             = &platform_dev_pm_ops,
};

这里重点关注成员函数match(),其确定device和driver如何匹配。

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);
    /* Attempt an OF style match first */ 
    if (of_driver_match_device(dev, drv))
        return 1;
    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;
    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
    /* fall-back to driver name match */
     return (strcmp(pdev->name, drv->name) == 0);
}

可以看出,匹配platform_device和platform_driver有4种可能性,一是基于设备树风格的匹配;二是基于ACPI风格的匹配;三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID 表内);第四种是匹配platform_device设备名和驱动的名字。匹配platform_device和platform_driver主要看二者的name字段是否相同。(name必须要相同才能匹配)

对于linuxARM平台而言,对设备platform_device的定义通常在BSP 的板级配置文件中实现,在板文件中,将platform_device归纳与一个数组,最终通过 platform_add_devices() 函数统一注册。

platform_add_devices() 函数可以将平台设备添加到系统中,这个函数的原型为:

int platform_add_devices(struct platform_device **devs,int num);

该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register() 函数以注册单个的平台设备。在linux3以后,ARMlinux 不太喜欢人们以编码的形式去填写platform_device和注册,而倾向于根据设备数中的内容自动展开platform_device。

下面将之前写的简单字符设备驱动改为platform总线的字符设备

先写一个设备框架mymodule_device.c,只添加设备驱动匹配名称

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>


static struct platform_device mymodule_dev = {
    .name = "mymodules",
    .id = -1,
};

static int mymodule_dev_init(void)
{
    int result = platform_device_register(&mymodele_dev);
    return result;
}

static void mymodule_dev_exit(void)
{
    platform_device_unregister(&mymodule_dev);
}

module_init(mymodule_dev_init);
module_exit(mymodule_dev_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");

在修改设备驱动文件mymodule_driver.c

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/poll.h> 
#include <linux/platform_device.h>

static int major=0,minor=0;
#define BUF_CLEAR 0x1 
//定义cdev结构体
struct mymodule_dev{
    struct cdev cdev;
    unsigned char buf[512];
    struct mutex mutex;
    unsigned int current_len;//buf中实际数据长度
    wait_queue_head_t r_wait;//定义读写等待队列头部
    wait_queue_head_t w_wait;
    struct fasync_struct *async_queue; //异步结构体
};
 
struct mymodule_dev *mydev;
struct class *my_class;
struct device *my_device;
 
static int mymodule_fasync(int fd,struct file *filp,int mode)
{
    struct mymodule_dev *dev = filp->private_data;
    return fasync_helper(fd,filp,mode,&dev->async_queue);
}

static int mymodule_open(struct inode *inode,struct file *filp)
{
    filp->private_data = mydev;
    return 0;
}
 
static int mymodule_release(struct inode *inode,struct file *filp)
{
    mymodule_fasync(-1,filp,0);
    return 0;
}
 
static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    DECLARE_WAITQUEUE(wait,current);//定义等待队列元素
    mutex_lock(&dev->mutex);
    add_wait_queue(&dev->r_wait,&wait);//等待队列头部添加到等待队列中
    while(dev->current_len ==0){
        if(filp->f_flags & O_NONBLOCK){  //非阻塞时
            ret = -EAGAIN;
            goto out;
        }
        __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态
        mutex_unlock(&dev->mutex);

        schedule();
        if(signal_pending(current)){  //如果是因为信号唤醒
            ret = -ERESTARTSYS;
            goto out2;
        }
        mutex_lock(&dev->mutex);
    }

    if(count > dev->current_len)
        count = dev->current_len;
   //内核空间->用户空间
    if(copy_to_user(buf,dev->buf,count)){
        ret = -EFAULT;
        goto out;
    }else{
        memcpy(dev->buf,dev->buf+count,dev->current_len-count);
        dev->current_len -= count;
        printk(KERN_INFO"Read %d bytes,current_len %d\n",count,dev->current_len);
        wake_up_interruptible(&dev->w_wait);//唤醒可能阻塞的写进程
        ret = count;
    }
out:
    mutex_unlock(&dev->mutex);
out2:
    remove_wait_queue(&dev->w_wait,&wait); //将元素移除等待队列
    set_current_state(TASK_RUNNING); //设置进程状态
    return ret;
}
 
static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    DECLARE_WAITQUEUE(wait,current);
    mutex_lock(&dev->mutex);
    add_wait_queue(&dev->w_wait,&wait);
    while(dev->current_len == 512){
        if(filp->f_flags & O_NONBLOCK){
            ret = -EAGAIN;
            goto out;
        }
        __set_current_state(TASK_INTERRUPTIBLE);
        mutex_unlock(&dev->mutex);

        schedule();
        if(signal_pending(current)){
            ret = -ERESTARTSYS;
            goto out2;
        }
        mutex_lock(&dev->mutex);
    }
    if(count > 512 - dev->current_len)
        count = 512 - dev->current_len;
    //用户空间->内核空间
    if(copy_from_user(dev->buf+dev->current_len,buf,count)){
        ret = -EFAULT;
        goto out;
    }else{
        dev->current_len += count;
        printk(KERN_INFO"Writen %d bytes,current_len: %d\n",count,dev->current_len);
        wake_up_interruptible(&dev->r_wait);

        if(dev->async_queue){
            kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
            printk(KERN_DEBUG"%s kill SIGIO\n",__func__);
        }
        ret = count;
    }
out:
    mutex_unlock(&dev->mutex);
out2:
    remove_wait_queue(&dev->w_wait,&wait);
    set_current_state(TASK_RUNNING);
    return ret;
}
 
static unsigned int mymodule_poll(struct file *filp,struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct mymodule_dev *dev = filp->private_data;

    mutex_lock(&dev->mutex);

    poll_wait(filp,&dev->r_wait,wait);
    poll_wait(filp,&dev->w_wait,wait);

    if(dev->current_len !=0)
        mask |= POLLIN | POLLRDNORM;

    if(dev->current_len != 512)
        mask |= POLLOUT | POLLWRNORM;

    mutex_unlock(&dev->mutex);
    return mask;
}

static long mymodule_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    struct mymodule_dev *dev = filp->private_data;
    switch(cmd)
    {
        case BUF_CLEAR:
            mutex_lock(&dev->mutex);
            memset(dev->buf,0,512);
            mutex_unlock(&dev->mutex);
            printk(KERN_INFO"globalmem is set to zero\n");
            break;
        default:
            return - EINVAL;
    }
    return 0;
}
//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
    .owner = THIS_MODULE,
    .open = mymodule_open,
    .release = mymodule_release,
    .read = mymodule_read,
    .write = mymodule_write,
    .poll = mymodule_poll,
    .compat_ioctl = mymodule_ioctl,
    .fasync = mymodule_fasync,
};
 
//初始化并添加cdev结构体
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
    int err,devno=MKDEV(major,minor);
    //初始化
    cdev_init(&dev->cdev,&mymodule_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &mymodule_fops;
    //注册,添加
    err = cdev_add(&dev->cdev,devno,1);
    if(err)
        printk(KERN_NOTICE"error %d adding mymodule",err);
}


static int mymodule_probe(void)
{
     //申请设备号
    int result;
    dev_t devno = MKDEV(major,minor);
    if(major)
        result = register_chrdev_region(devno,1,"mymodule");
    else{
        result = alloc_chrdev_region(&devno,minor,1,"mymodule");
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    if(result<0)
        return result;
    //动态申请设备结构体内存
    mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
    if(!mydev){
        result=-ENOMEM;
        goto fail_malloc;
    }
    memset(mydev,0,sizeof(struct mymodule_dev));
    //初始化互斥体
    mutex_init(&mydev->mutex); 
   //cdev字符设备的初始化和添加
    mymodule_cdev_setup(mydev);

    //初始化等待队列长度
    init_waitqueue_head(&mydev->r_wait);
    init_waitqueue_head(&mydev->w_wait);
    
    //注册设备节点
    my_class = class_create(THIS_MODULE,"mymodule_t");
    my_device = device_create(my_class,NULL,MKDEV(major,minor),NULL,"mymodules");
 
    return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;
}

static int mymodule_remove(void)
{
    device_destroy(my_class,MKDEV(major,minor));
    class_destroy(my_class);
    //删除cdev结构体
    cdev_del(&mydev->cdev);
    kfree(mydev);
    //注销设备号
    unregister_chrdev_region(MKDEV(major,minor),1);
}

static struct platform_driver mymodule_drv = {
    .porbe = mymodule_probe,
    .remove = mymodule_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "mymodules",
    },
};
 
//模块加载
int __init mymodule_init(void)
{
    int result = platform_driver_register(&mymodule_drv);
    return result;
}
//模块卸载
void __exit mymodule_exit(void)
{
    platform_driver_unregister(&mymodule_drv);
}
 
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");

编译后两个.ko  都加载后,在/dev下才出现 mymodules的设备文件,并且在/sys/devices/platform 下会出现mymodules的文件夹。在mymodules文件夹下会有driver文件,它是指向/sys/bus/platform/devices/mymodules 的符号链接,这证明驱动和设备匹配上了。

上述的mymodule_device.c 的文件可以不写,只需要在内核/arch/arm/mach-vexpress/ct-ca9x4.c 中添加如下代码代码,然后重新编译内核与驱动。

static struct platform_device mymodule_device = {
    .name         = "mymodules",
    .id         = -1, 
}
发布了47 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42005898/article/details/104948337