1. 程式人生 > >linux平臺裝置驅動模型

linux平臺裝置驅動模型

linux平臺裝置介紹

linux2.6以上的裝置驅動模型中,有三大實體:匯流排,裝置和驅動。匯流排負責將裝置和驅動繫結,在系統沒註冊一個裝置的時候,會尋找與之匹配的驅動:相反的,在系統每註冊一個驅動的時候,會尋找與之匹配的裝置,而匹配則由匯流排完成。
一個現實的linux裝置和驅動通常都需要掛接在一個總線上例如PCIUSBI2CSPI介面的裝置都是由對應的匯流排來管理,通過匯流排來操作裝置。但對於Soc系統中整合的獨立外設控制器,比如PCIUSBI2CSPI等匯流排。基於這一背景,linux發明了一種虛擬的匯流排,稱為platform匯流排,相應的裝置稱為platform_device

,而驅動稱之為platform_driver

平臺匯流排驅動模型的特點

  • 平臺模型採用了分層結構,把一個裝置驅動分為了兩個部分:平臺裝置(platform_device)和平臺驅動(platform_driver)
  • 平臺裝置將裝置本身的資源(佔用哪些中斷號,記憶體資源,IO口等)註冊進核心,可以由核心統一管理,驅動更加安全、可靠。
    驅動層probe函式使用資源前必須先申請:
    如果前面驅動對資源已經佔用,則本驅動佔用失敗
  • 統一了裝置驅動模型,使智慧電源管理更容易實現
  • 從程式碼維護角度看,平臺模型的可移植性,通用性更好
  • 平臺裝置並不是一種新的裝置,linux目前只有三種裝置型別:字元裝置、塊裝置、網路裝置
  • 平臺裝置是按匯流排進行劃分的,所以平臺裝置不是一種新裝置,也不是特定字元裝置或塊裝置,它可以是三種裝置之一

platform模型分層模型

  • 分為兩個部分:裝置資訊、驅動程式
  • 如果裝置正常工作,就必須把裝置資訊及驅動程式都註冊到核心中
  • 裝置資訊和驅動程式分別攜程獨立的ko檔案(也可以寫成一個ko

認識分層

  • 先安裝驅動,再安裝裝置
    當安裝裝置模組的時候,核心會為當前安裝的裝置查詢匹配的驅動程式,如果找到了,則繫結在一起,然後呼叫驅動中的platform_driver結構中的probe成員指標所指向的函式;當解除安裝已經匹配上的裝置模組的時候,核心會呼叫驅動中的platform_driver
    結構中remove成員指標所指向的函式
  • 先安裝裝置,再安裝驅動
    當安裝驅動模組的時候,核心會為當前安裝的驅動查詢匹配的裝置,如果找到了,則繫結在一起,然後呼叫驅動中的platform_driver結構中的probe成員指標所指向的函式;當解除安裝已經匹配上的裝置模組的時候,核心會呼叫驅動中的platform_driver結構中remove成員指標所指向的函式
  • 如果只安裝裝置
    解除安裝時和普通的模組解除安裝一樣,並且還會執行裝置模組中的plaform_device.dev_release成員指標指向的函式
  • 如果只安裝驅動
    則解除安裝模組時執行情況和普通模組執行狀況一樣,不會執行proberemove指標指向的函式

    總的結論:不管先安裝裝置模組還是先安裝驅動模組,核心都正確的匹配進行繫結裝置和驅動的繫結

核心為裝置和驅動匹配的依據

根據驅動模組中的`platform_driver.driver.name`和裝置模組中的`platform_device.name` 字串是否相同。 這只是平臺裝置驅動模型第一種匹配方式,當前核心還有其他兩種匹配方法: 第一種:根據驅動程式碼`platform_driver.id_table.name`是否和裝置程式碼中的`platform_device.name`相同 第二種:使用裝置樹方式進行匹配,驅動需要使用到`platform_driver.driver.of_device_id`實現,這個是高版本核心最新出現的一種匹配方式

使用id_table方式進行匹配可以做到多個裝置驅動一個驅動的效果

平臺裝置層程式設計

核心資料結構

  • struct platform_device
    路徑:platform_device.h linux-3.5\include\linux
struct platform_device {
    const char  * name;    //需要關注的
    int     id;            //需要關注的
    struct device   dev;   //需要關注的
    u32     num_resources; //需要關注的
    struct resource * resource;  //需要關注的

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

重要成員說明:
name:裝置名,用來和驅動層程式碼做匹配,必須和驅動層中的platform_driver.driver.name或者platform_driver.id_table.name相同,這樣安裝裝置和驅動模組才可以匹配上。
id:當裝置和驅動是一對一關係時,一般設定成-1
dev:內嵌的裝置模型結構2.6版本把裝置模型進行了統一,就使用該結構描述一切裝置,是所有裝置的基礎
num_resources:標識裝置佔用了多少個資源,實際上用來描述,resource指向的資源陣列的元素數量。
resource:平臺裝置資源指標(資源表示物理裝置在系統中佔用了哪些記憶體,IO口,中斷號等)

  • 講解struct resource資源指標結構體
    路徑:ioport.h linux-3.5\include\linux
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};

引數:
start:資源起始值
end:資源結束值
name:資源名字,隨便起
flags:資源型別
parent, sibling, child:驅動開發者不需要關注,核心使用到

start end值含義與flags值有關,flags用來描述資源型別,其可取值型別內*核定義如下:

#define IORESOURCE_IO       0x00000100  //資源是是IO口型別
#define IORESOURCE_MEM      0x00000200  //資源是實體記憶體
#define IORESOURCE_IRQ      0x00000400  //資源是中斷編號
#define IORESOURCE_DMA      0x00000800  //資源師DMA
#define IORESOURCE_BUS      0x00001000  //資源是匯流排編號

常用的是IORESOURCE_MEMIORESOURCE_IRQ
補充:IORESOURCE_IO不是指aem晶片的GPIO引腳而是指具有獨立記憶體空間編址的GPIO記憶體地址
這裡寫圖片描述
類似上面圖片這種情況左邊的P0-P3口占用的記憶體空間才屬於IO空間,IO資源一般情況在x86架構晶片上才會有
ARM晶片GPIO口占用的空間是屬於右邊這部分空間,也就是4G主存中的一部分,所以ARM晶片的GPIO佔用的資源是記憶體資源,不是IO資源

  • 講解struct device結構體
    linuxn核心使用這個結構體來描述一個裝置資訊,他是linux裝置模型的基礎
    路徑:device.h \linux-3.5\include\linux
struct device {
    void        *platform_data; /* Platform specific data, device
                       core doesn't touch it */
                       //平臺數據  任意型別
    dev_t           devt;   /* dev_t, creates the sysfs "dev" *///裝置號
    ···········//中間內容不關心
    void    (*release)(struct device *dev);//指向解除安裝模組時執行的函式指標
};

平臺裝置層API函式

platform_device_register

原型:
int platform_device_register(struct platform_device *pdev);
功能:向核心註冊一個平臺裝置
引數:pdev要註冊的平臺裝置結構指標
返回值:0 成功 負數失敗

platform_device_unregister

原型:
void platform_device_unregister(struct platform_device *pdev);
功能:從核心中登出一個平臺裝置
引數:pdev要登出的平臺裝置結構指標
返回值:

platform_add_devices

原型:
int platform_add_devices(struct platform_device **devs, int num)
功能:向核心一次註冊多個平臺裝置
引數:devs要註冊的平臺裝置陣列,num需要註冊平臺裝置的個數
返回值:0 成功 負數 失敗

平臺裝置編寫步驟

  • 先是編寫一個模組的模板程式碼
  • 在模組檔案中定義一個struct platform_device結構變數
  • 初始化上一步定義的struct platform_device結構變數必要的元素,一般在定義時就初始化了
    初始化的核心分析清楚裝置佔用的資源:分析硬體原理圖
    定義裝置佔用的資源陣列
    初始化裝置結構的resourcenum_resource
    根據需要決定是否需要傳遞平臺數據:dev.platform_data成員
  • 在模組載入時呼叫platform_device_register函式註冊已初始化好的機構變數
  • 在模組解除安裝時使用platform_device_unregister函式登出註冊的平臺裝置

編寫程式碼

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

#include<linux/types.h>
#include<linux/interrupt.h>
#include<linux/platform_device.h>

//定義leds裝置佔用的資源
struct resource myleds_res[]={
    [0]={
        .start=0x110002e0,
        .end=0x110002f7,
        .flags=IORESOURCE_MEM,
        .name="GPM4",
    }
};


//定義一個平臺裝置變數
struct platform_device myleds_dev={
    /*
    //原理圖上4個led GPM4^0-4,所以使用GPM4相關暫存器
    //起始暫存器地址0x1100 02E0 結束暫存器地址0x1100 02F4
    //但是最後一個暫存器佔用4位元組地址
    //總的佔用記憶體空間大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
    */
    .name         ="myleds",
    .id           =-1,
    .num_resources = ARRAY_SIZE(myleds_res),
    .resource      = myleds_res,

};
static int __init ldedrv_init(void)
{
    platform_device_register(&myleds_dev);
    printk("%s is call\r\n",__FUNCTION__);
    return 0;
}

static void __exit leddrv_exit(void)
{  
    platform_device_unregister(&myleds_dev);
    printk("%s is call\r\n",__FUNCTION__);
}
module_init(ldedrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");

平臺驅動層程式設計

驅動層核心資料結構

路徑:platform_device.h linux-3.5\include\linux

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 (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};

重要成員:
probe:指向探測函式的函式指標,當裝置和驅動匹配時,其指向的函式會被自動執行
remove:指向裝置移除函式的函式指標,當解除安裝已經匹配好的裝置和驅動中的任意一個,都會執行該函式,簡單理解:功能與probe相反
比如:在probe函式中動態申請了一塊記憶體,則在remove函式中必須釋放所申請的記憶體,在probe函式中註冊一個雜項裝置,則必須在remove中登出一個雜項裝置
以上兩個必須實現
device_driver:裝置結構體,這個是核心用來描述一個裝置對應的驅動程式的抽象結構。
id_table:可以用來和裝置層進行匹配,如果實現了這個成員,則不會使用.name進行匹配
其他:suspend resume是關於裝置電源管理方面的介面,這兩個介面是相反的。
補充

struct platform_device_id {
    char name[PLATFORM_NAME_SIZE];
    kernel_ulong_t driver_data
            __attribute__((aligned(sizeof(kernel_ulong_t))));
};

當需要實現一個驅動可以實現多個裝置時候可以使用這種方式實現
具體操作是定義一個數組,然後實現其中name成員。所有在這個陣列中的name成員相同的時候,則匹配上,呼叫驅動程式中的probe函式
補充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;  //高版本核心平臺裝置可以使用裝置樹方式實現裝置層,該成員就是當使用裝置樹方式實現時可以使用這個成員進行匹配

    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;
};
`

一般驅動只需要實現name成員即可

平臺驅動層API函式

  • platform_driver_register
    路徑:platform_device.h linux-3.5\include\linux
    原型:int platform_driver_register(struct platform_driver *drv)
    功能:向核心註冊一個平臺驅動。如果此時有匹配平臺裝置,會觸發驅動結構中的probe函式
    引數:drv要註冊的平臺驅動結構
    返回值:0 註冊成功,負數 註冊失敗
  • platform_driver_unregister
    路徑:platform_device.h linux-3.5\include\linux
    原型:void platform_driver_unregister(struct platform_driver *drv)
    功能:從核心登出一個平臺驅動。如果此時已有匹配平臺裝置,會觸發驅動結構中的remove函式
    引數:drv要註冊的平臺驅動結構
    返回值:

通常一個裝置包含有很多資源例如:IO口、 中斷編號、記憶體空間佔用等等,下面幾個是怎樣方便的獲取這些特定型別的資源API函式,可以方便的尋找指定型別的資源

  • platform_get_resource
    路徑:platform_device.h linux-3.5\include\linux
    原型:
struct resource *platform_get_resource(struct platform_device *dev,
                       unsigned int type, unsigned int num)

功能:獲取裝置層平臺裝置結構中資源結構體指定的陣列成員的結構,該函式在驅動層中使用
引數:
dev:平臺裝置結構體
type:資源結構體陣列成員中資源型別
num:資源結構體陣列中第幾個type型別的成員,同資源型別的第幾個成員,下標與陣列下標不相同
返回值:獲取到的資源結構體陣列中的指定成員結構

  • platform_get_resource_byname
    路徑:platform_device.h linux-3.5\include\linux
    原型:
struct resource *platform_get_resource_byname(struct platform_device *dev,
                          unsigned int type,
                          const char *name)

功能:通過名字獲取裝置結構中資源結構體中指定型別資源結構
引數:
dev:平臺裝置結構體
type:資源結構體陣列成員中資源型別
name:資源結構體陣列中名稱為name的type型別的成員
返回值:獲取到的資源結構體陣列中的指定成員結構

  • platform_get_irq
    路徑:platform_device.h linux-3.5\include\linux
    原型:int platform_get_irq(struct platform_device *dev, unsigned int num)
    功能:通過裝置指標,獲得指定編號中斷的起始編號
    引數:
    dev:平臺裝置結構指標
    num:中斷資源編號,與陣列小標不相同,
    返回值:>0 中斷資源中的其實編號;-ENXIO:失敗
    函式具體還是呼叫的platform_get_resource函式
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
    struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);

    return r ? r->start : -ENXIO;
}
  • platform_get_irq_byname
    路徑:platform_device.h linux-3.5\include\linux
    原型:int platform_get_irq_byname(struct platform_device *dev, const char *name)
    功能:通過裝置指標,獲得指定名字中斷的起始編號
    引數:
    dev:平臺裝置結構指標
    name:中斷資源名稱,
    返回值:>0 中斷資源中的其實編號;-ENXIO:失敗
    函式具體還是呼叫的platform_get_resource函式
int platform_get_irq_byname(struct platform_device *dev, const char *name)
{
    struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ,
                              name);

    return r ? r->start : -ENXIO;
}

第三類API

  • request_region
    路徑:ioport.h linux-3.5\include\linux
    原型:
#define request_region(start,n,name)        __request_region(&ioport_resource, (start), (n), (name), 0)

功能:向核心申請一段IO埠空間(IORESOURCE_IO型別)(x86架構)
引數:
start:起始地址
n:連續大小
name:使用者名字,用於核心登記
返回值:NULL 申請成功資源結構記憶體地址struct resource*
NULL:所申請的IO資源已被佔用申請失敗

  • request_mem_region
    路徑:ioport.h linux-3.5\include\linux
    原型:
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

功能:向核心申請一段IO記憶體(IORESOURCE_MEM型別)(ARM架構)
引數:
start:起始地址
n:連續大小
name:使用者名字,用於核心登記
返回值:NULL 申請成功資源結構記憶體地址struct resource*
NULL:所申請的IO資源已被佔用申請失敗

  • release_region
    路徑:ioport.h linux-3.5\include\linux
    原型:
#define release_region(start,n) __release_region(&ioport_resource, (start), (n))

功能:釋放一段request_region申請的IO埠空間(IORESOURCE_IO型別)(x86架構)
引數:
start:起始地址
n:連續大小
返回值:

  • release_mem_region
    路徑:ioport.h linux-3.5\include\linux
    原型:
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))

功能:釋放一段release_mem_region申請的IO記憶體空間(IORESOURCE_MEM型別)(ARM架構)
引數:
start:起始地址
n:連續大小
返回值:

特別注意
- 高版本中的相同功能巨集,安全性較高,只是多了一個引數指定了裝置指標中的device結構指標不是平臺裝置結構指標而是平臺裝置結構中的device結構指標其餘用法相同

#define devm_request_region(dev,start,n,name) \
    __devm_request_region(dev, &ioport_resource, (start), (n), (name))
#define devm_request_mem_region(dev,start,n,name) \
    __devm_request_region(dev, &iomem_resource, (start), (n), (name))

extern struct resource * __devm_request_region(struct device *dev,
                struct resource *parent, resource_size_t start,
                resource_size_t n, const char *name);

#define devm_release_region(dev, start, n) \
    __devm_release_region(dev, &ioport_resource, (start), (n))
#define devm_release_mem_region(dev, start, n) \
    __devm_release_region(dev, &iomem_resource, (start), (n))

驅動層編寫

目標:把以前寫的雜項led驅動修改為平臺裝置驅動模型

程式設計思想

  • 先編寫一個模組基本模板
  • 定義一個平臺驅動結構變數
  • 初始化上一步定義的平臺結構變數
  • 在模組載入函式中用平臺驅動註冊函式註冊上一步初始化好的平臺驅動結構變數
  • 在模組解除安裝函式呼叫平臺驅動登出函式登出平臺驅動

核心:

  • 裝置名:.driver.name:保持和裝置層中相同即可
  • id:如果一對一就賦值為-1
  • probe函式:
    探測平臺裝置資源
    申請使用平臺裝置資源
    使用申請到的資源:記憶體資源-對映成虛擬地址使用,中斷資源-註冊中斷
    註冊字元裝置(雜項裝置):以前應該怎麼寫還怎麼寫

  • remove函式:功能與probe函式相反,所做的事情就是釋放在probe函式中佔用的資源

整體程式碼

device程式碼

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>

#include<linux/types.h>
#include<linux/interrupt.h>
#include<linux/platform_device.h>

//定義leds裝置佔用的資源
struct resource myleds_res[]={
    [0]={
        .start   =0x110002e0,
        .end     =0x110002f7,
        .flags   =IORESOURCE_MEM,
        .name    ="GPM4",
    }
};

void myleds_release(void)
{

}
//定義一個平臺裝置變數
struct platform_device myleds_dev={
    //原理圖上4個led GPM4^0-4,所以使用GPM4相關暫存器
    //起始暫存器地址0x1100 02E0 結束暫存器地址0x1100 02F4
    //但是最後一個暫存器佔用4位元組地址
    //總的佔用記憶體空間大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
    .name         ="myleds",
    .id           =-1,
    .num_resources = ARRAY_SIZE(myleds_res),
    .resource      = myleds_res,
    .dev={
    .release=myleds_release,
    }
};
static int __init ldedrv_init(void)
{
    platform_device_register(&myleds_dev);
    printk("%s is call\r\n",__FUNCTION__);
    return 0;
}

static void __exit leddrv_exit(void)
{  
    platform_device_unregister(&myleds_dev);
    printk("%s is call\r\n",__FUNCTION__);
}
module_init(ldedrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");

driver程式碼

#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>//新增標頭檔案
#include<linux/ioctl.h>

#include<linux/types.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<asm/io.h>
#include<linux/platform_device.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>

//定義資源指標
static struct resource  *res;

unsigned long *base_addr=NULL;

#define GPM4CON (*(volatile unsigned long *)(base_addr+0)) 
#define GPM4DAT (*(volatile unsigned long *)(base_addr+1))

//開啟函式
static int  misc_open(struct inode *node, struct file *fp)
{   
    GPM4DAT &= ~0XF;
    printk("all leds is light\r\n");
    return 0;
}
//關閉函式
static int misc_close(struct inode *node, struct file *fp)
{
    printk("this dev is close\r\n");
    return 0;
}

//讀函式
ssize_t misc_read(struct file *fp, char __user *buf, size_t size, loff_t *loff)
{
   printk("this dev is read\r\n");
   return 0;
}

//寫函式
ssize_t misc_write(struct file *fp, const char __user *buf, size_t size, loff_t *loff)
{
  printk("this dev is write\r\n");
  return 0;
}


//操作函式
struct file_operations fops={
  .owner=THIS_MODULE,
  .open=misc_open,
  .read=misc_read,
  .write=misc_write,
  .release=misc_close,
};

//雜項裝置結構體
struct miscdevice mymisc={
  .minor=255,
  .name="my_leds",
  .fops=&fops,
};

//探測函式
static int led_probe(struct platform_device *pdev)
{
    int irq;
    int size;
    int ret;

    //指向裝置層中的 &my_leds[0]
    res=platform_get_resource(pdev,IORESOURCE_MEM,0);
    printk("res->name:%s\r\n",res->name);
    printk("res->start:0x%x,res->end:0x%x",res->start,res->end);

    //向核心申請資源
    size=res->end - res->start + 1;
    res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");

    if(!res)
        {
        printk("devm_request_mem_region error\r\n");
        //註冊失敗釋放資源
        size=res->end - res->start + 1;
        //這裡應特別注意第一個引數為平臺裝置結構中的device結構指標
        devm_release_mem_region(&pdev->dev, res->start, size);
        return -EBUSY;
    }
    printk("devm_request_mem_region ok\r\n");

    //註冊核心結構
    if(misc_register(&mymisc))
      { 
        printk("misc_register is insmod fail\r\n");
        return  -1;
      }
      printk("misc_register is success\r\n");
    base_addr   = ioremap(res->start,size);
    GPM4CON &= ~0XFFFF;
    GPM4CON |=  0x1111;
    GPM4DAT |= 0XF;
    return 0;
}

static int led_remove(struct platform_device *pdev)
{
    int size;
    int ret;
    //登出核心結構
    misc_deregister(&mymisc);
    res=platform_get_resource(pdev,IORESOURCE_MEM,0);
    size=res->end - res->start + 1;
    devm_release_mem_region(&pdev->dev, res->start, size);
    iounmap(base_addr);
}

//平臺裝置驅動結構
struct platform_driver led_driver={
    .probe=led_probe,
    .remove=led_remove,
    .driver={
    .owner=THIS_MODULE,
    .name="myleds",
    },
};

/* 2、編寫驅動入口出口函式 */
static __init int leddrv_init(void)
{
    /* 平臺設備註冊 */
    platform_driver_register(&led_driver);  
    printk("leddrv_init...!!\n");
    return 0;
}
static __exit void leddrv_exit(void)
{
    platform_driver_unregister(&led_driver);
}


/* 1、指定驅動入口出口函式 */
module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");

app程式碼

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[])
{
  unsigned char LED_STATUE,shi;
  int ge;

  int led_fp;

  led_fp = open(argv[1],O_RDWR);

  close(led_fp);
}

Makefile

KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
obj-m += platform_driver.o 
obj-m += platform_device.o

開發板執行效果

[root@ZC/zhangchao]#insmod platform_device.ko 
[ 1208.980000] ldedrv_init is call
[root@ZC/zhangchao]#insmod platform_driver.ko 
[ 1222.025000] res->name:GPM4
[ 1222.025000] res->start:0x110002e0,res->end:0x110002f7devm_request_mem_region ok
[ 1222.035000] misc_register is success
[ 1222.040000] leddrv_init...!!
[root@ZC/zhangchao]#./app /dev/my_leds 
[ 1232.870000] all leds is light
[ 1232.870000] this dev is close

對平臺匯流排驅動模型的特點的理解

  • 平臺模型採用了分層結構,把一個裝置驅動分為了兩個部分:平臺裝置(platform_device)和平臺驅動(platform_driver)
  • 平臺裝置將裝置本身的資源(佔用哪些中斷號,記憶體資源,IO口等)註冊進核心,可以由核心統一管理,驅動更加安全、可靠。
    驅動層probe函式使用資源前必須先申請:
    res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");

    res= request_mem_region(res->start, size, "myleds");
    如果前面驅動對資源已經佔用,則本驅動佔用失敗.如果一個資源申請後沒有釋放,則重複申請會失敗,這樣保證資源的安全性,系統安全性
  • 統一了裝置驅動模型,使智慧電源管理更容易實現
    如果你想你的裝置可以休眠,可以實現下面兩個成員結構
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 (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};

這是舊版本的電源管理結構,新版本的電源管理在.driver.pm結構中 const struct dev_pm_ops *pm;

  • 從程式碼維護角度看,平臺模型的可移植性,通用性更好
    一般只需要修改裝置驅動模組中的裝置資訊和資源資訊,驅動層不做修改
    真正優秀的平臺驅動程式碼可以做到,只要CPU型號相同,一個驅動程式可以適應任何型號具體的開發板,對不同型號的開發板,只需要修改平臺裝置層的資源及平臺數據即可。當然這裡舉的例子並不合適,將雜項裝置的led驅動修改為平臺模型反而更復雜。
    上面程式碼存在的問題:
    1 驅動固定寫死4led,並且是連續的,只能從第0個開始
    2 如果要實現真正一個通用性效果就要對上面程式碼進行改進,使用平臺數據進行改進
    3 目標:實現一個平臺驅動層程式碼可以任意數量的led,任意IO位置的效果
  • 平臺裝置並不是一種新的裝置,linux目前只有三種裝置型別:字元裝置、塊裝置、網路裝置
  • 平臺裝置是按匯流排進行劃分的,所以平臺裝置不是一種新裝置,也不是特定字元裝置或塊裝置,它可以是三種裝置之一

平臺驅動程式碼優化

平臺數據的使用

目標:只要同一組IO口,平臺驅動層程式碼可以適應任意數量,任意IO口。對不同的情況只需要修改一下平臺裝置層程式碼即可
分析:要實現以上功能平臺裝置層必須傳遞以下兩個資訊給平臺驅動層
1 平臺裝置層要傳遞的led燈數量
2 每一個led所在本組第幾個IO口上

其實我們知道,裝置結構中的所有資料,在驅動中probe函式都可以從本身的引數拿到,這樣的話有沒有我們可以自定義的資料可以進行資料傳遞呢?有!這個資料就在
struct platform_device 結構中struct device結構成員中的void *platform_data成員,由於是void*型別可以傳遞任意型別的資料。
.platform_device.dev.platform_data

設計思想:

  • 把每一個燈看組一個物件:必須資訊是在哪個編號上。(如果不在一組上還需直到在哪一個組)
  • 總共有幾個燈
    可以把上面內容設計一個結構體:然後在一個頭檔案中宣告,裝置模組和驅動模組都包含這個標頭檔案。

在驅動模組中定義

公共標頭檔案的內容

//定義單燈資訊結構
struct led_info{
    int pin_nr;//本組第幾個IO口
};
//定義總結構
struct leds_data{
    struct led_info *leds_info;
    int leds_num;
};

裝置模組中定義的內容

//定義開發板每個led資訊
struct led_info myled_info[]={
    [0]={
    .pin_nr=0,
    },

    [1]={
    .pin_nr=1,
    },

    [2]={
    .pin_nr=2,
    },

    [3]={
    .pin_nr=3,
    },
};
//定義leds裝置佔用的資源
struct resource myleds_res[]={
    [0]={
        .start   =0x110002e0,
        .end     =0x110002f7,
        .flags   =IORESOURCE_MEM,
        .name    ="GPM4",
    }
};
struct platform_device myleds_dev={
    //原理圖上4個led GPM4^0-4,所以使用GPM4相關暫存器
    //起始暫存器地址0x1100 02E0 結束暫存器地址0x1100 02F4
    //但是最後一個暫存器佔用4位元組地址
    //總的佔用記憶體空間大小0x1100 02F4 + 4 - 1 -0x1100 02E0 +1=0x18
    .name         ="myleds",
    .id           =-1,
    .num_resources = ARRAY_SIZE(myleds_res),
    .resource      = myleds_res,
    .dev={
    .release=myleds_release,
    .platform_data=&myleds_data,
    }
};

在驅動模組中獲取傳遞下來的資料,根據資料初始化相應的暫存器

//探測函式
struct leds_data *pdata_leds;
static int led_probe(struct platform_device *pdev)
{
    int irq;
    int size;
    int ret;
    int i;
    //定義資源指標
    static struct resource  *res;
//////////////////////////////////////////////////////////////////////
    //取得平臺裝置傳下來的資料結構
    pdata_leds=(struct leds_data*)pdev->dev.platform_data;
    base_addr   = ioremap(res->start,size);
    //根據傳下來的資料進行初始化對應的IO
    for(i=0;i<pdata_leds->leds_num,i++){
        GPM4CON &= ~(0XF<<pdata_leds->leds_info[i].pin_nr);
        GPM4CON |=  (0X1<<pdata_leds->leds_info[i].pin_nr);
        GPM4DAT |=  (0X1<<pdata_leds->leds_info[i].pin_nr);
    }
////////////////////////////////////////////////////////////////////
    //指向裝置層中的 &my_leds[0]
    res=platform_get_resource(pdev,IORESOURCE_MEM,0);
    printk("res->name:%s\r\n",res->name);
    printk("res->start:0x%x,res->end:0x%x",res->start,res->end);

    //向核心申請資源
    size=res->end - res->start + 1;
    res= devm_request_mem_region(&pdev->dev,res->start, size, "myleds");

    if(!res)
        {
        printk("devm_request_mem_region error\r\n");
        //註冊失敗釋放資源
        size=res->end - res->start + 1;
        //這裡應特別注意第一個引數為平臺裝置結構中的device結構指標
        devm_release_mem_region(&pdev->dev, res->start, size);
        return -EBUSY;
    }
    printk("devm_request_mem_region ok\r\n");

    //註冊核心結構
    if(misc_register(&mymisc))
      { 
        printk("misc_register is insmod fail\r\n");
        return  -1;
      }
      printk("misc_register is success\r\n");

    return 0;
}

根據燈的IO編號可以初始化的相應IO的暫存器,如果燈在IO口上不是連續的,只需要修改每個燈對應led_info.pin_nr引腳值即可。例如以下

struct led_info myled_info[]={
    [0]={
    .pin_nr=0,
    },

    [1]={
    .pin_nr=3,
    },

    [2]={
    .pin_nr=5,
    },

    [3]={
    .pin_nr=7,
    },
};

獲取指定燈的狀態,或者設定指定燈的狀態都可以通過platform_data傳下的資料進行設定。