1. 程式人生 > >14.linux-platform機制實現驅動層分離(詳解)

14.linux-platform機制實現驅動層分離(詳解)

擴展性 blank 事件處理 相關 技術分享 消息 驅動 array iou

版權聲明:本文為博主原創文章,未經博主允許不得轉載。


本節目標:

學習platform機制,如何實現驅動層分離


1.先來看看我們之前分析輸入子系統的分層概念,如下圖所示:

技術分享

如上圖所示,分層就是將一個復雜的工作分成了4層, 分而做之,降低難度,每一層專註於自己的事情, 系統只將其中的核心層和事件處理層寫好了,所以我們只需要來寫驅動層即可,接下來我們來分析platform機制以及分離概念

2.分離概念

優點:

  • 將所有設備掛接到一個虛擬的總線上,方便sysfs節點和設備電源的管理
  • 使得驅動代碼,具有更好的擴展性和跨平臺性,就不會因為新的平臺而再次編寫驅動

介紹:

分離就是在驅動層中使用platform機制把硬件相關的代碼(固定的,如板子的網卡、中斷地址)和驅動(會根據程序作變動,如點哪一個燈)分離開來,即要編寫兩個文件:dev.c和drv.c(platform設備和platform驅動)

3.platform機制

基本內容:

platform會存在/sys/bus/裏面

如下圖所示, platform目錄下會有兩個文件,分別就是platform設備和platform驅動

技術分享

1) device設備

掛接在platform總線下的設備, platform_device結構體類型

2) driver驅動

掛接在platform總線下,是個與某種設備相對於的驅動, platform_driver結構體類型

3) platform總線

是個全局變量,為platform_bus_type,屬於虛擬設備總線,通過這個總線將設備和驅動聯系起來,屬於Linux中bus的一種

該platform_bus_type的結構體定義如下所示(位於drivers/base):

struct bus_type platform_bus_type = {              
.name              = "platform",             //設備名稱
.dev_attrs        = platform_dev_attrs,                 //設備屬性、含獲取sys文件名,該總線會放在/sys/bus下
.match = platform_match, //匹配設備和驅動,匹配成功就調用device或driver的.probe函數 .uevent = platform_uevent, //消息傳遞,比如熱插拔操作 .suspend = platform_suspend, //電源管理的低功耗掛起 .suspend_late = platform_suspend_late, //電源管理的恢復、喚醒 .resume_early = platform_resume_early, .resume = platform_resume, };

驅動、設備註冊匹配圖如下所示:

技術分享

只要有一方註冊,就會調用platform_bus_type的.match匹配函數,來找對方,成功就調用driver驅動結構體裏的.probe函數來使總線將設備和驅動聯系起來

4.實例-分析driver驅動:

我們以/drivers/input/keybard/gpio_keys.c內核自帶的示例程序為例,

它的代碼中只有driver驅動,因為是個示例程序,所以沒有device硬件設備代碼

4.1發現在gpio_keys.c中有1個全局變量driver驅動:

struct platform_driver gpio_keys_device_driver = {  //定義一個platform_driver類型驅動

    .probe      = gpio_keys_probe,                //設備的檢測,當匹配成功就會調用這個函數(需要自己編寫)   
    .remove     = __devexit_p(gpio_keys_remove), //刪除設備(需要自己編寫)
    .driver     = {
            .name  = "gpio-keys",               //驅動名稱,用來與設備名稱匹配用的
         }
};

4.2然後來找找這個gpio_keys_device_driver被誰用到

發現在驅動層init入口函數中通過platform_driver_register()來註冊diver驅動

在驅動層exit出口函數中通過platform_driver_unregister()函數來註銷diver驅動

代碼如下:

static int __init gpio_keys_init(void)    //init出口函數
{
   return platform_driver_register(&gpio_keys_device_driver);   //註冊driver驅動
}

static void __exit gpio_keys_exit(void)   //exit出口函數
{
   platform_driver_unregister(&gpio_keys_device_driver);   //註銷driver驅動
}

3.3我們進來platform_driver_register(),看它是如何註冊diver的,註冊到哪裏?

platform_driver_register()函數如下:

int platform_driver_register(struct platform_driver *drv)
{
   drv->driver.bus = &platform_bus_type;                //(1)掛接到虛擬總線platform_bus_type上
   if (drv->probe)
         drv->driver.probe = platform_drv_probe;
   if (drv->remove)
        drv->driver.remove = platform_drv_remove;
   if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
   if (drv->suspend)
        drv->driver.suspend = platform_drv_suspend;
    if (drv->resume)
        drv->driver.resume = platform_drv_resume;

   return driver_register(&drv->driver);              //(2) 註冊到driver目錄下
}

(1) 掛接到虛擬總線platform_bus_type上,然後會調用platform_bus_type下的platform_match匹配函數,來匹配device和driver的名字,其中driver的名字如下圖所示:

技術分享

platform_match()匹配函數如下所示:

static int platform_match(struct device * dev, struct device_driver * drv)
{
/*找到所有的device設備*/
struct platform_device *pdev = container_of(dev, struct platform_device, dev);

return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); //找BUS_ID_SIZE次
}

若名字匹配成功,則調用device的.probe成員函數

(2)然後放到/sys/bus/platform/driver目錄下,其中driver_register()函數就是用來創建dirver目錄的

5. 使用platform機制,編寫LED驅動層

首先創建設備代碼和驅動代碼:led_dev.c 、led_drv.c

led_dev.c用來指定燈的引腳地址,當更換平臺時,只需要修改這個就行

led_drv.c用來初始化燈以及如何控制燈的邏輯,當更換控制邏輯時,只需要修改這個就行

6.編寫led.dev.c

6.1編寫led_dev.c之前先來看看platform_device結構體和要使用的函數:

platform_device結構體如下:

 struct platform_device {
  const char       * name;           //設備名稱,要與platform_driver的name一樣,這樣總線才能匹配成功
  u32          id;                   //id號,插入總線下相同name的設備編號(一個驅動可以有多個設備),如果只有一個設備填-1
  struct  device  dev;               //內嵌的具體的device結構體,其中成員.release函數不可缺少
  u32 num_resources;                 //資源數量,
  struct resource         * resource;    //資源結構體,保存設備的信息
};

其中resource資源結構體,如下:

struct resource {
         resource_size_t start;                    //起始資源,如果是地址的話,必須是物理地址
         resource_size_t end;                      //結束資源,如果是地址的話,必須是物理地址
         const char *name;                         //資源名
         unsigned long flags;                      //資源的標誌
         //比如IORESOURCE_MEM,表示地址資源, IORESOURCE_IRQ表示中斷引腳... ...

         struct resource *parent, *sibling, *child;   //資源拓撲指針父、兄、子,可以構成鏈表
};

要用的函數如下,在dev設備的入口出口函數中用到

int platform_device_register(struct platform_device * pdev);       //註冊dev設備
int platform_device_register(struct platform_device * pdev);       //註銷dev設備

6.2接下來開始寫代碼

1)先寫要註冊的led設備:platform_device結構體

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>

static struct resource led_resource[] = {               //資源數組
    [0] = {
        .start = 0x56000050,                     //led的寄存器GPFCON起始地址
        .end   = 0x56000050 + 8 - 1,     // led的寄存器GPFDAT結束地址
        .flags = IORESOURCE_MEM,      //表示地址資源
    },
    [1] = {
        .start =  5,                                   //表示GPF第幾個引腳開始
        .end   = 5,                            //結束引腳
        .flags = IORESOURCE_IRQ,     //表示中斷資源    
} };
static void led_release(struct device * dev) //釋放函數 {} static struct platform_device led_dev = { .name = "myled", //對應的platform_driver驅動的名字 .id = -1, //表示只有一個設備 .num_resources = ARRAY_SIZE(led_resource), //資源數量,ARRAY_SIZE()函數:獲取數量 .resource = led_resource, //資源數組led_resource .dev = { .release = led_release, //釋放函數,必須向內核提供一個release函數, 、 //否則卸載時,內核找不到該函數會報錯 }, };

2)最後寫出口入口函數:

static int led_dev_init(void)    //入口函數,註冊dev設備
{
  platform_device_register(&led_dev);
  return 0;
}

static void led_dev_exit(void) //出口函數,註銷dev設備
{
  platform_device_unregister(&led_dev); 
}
module_init(led_dev_init);   //修飾入口函數
module_exit(led_dev_exit);  //修飾出口函數
MODULE_LICENSE("GPL");   //聲明函數

7.編寫led.drv.c

7.1編寫led_dev.c之前先來看看platform_device結構體和要使用的函數:

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 (*suspend_late)(struct platform_device *, pm_message_t state);
       int (*resume_early)(struct platform_device *);
       int (*resume)(struct platform_device *);           //喚醒
       struct device_driver driver;       //內嵌的driver,其中的name成員要等於設備的名稱才能匹配

};



int platform_driver_register(struct platform_driver *drv); //註冊驅動 platform_driver_unregister(struct platform_driver *drv); //卸載驅動 struct resource * platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num); //獲取設備的某個資源,獲取成功,則返回一個resource資源結構體 //參數: // *dev :指向某個platform device設備 // type:獲取的資源類型 // num: type資源下的第幾個數組

7.2接下來開始寫代碼

1)先寫要註冊的led驅動:platform_driver結構體

/*函數聲明*/
static  int  led_remove(struct platform_device *led_dev);
static  int led_probe(struct platform_device *led_dev);

struct platform_driver led_drv = {
       .probe           = led_probe,        //當與設備匹配,則調用該函數
       .remove         = led_remove,             //刪除設備

       .driver            = {
              .name     = "myled",           //與設備名稱一樣
       }
};

2)寫file_operations 結構體、以及成員函數(.open、.write)、.probe函數、

當驅動和設備都insmod加載後,然後bus總線會匹配成功,就進入.probe函數,

在.probe函數中便使用platform_get_resource()函數獲取LED的地址和引腳,然後初始化LED,並註冊字符設備和設備節點"led"

static struct class *cls;                                      //類,用來註冊,和註銷
static volatile unsigned long *gpio_con;         //被file_operations的.open函數用
static volatile unsigned long *gpio_dat;          //被file_operations的.write函數用
static int pin;                                                 //LED位於的引腳值

static int led_open(struct inode *inode, struct file  *file)
 {
       *GPFcon&=~(0x03<<(LED_PIN*2));
       *GPFcon|=(0x01<<(LED_PIN*2));   
       return 0;
 } 

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
       int val=0;
       if(count!=1)
              return -EINAL;
       copy_from_user(&val,buf,count);      //從用戶(應用層)拷貝數據       

       if(val)      //開燈
       {
       *GPFdat&=~(0x1<<LED_PIN);
       }
       else
       {
       *GPFdat |= (0x1<<LED_PIN);
       }   
       return 0 ;
}


static struct  file_operations led_fops= {
    .owner  =   THIS_MODULE,     //被使用時阻止模塊被卸載
    .open   =   led_open,     
    .write   =   led_write,   
  };

 
static int led_probe(struct platform_device *pdev)
{
       struct resource      *res;
       printk("enter probe\n");

       /* 根據platform_device的資源進行ioremap */
       res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲取寄存器地址
       gpio_con = ioremap(res->start, res->end - res->start + 1); //獲取虛擬地址
       gpio_dat = gpio_con + 1;

       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);   //獲取引腳值
       pin = res->start;

       /* 註冊字符設備驅動程序 */
       major = register_chrdev(0, "myled", &led_fops);   //賦入file_operations結構體
       cls = class_create(THIS_MODULE, "myled");
       class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
       return 0;
}

3)寫.remove函數

如果驅動與設備已聯系起來,當卸載驅動時,就會調用.remove函數卸載設備

和.probe函數一樣,註冊了什麽就卸載什麽便可

static int led_remove(struct platform_device *pdev)
{
       /* 卸載字符設備驅動程序 */
       printk("enter remove\n");
       class_device_destroy(cls, MKDEV(major, 0));
       class_destroy(cls);
       unregister_chrdev(major, "myled");

       iounmap(gpio_con);       //註銷虛擬地址
       return 0;
}

4)最後寫drv的入口出口函數

static int led_drv_init(void)           //入口函數,註冊驅動
{
       platform_driver_register(&led_drv);
       return 0;
} 
static void led_drv_exit(void)       //出口函數,卸載驅動
{
       platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);     
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

5.測試運行

1)如下圖,我們先掛載dev設備模塊,和我們之前分析的一樣,它在platform/devices目錄下生成一個"myled"設備

技術分享

2)如下圖,我們再來掛載drv驅動模塊,同樣的在platform/drivers目錄下生成一個"myled"驅動,devices目錄下的"myled"設備匹配成功,進入.probe函數創建設備,接下來就可以使用應用程序來控制led燈了

技術分享

3)如下圖,卸載驅動時,也會進入.remove函數卸載設備

技術分享

14.linux-platform機制實現驅動層分離(詳解)