Linux Platform驅動模型(二) _驅動方法

来源:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/06/6367910.html
-Advertisement-
Play Games

在 "Linux設備樹語法詳解" 和 "Linux Platform驅動模型(一) _設備信息" 中我們討論了設備信息的寫法,本文主要討論平臺匯流排中另外一部分 驅動方法,將試圖回答下麵幾個問題: 1. 如何填充platform_driver對象? 2. 如何將驅動方法對象註冊到平臺匯流排中? 正文前的 ...


Linux設備樹語法詳解Linux Platform驅動模型(一) _設備信息中我們討論了設備信息的寫法,本文主要討論平臺匯流排中另外一部分-驅動方法,將試圖回答下麵幾個問題:

  1. 如何填充platform_driver對象?
  2. 如何將驅動方法對象註冊到平臺匯流排中?

正文前的一點羅嗦

寫驅動也有一段時間了,可以發現,其實驅動本質上只做了兩件事:向上提供介面,向下控制硬體,當然,這裡的向上並不是直接提供介面到應用層,而是提供介面給內核再由內核間接的將我們的介面提供給應用層。而寫驅動也是有一些套路可尋的,拿到一個硬體,我們大體可以按照下麵的流程寫一個驅動:

  1. 確定驅動架構:根據硬體連接方式結合分層/分離思想設計驅動的基本結構
  2. 確定驅動對象:內核中的一個驅動/設備就是一個對象,1.定義,2.初始化,3.註冊,4.註銷
  3. 向上提供介面:根據業務需要確定提供cdev/proc/sysfs哪種介面
  4. 向下控制硬體:1.查看原理圖確定引腳和控制邏輯,2.查看晶元手冊確定寄存器配置方式,3.進行記憶體映射,4.實現控制邏輯

認識驅動方法對象

內核用platform_driver結構來表示一個驅動方法對象

//include/linux/device.h
173 struct platform_driver {                 
174         int (*probe)(struct platform_device *);
175         int (*remove)(struct platform_device *);
176         void (*shutdown)(struct platform_device *);
177         int (*suspend)(struct platform_device *, pm_message_t state);
178         int (*resume)(struct platform_device *);
179         struct device_driver driver;
180         const struct platform_device_id *id_table;
181         bool prevent_deferred_probe;
182 };

在這個結構中,我們主要關心以下幾個成員

struct platform_driver
--174-->探測函數,如果驅動匹配到了目標設備,匯流排會自動回調probe函數,必須實現,下麵詳細討論
--175-->釋放函數,如果匹配到的設備從匯流排移除了,匯流排會自動回調remove函數,必須實現
--179-->platform_driver的父類,我們接下來討論
--180-->用於C語言寫的設備信息,下麵詳細討論

platform_driver裡面有些內容需要在父類driver中實現,

 //include/linux/device.h
 228 struct device_driver {     
 229         const char              *name;
 230         struct bus_type         *bus;
 231        
 232         struct module           *owner;
 233         const char              *mod_name;      /* used for built-in modules */
 234 
 235         bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */
 236        
 237         const struct of_device_id       *of_match_table;
 238         const struct acpi_device_id     *acpi_match_table;
 239 
 240         int (*probe) (struct device *dev);
 241         int (*remove) (struct device *dev);
 242         void (*shutdown) (struct device *dev);
 243         int (*suspend) (struct device *dev, pm_message_t state);
 244         int (*resume) (struct device *dev);
 245         const struct attribute_group **groups;
 246 
 247         const struct dev_pm_ops *pm;
 248   
 249         struct driver_private *p;
 250 };

下麵是我們關心的幾個成員

struct device_driver
--229-->驅動名,如果這個驅動只匹配一個C語言的設備,那麼可以通過name相同來匹配
--230-->匯流排類型,這個成員由內核填充
--232-->owner,通常就寫THIS_MODULE
--237-->of_device_id顧名思義就是用來匹配用設備樹寫的設備信息,下麵詳細討論
--249-->私有數據

driver與device的匹配

設備信息有三種表達方式,而一個驅動是可以匹配多個設備的,平臺匯流排中的驅動要具有三種匹配信息的能力,基於這種需求,platform_driver中使用不同的成員來進行相應的匹配。

of_match_table

對於使用設備樹編碼的設備信息,我們使用其父類device_driver中的of_match_table就是用來匹配

//include/linux/mod_devicetable.h
220 /*
221  * Struct used for matching a device
222  */
223 struct of_device_id                                      
224 {
225         char    name[32];
226         char    type[32];
227         char    compatible[128];
228         const void *data;
229 };

struct of_device_id
--225-->name[32]設備名
--226-->type[32]設備類型
--227-->重點!compatible[128]用於與設備樹compatible屬性值匹配的字元串
--228-->data驅動私有數據

對於一個驅動匹配多個設備的情況,我們使用struct of_device_id tbl[]來表示。

struct of_device_id of_tbl[] = {
    {.compatible = "xj4412,demo0",},
    {.compatible = "xj4412,demo1",},
    {},
};

id_table

對於使用C語言編碼的設備信息,我們用platform_driver對象中的id_table就是用來匹配。我們使用struct platform_device_id ids[]來實現一個驅動匹配多個C語言編碼的設備信息。

//include/linux/mod_deviceid.h
485 struct platform_device_id {
486         char name[PLATFORM_NAME_SIZE];
487         kernel_ulong_t driver_data;
488 }; 

struct platform_device_id
--486-->name就是設備名

下麵這個例子就是用一個驅動來匹配兩個分別叫"demo0"和"demo1"的設備,註意,數組最後的{}是一定要的,這個是內核判斷數組已經結束的標誌。

static struct platform_device_id tbl[] = {
    {"demo0"},
    {"demo1"},
    {},
};

name

如果platform_driver和C語言編碼的platform_device是一一匹配的,我們還可以使用device_driver中的name來進行匹配

註冊設備表

填充完platform_driver結構之後,我們應該將其中用到的設備表註冊到內核,雖然不註冊也可以工作,但是註冊可以將我們表加入到相關文件中,便於內核管理設備。

MODULE_DEVICE_TABLE(類型, ID表);
設備樹ID表
類型:of
C寫的platform_device的ID表
類型:platform
C寫的i2c設備的ID表
類型:i2c
C寫的USB設備的ID表
類型:usb

匹配小結

細心的讀者可能會發現,這麼多方式都寫在一個對象中,那如果我同時註冊了三種匹配結構內核該用哪種呢?此時就需要我們搬出平臺匯流排的匹配方式:

//drivers/base/platform.c
 748 static int platform_match(struct device *dev, struct device_driver *drv)  
 749 {
 750         struct platform_device *pdev = to_platform_device(dev);
 751         struct platform_driver *pdrv = to_platform_driver(drv);
 752 
 753         /* Attempt an OF style match first */
 754         if (of_driver_match_device(dev, drv))
 755                 return 1;
 756 
 757         /* Then try ACPI style match */
 758         if (acpi_driver_match_device(dev, drv))
 759                 return 1;
 760 
 761         /* Then try to match against the id table */
 762         if (pdrv->id_table)
 763                 return platform_match_id(pdrv->id_table, pdev) != NULL;
 764 
 765         /* fall-back to driver name match */
 766         return (strcmp(pdev->name, drv->name) == 0);
 767 }

從中不難看出,這幾中形式的匹配是有優先順序的:of_match_table>id_table>name,瞭解到這點,我們甚至可以構造出同時適應兩種設備信息的平臺驅動:

static struct platform_driver drv = {
    .probe  = demo_probe,
    .remove = demo_remove,

    .driver = {
        .name = "demo",
#ifdef CONFIG_OF
        .of_match_table = of_tbl,
#endif
    },

    .id_table = tbl,
};

此外,如果你追一下of_driver_match_device(),就會發現平臺匯流排的最終的匹配是compatible,name,type三個成員,其中一個為NULL或""時表示任意,所以我們使用平臺匯流排時總是使用compatile匹配設備樹,而不是節點路徑或節點名

probe()

probe即探測函數,如果驅動匹配到了目標設備,匯流排會自動回調probe函數,下麵詳細討論。並把匹配到的設備信息封裝策劃嗯platform_device對象傳入,裡面主要完成下麵三個工作

  1. 申請資源
  2. 初始化
  3. 提供介面(cdev/sysfs/proc)

顯然,remove主要完成與probe相反的操作,這兩個介面都是我們必須實現的。

在probe的工作中,最常見的就是提取設備信息,雖然匯流排會將設備信息封裝成一個platform_device對象並傳入probe函數,我們可以很容易的得到關於這個設備的所有信息,但是更好的方法就是直接使用內核API中相關的函數

/**
 * platform_get_resource - 獲取資源
 * @dev: 平臺匯流排設備
 * @type:資源類型,include/linux/ioport.h中有定義
 * @num: 資源索引,即第幾個此類型的資源,從0開始
 */
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
/**
 * platform_get_irq - 獲取一個設備的中斷號
 * @dev: 平臺匯流排設備
 * @num: 中斷號索引,即想要獲取的第幾個中斷號,從0開始
 */
int platform_get_irq(struct platform_device *dev, unsigned int num)
/**
 * dev_get_platdata - 獲取私有數據
 */
static inline void *dev_get_platdata(const struct device *dev){       
        return dev->platform_data;
}

註冊/註銷platform_driver對象

內核提供了兩個API來註冊/註銷platform_driver對象到內核

/**
 * platform_driver_register - 註冊
 */
int platform_driver_register(struct platform_driver *drv);
/**
 * platform_driver_unregister - 註銷
 */
int platform_driver_unregister(struct platform_driver *drv);

在動態編譯的情況下,我們往往在模塊初始化函數中註冊一個驅動方法對象,而在模塊卸載函數中註銷一個驅動方法對象,所以我們可以寫一個如下的巨集來提高代碼復用

#define module_platform_device(xxx)             \
static int __init xxx##_init(void)          \
{                           \
    return platform_device_register(&xxx);      \
}                           \
static void __exit xxx##_exit(void)         \
{                           \
    platform_device_unregister(&xxx);       \
}                           \
module_init(xxx##_init);                \
module_exit(xxx##_exit);

#endif

實例

這個實例同時使用了設備信息模塊和設備樹兩種設備信息來源,不過最終使用的是設備樹,需要註意的是,當我們用設備樹的設備信息時,有一個成員platform_device.device.of_node來表示設備的節點,這樣就允許我們使用豐富的設備樹操作API來操作。

//#include "private.h"
/*
/{
    demo{
        compatible = "4412,demo0";
        reg = <0x5000000 0x2 0x5000008 0x2>;
        interrupt-parent = <&gic>;
        interrupts = <0 25 0>, <0 26 0>;
        intpriv = <0x12345678>;
        strpriv = "hello world";
    };
};
*/

struct privatedata {
    int val;
    char str[36];
};
static void getprivdata(struct device_node *np)
{
    struct property *prop;
    prop = of_find_property(np, "intpriv", NULL);
    if(prop)
        printk("private val: %x\n", *((int *)(prop->value)));

    prop = of_find_property(np, "strpriv", NULL);
    if(prop)
        printk("private str: %s\n", (char *)(prop->value) );
}

static int demo_probe(struct platform_device *pdev)
{
    int irq;
    struct resource *addr;
    struct privatedata *priv;
    
    printk(KERN_INFO "%s : %s : %d - entry.\n", __FILE__, __func__, __LINE__);

    priv = dev_get_platdata(&pdev->dev);
    if(priv){
        printk(KERN_INFO "%x : %s \n", priv->val, priv->str);       
    }else{
        getprivdata(pdev->dev.of_node);
    }

    addr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(addr){
        printk(KERN_INFO "0: %x : %d \n", addr->start, resource_size(addr));        
    }
    addr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if(addr){
        printk(KERN_INFO "1: %x : %d \n", addr->start, resource_size(addr));        
    }
    addr = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    if(!addr){
        printk(KERN_INFO "No 2 resource\n");        
    }

    irq = platform_get_irq(pdev, 0);
    if(0 > irq){
        return irq;
    }else{
        printk(KERN_INFO "irq 0: %d \n", irq);      
    }
    irq = platform_get_irq(pdev, 1);
    if(0 > irq){
        return irq;
    }else{
        printk(KERN_INFO "irq 0: %d \n", irq);      
    }

    irq = platform_get_irq(pdev, 2);
    if(0 > irq){
            printk(KERN_INFO "No 2 irq\n");     
    }

    return 0;
}

static int demo_remove(struct platform_device *pdev)
{
    return 0;
}

static struct platform_device_id tbl[] = {
    {"demo0"},
    {"demo1"},
    {},
};
MODULE_DEVICE_TABLE(platform, tbl);

#ifdef CONFIG_OF
struct of_device_id of_tbl[] = {
    {.compatible = "4412,demo0",},
    {.compatible = "4412,demo1",},
    {},
};
#endif

//1. alloc obj
static struct platform_driver drv = {
    .probe  = demo_probe,
    .remove = demo_remove,

    .driver = {
        .name = "demo",
#ifdef CONFIG_OF
        .of_match_table = of_tbl,
#endif
    },

    .id_table = tbl,
};


static int __init drv_init(void)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - entry.\n",current->comm, current->pid, __FILE__, __func__, __LINE__);
    return platform_driver_register(&drv);
}
static void __exit drv_exit(void)
{
    //get command and pid
    printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",current->comm, current->pid, __FILE__, __func__, __LINE__);

    platform_driver_unregister(&drv);
}
module_init(drv_init);
module_exit(drv_exit);

MODULE_LICENSE("GPL");




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 根據功能模塊劃分(Android開發推薦此方法) - Activity mobilesafe.activty - 後臺服務 mobilesafe.service - 廣播接受者 mobilesafe.receiver - 資料庫 mobilesafe.db.dao - 對象(java bean) m ...
  • 在google play上發佈apk,當上傳了apk文件,填寫了相關的內容信息和介紹圖片、圖標後,出現“發佈應用”始終灰色無法點擊,查看原因顯示如下問題: 其中支持的設備數量始終顯示為0,懷疑是編譯出來的apk哪裡不規範! 經過對原有工程代碼的查找修改,去除了mips和x86兩個jniLibs下的鏈 ...
  • 學習視頻之後自己操作時的筆記。 0.視頻地址:http://www.imooc.com/video/3265 1.功能預覽: 說明:1)輸入錯誤用戶名和密碼,點擊登錄,彈出提示框“禁止登錄”; 2)輸入正確用戶名和密碼,點擊登錄,彈出提示框“登錄成功”; 3)輸入正確用戶名和密碼,並且勾選保存用戶名 ...
  • 沒有蘋果電腦打包iOS平臺的 Ionic 2程式——《Ionic 2 實例開發》更新內容春節剛過,祝各位新的一年裡萬事如意,一帆風順。《Ionic 2 實例開發》在這段時間里更新瞭如下內容:Ionic 2 中使用管道處理數據Ionic 2 中使用HTTP與遠程伺服器交互數據Ionic 2 中的樣式與 ...
  • 0.視頻地址:http://www.imooc.com/video/3265 1.使用SharePreferences存取數據: public class MainActivity extends Activity { @Override protected void onCreate(Bundle ...
  • 作者:Antonio Leiva 時間:Jan 25, 2017 原文鏈接:https://antonioleiva.com/data-classes-kotlin/ 在前面的文章中,我們已經見到了類,而數據類還可以進一步幫助我們簡化代碼。 數據類是什麼? 數據類是僅僅包含狀態而沒有任何可執行的操作 ...
  • Android 第三方類庫之EventBus 1 PS 工欲善其事必先利其器. Eventbus也是一款在開發中常用的利器 這篇也對EventBus的簡單介紹和使用,與之前個xutils介紹的級別一樣.http://www.cnblogs.com/greentomlee/p/6025470.html... ...
  • 題記:在開發的路途上,有的人走的很深很遠,而對於停留在初級階段的我來說,還要學的、經歷的還有很多... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...