Linux內核的LED設備驅動框架【轉】

来源:https://www.cnblogs.com/linhaostudy/archive/2020/02/27/12370441.html
-Advertisement-
Play Games

/ 本文為個人學習記錄,如有錯誤,歡迎指正。 本文參考資料: "https://blog.csdn.net/qq_28992301/article/details/52410587" "https://blog.csdn.net/hanp_linux/article/details/79037610 ...


/************************************************************************************

*本文為個人學習記錄,如有錯誤,歡迎指正。

*本文參考資料:

*        https://blog.csdn.net/qq_28992301/article/details/52410587

*        https://blog.csdn.net/hanp_linux/article/details/79037610

************************************************************************************/

1. 驅動框架的概念

內核中驅動部分維護者針對每個種類的驅動設計一套成熟的、標準的、典型的驅動實現,並把不同廠家的同類硬體驅動中相同的部分抽出來自己實現好,再把不同部分留出介面給具體的驅動開發工程師來實現,這就叫驅動框架。即標準化的驅動實現,統一管理系統資源,維護系統穩定。

2. LED設備驅動框架概述

(1)LED設備的共性:

1)LED的亮與滅;

2)具有相應的設備節點(設備文件)。

(2)LED設備的不同點:

1)LED的硬體連接方式不同(GPIO不同);

2)LED的控制方式不同(低或高電平觸發);

3)等其他不同點。

因此,Linux中LED的驅動框架把所有LED設備的共性給實現了,把不同的地方留給驅動工程師去做。

(3)核心文件:

  

/kernel/driver/leds/led-class.c 
/kernel/driver/leds/led-core.c 
/kernel/driver/leds/led-triggers.c 
/kernel/include/linux/leds.h

(4)輔助文件(根據需求來決定這部分代碼是否需要):

  

/kernel/driver/leds/led-triggers.c 
/kernel/driver/leds/trigger/led-triggers.c 
/kernel/driver/leds/trigger/ledtrig-oneshot.c 
/kernel/driver/leds/trigger/ledtrig-timer.c 
/kernel/driver/leds/trigger/ledtrig-heartbeat.c

3. LED設備驅動框架分析

3.1 創建leds類

subsys_initcall是一個巨集,它的功能是將其聲明的函數放到一個特定的段:.initcall4.init。

內核在啟動過程中,內核需要按照先後順序去進行初始化操作。因此,內核給是給啟動時要調用的所有初始化函數歸類,然後每個類按照一定的次序去調用執行。這些分類名就叫.initcalln.init,n的值從1到8。內核開發者在編寫內核代碼時只要將函數設置合適的級別,這些函數就會被鏈接的時候放入特定的段,內核啟動時再按照段順序去依次執行各個段即可。module_init()、module_exit()也是一個巨集,其功能與subsys_initcall相同,只是指定的段不同。

//所在文件/kernel/include/linux/init.h
#define subsys_initcall(fn)        __define_initcall("4",fn,4)

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

LED驅動框架使用subsys_initcall巨集修飾leds_init()函數,因此leds_init()函數在內核啟動階段被調用。leds_init()函數的主要工作是:調用class_create()函數在/sys/class目錄下創建一個leds類目錄。

//所在文件/kernel/driver/leds/led-class.c
static int __init leds_init(void)
{
    leds_class = class_create(THIS_MODULE, "leds");  //在/sys/class目錄下創建一個leds類目錄
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
  /*填充leds_class*/ 
  leds_class->suspend = led_suspend; 
  leds_class->resume = led_resume; 
  leds_class->dev_attrs = led_class_attrs; //類屬性 
  return 0; 
} 

subsys_initcall(leds_init);

3.2 leds類屬性的定義與初始化

leds_class->dev_attrs規定了leds設備類的類屬性,其中的類屬性將被sysfs以文件的形式導出至/sys/class/leds目錄下,用戶空間通過對這些文件的訪問來操作硬體設備。詳見Linux設備管理:sysfs文件系統的功能及其應用。

led_class_attrs結構體數組設置了leds設備類的屬性,即led硬體操作的對象和方法。分析可知,leds類設備的操作對象一共由3個brightness(LED的亮滅狀態)、max_brightness(LED最高亮度值)、trigger(LED閃爍狀態)。對應的操作規則有讀寫,即show和store。這些操作規則內部其實調用了設備體led_classdev內的具體操作函數,譬如:當用戶層試圖寫brightness這個對象時,會觸發操作規則led_brightness_store。

//所在文件/kernel/driver/leds/led-class.c
static struct device_attribute led_class_attrs[] = 
{
    __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
    __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
    __ATTR_NULL,
};
/*
*所在文件/kernel/include/linux/sysfs.h
*_name表示屬性的名字,即在sys中呈現的文件。
*_mode表示這個屬性的讀寫許可權,如0666, 分別表示user/group/other的許可權都是可讀可寫。
*_show表示的是對此屬性的讀函數,當cat這個屬性的時候被調用,_stroe表示的是對此屬性的寫函數,當echo內容到這個屬性的時候被調用。
*/

#define __ATTR(_name,_mode,_show,_store) { \
    .attr = {.name = __stringify(_name), .mode = _mode },    \
    .show    = _show,                    \
    .store    = _store,                    \
}

3.3 LED設備信息初始化

在registerLED設備之前,需要先定義並初始化一個struct led_classdev結構體變數,該結構體包含了該LED設備的所有信息。

初始化struct led_classdev結構體變數時,只需填充如下值即可,其餘的在register過程中自動完成填充。

--name:LED設備目錄名稱;

--brightness:LED設備初始亮度;

--max_brightness:LED設備的最大亮度;

--void (brightness_set)(struct led_classdev led_cdev, enum led_brightness brightness):該函數為實際操作LED硬體的函數,由驅動工程師根據具體的LED設備來實現;

--enum led_brightness (brightness_get)(struct led_classdev led_cdev):該函數用於獲取LED設備的當前亮度值,LED驅動框架已實現led_get_brightness()函數(/kernel/drivers/leds/leds.h),將該函數的函數名賦予這個指針變數即可。

struct led_classdev {
    const char      *name;          //LED設備名
    int             brightness;     //LED設備的初始亮度
    int             max_brightness; //LED設備的最大亮度
    int             flags;

    /* Lower 16 bits reflect status */
#define LED_SUSPENDED        (1 << 0)
    /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME    (1 << 16)

    void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness); //設置LED設備的亮度
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);           //獲取LED設備的當前亮度

    /* Activate hardware accelerated blink, delays are in
     * miliseconds and if none is provided then a sensible default
     * should be chosen. The call can adjust the timings if it can't
     * match the values specified exactly. */
    int (*blink_set)(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off);

    struct device        *dev;
    struct list_head     node;            /* LED Device list */
    const char        *default_trigger;    /* Trigger to use */

#ifdef CONFIG_LEDS_TRIGGERS
    /* Protects the trigger data below */
    struct rw_semaphore     trigger_lock;

    struct led_trigger    *trigger;
    struct list_head     trig_list;
    void            *trigger_data;
#endif
};

3.4 LED設備的register介面

LED設備驅動框架為驅動開發者提供在/sys/class/leds這個類下創建LED設備的介面。

當驅動調用led_classdev_register註冊了一個LED設備,那麼就會在/sys/class/leds目錄下創建xxx設備,由sysfs創建該設備的一系列attr屬性文件(brightness、max_brightness等)將被保存至該目錄下供用戶空間訪問。

//所在文件/kernel/driver/leds/led-class.c
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
    //在/sys/class/leds設備類目錄下創建具體的設備目錄,目錄名由led_cdev->name指定
    led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name); 

    if (IS_ERR(led_cdev->dev))
        return PTR_ERR(led_cdev->dev);

#ifdef CONFIG_LEDS_TRIGGERS
    init_rwsem(&led_cdev->trigger_lock);
#endif
    /* add to the list of leds */
    down_write(&leds_list_lock);
    list_add_tail(&led_cdev->node, &leds_list);
    up_write(&leds_list_lock);
  
  //如果設備驅動在註冊時沒有設置max_brightness,則將max_brightness設置為滿即255
    if (!led_cdev->max_brightness)
        led_cdev->max_brightness = LED_FULL;

  //如果在初始化struct_classdev *led_cdev時,設置了get_brightness方法,則讀出當前的brightness並更新
    led_update_brightness(led_cdev);

#ifdef CONFIG_LEDS_TRIGGERS
    led_trigger_set_default(led_cdev);
#endif

    printk(KERN_DEBUG "Registered led device: %s\n", led_cdev->name); //在內核啟動過程中列印所註冊設備類的名稱

    return 0;
}

3.5 leds類屬性的操作方法實現

當用戶在文件系統下讀寫LED設備的屬性文件時,就會調用這些屬性文件的show和store方法,從而操作硬體。

image

(1)brightness屬性操作

1)當用戶cat /sys/class/leds/xxx/brightness時會調用led-class.c中的brightness_show函數。

//所在文件/kernel/driver/leds/led-class.c
static ssize_t led_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
{
  //根據device結構體獲取led_classdev結構體,其中包含了該LED設備的所有信息
  struct led_classdev *led_cdev = dev_get_drvdata(dev);
  //如果在初始化struct_classdev *led_cdev時,設置了get_brightness方法,則讀出當前的brightness並更新
  led_update_brightness(led_cdev); 
  
  return sprintf(buf, "%u\n", led_cdev->brightness);  //將LED當前亮度值存入buf中
}

static void led_update_brightness(struct led_classdev *led_cdev)
{
    if (led_cdev->brightness_get)
        led_cdev->brightness = led_cdev->brightness_get(led_cdev); 
}


static inline int led_get_brightness(struct led_classdev *led_cdev)
{
    return led_cdev->brightness;
}

2)當用戶 echo 100 > /sys/class/leds/xxx/brightness時會調用led-class.c中的brightness_store函數。

//所在文件/kernel/driver/leds/led-class.c
static ssize_t led_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
{
   //根據device結構體獲取led_classdev結構體,其中包含了該LED設備的所有信息
   struct led_classdev *led_cdev = dev_get_drvdata(dev);
    ssize_t ret = -EINVAL;
    char *after;
    unsigned long state = simple_strtoul(buf, &after, 10);
    size_t count = after - buf;

    if (isspace(*after))
        count++;

    if (count == size) {
        ret = count;

        if (state == LED_OFF)
            led_trigger_remove(led_cdev);
        led_set_brightness(led_cdev, state);//設置LED亮度
    }
    return ret;
}

//所在文件/kernel/driver/leds/leds.h
static inline void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value)
{
    if (value > led_cdev->max_brightness)
        value = led_cdev->max_brightness;
    led_cdev->brightness = value;
    if (!(led_cdev->flags & LED_SUSPENDED))
        led_cdev->brightness_set(led_cdev, value);  //調用led_classdev下的LED硬體操作函數brightness_set,該函數由驅動工程師完成編寫。
}

(2)max_brightness屬性操作

當用戶當用戶cat /sys/class/leds/xxx/max_brightness時會調用led-class.c中的led_max_brightness_show函數。

//所在文件/kernel/driver/leds/led-class.c
static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    struct led_classdev *led_cdev = dev_get_drvdata(dev);

    return sprintf(buf, "%u\n", led_cdev->max_brightness);//將最大亮度值保存至buf中
}

3.6 LED設備的unregister介面

LED設備驅動框架為驅動開發者LED設備驅動的卸載介面。調用led_classdev_unregister()函數卸載LED設備驅動。

//所在文件/kernel/driver/leds/led-class.c
void led_classdev_unregister(struct led_classdev *led_cdev)
{
#ifdef CONFIG_LEDS_TRIGGERS
    down_write(&led_cdev->trigger_lock);
    if (led_cdev->trigger)
        led_trigger_set(led_cdev, NULL);
    up_write(&led_cdev->trigger_lock);
#endif

    device_unregister(led_cdev->dev);  //註銷設備類下的設備

    down_write(&leds_list_lock);
    list_del(&led_cdev->node);
    up_write(&leds_list_lock);
}

//註銷設備類
static void __exit leds_exit(void)
{
    class_destroy(leds_class);
}

module_exit(leds_exit);

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

-Advertisement-
Play Games
更多相關文章
  • 目錄 touch cat more less head tail touch 解釋 語法 示例 cat 解釋 語法 示例 more 解釋 語法 示例 less 解釋 語法 示例 head 解釋 語法 示例 tail 解釋 語法 示例 ...
  • 目錄處理命令:ls 解釋 語法 ls 列出當前目錄下的所有文件(沒有隱藏的) ls a ls l 列出當前目錄下所有的文件的詳細信息 詳細解釋 第一個單獨解釋 ls lh 列出文件詳細信息,文件單位由系統判定顯示,或顯示K,或現實M 解釋 ls ld 查看目錄的詳細信息,而不是文件夾下的文件信息 l ...
  • 距離centos8.0(現在已經更新到8.1了)的發佈已經過去幾個月了,作為一個剛剛接觸過幾個月centos的萌新來說,本文想通過實際的操作體驗來說對比一下centos8代與7代 首先,centos8 dvd版的鏡像有7G大,也是我目前安裝過最大的操作系統鏡像了,首先從官網下載後,安裝的時候就踩了一 ...
  • 文件目錄結構 閑話篇: linux我也是最近才開始學,寫隨筆是為分享學習經驗的同時也留著供自己以後來參考。因為linux一切皆文件的基本哲學思想。所以我決定從文件目錄開始寫。 正文: 首先linux文件系統格式為ext3/4(ext是extended的縮寫,意為擴展,全稱linux擴展文件系統),這 ...
  • Everspin 是設計,製造和商業銷售離散和嵌入式磁阻RAM(MRAM)和自旋傳遞扭矩MRAM(STT-MRAM)的全球領導者,其市場和應用領域涉及數據持久性和完整性,低延遲和安全性至關重要。Everspin在數據中心,雲存儲,能源,工業,汽車和運輸市場中部署了超過1.2億個MRAM和STT-MR ...
  • 隨著單塊磁碟在數據安全、性能、容量上呈現出的局限,磁碟陣列(Redundant Arrays of Inexpensive/Independent Disks,RAID)出現了,RAID把多塊獨立的磁碟按不同的方式組合起來,形成一個磁碟組,以獲得比單塊磁碟更高的數據安全、性能、容量。 一. 常見的R ...
  • 一 磁碟物理結構 (1) 碟片:硬碟的盤體由多個碟片疊在一起構成。 在硬碟出廠時,由硬碟生產商完成了低級格式化(物理格式化),作用是將空白的碟片(Platter)劃分為一個個同圓心、不同半徑的磁軌(Track),還將磁軌劃分為若幹個扇區(Sector),每個扇區可存儲128×2的N次方(N=0.1. ...
  • Samba伺服器安裝和配置 1:安裝Samba伺服器軟體包 [root@localhost ~]# rpm -qa | grep samba [root@localhost ~]# yum -y install samba [root@localhost ~]# yum -y install sam ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...