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, 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章