天天看點

Linux裝置模型剖析系列之三(device和device driver)

五、device和device driver

1. 前言

  • device和device driver是Linux驅動開發的基本概念。Linux kernel的思路很簡單:驅動開發,就是要為指定的裝置(device)開發指定的軟體(device_driver),是以kernel就為裝置和驅動定義了兩個資料結構,分别是device和device_driver。下文将會圍繞這兩個資料結構,介紹Linux裝置模型的核心邏輯,包括:
  • 裝置及裝置驅動在kernel中的抽象、使用和維護;
  • 裝置及裝置驅動的注冊、加載、初始化原理;
  • 裝置模型在實際驅動開發過程中的使用方法。

2. struct device和struct device_driver

在include/linux/device.h中,Linux核心定義了裝置模型中最重要的兩個資料結構,struct device和struct device_driver。

2.1 struct device

/* include/linux/device.h, line 660 */
struct device {
    struct device            *parent;
    struct device_private    *p;
    struct kobject kobj;
    const char *            init_name;  /* initial name of the device */
    const struct device_type *type;
    struct mutex            mutex;      /* mutex to synchronize calls to its driver.*/
    struct bus_type         *bus;       /* type of bus device is on */
    struct device_driver    *driver;    /* which driver has allocated this device */
    void *platform_data;                /* platform specific data, device core doesn't touch it */
    struct dev_pm_info      power;
    struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_PINCTRL
    struct dev_pin_info     *pins;
#endif

#ifdef CONFIG_NUMA
    int numa_node;                      /* NUMA node this device is close to */
#endif
    
    u64     *dma_mask;                  /* dma mask (if dma'able device) */
    
    /* 
     * Like dma_mask, but for alloc_coherent mappings as  
     * not all hardware supports 64 bit addresses for consistent
     * allocations such descriptors. 
     */
    u64     coherent_dma_mask;
    
    struct device_dma_parameters *dma_parms;
    struct list_head             dma_pools;         /* dma pools (if dma'ble) */
    struct dma_coherent_mem      *dma_mem;          /* internal for coherent mem  override */
#ifdef CONFIG_CMA
    struct cma                  *cma_area;         /* contiguous memory area for dma allocations */
#endif
    
    struct dev_archdata         archdata;       /* arch specific additions */
    struct device_node          *of_node;       /* associated device tree node */
    struct acpi_dev_node        acpi_node;      /* associated ACPI device node */

    dev_t                       devt;           /* dev_t, creates the sysfs "dev" */
    u32                         id;            /* device instance */

    spinlock_t                  devres_lock;
    struct list_head            devres_head;
    struct klist_node           knode_class;
    struct class                *class;
    const struct attribute_group **groups;      /* optional groups */
    void (*release)(struct device *dev);
    struct iommu_group          *iommu_group;
};      
  • device結構很複雜,這裡将會選一些對了解裝置模型非常關鍵的字段進行說明:
  • parent:該裝置的父裝置,一般是該裝置所從屬的bus、controller等裝置。
  • p: 一個用于struct device的私有資料結構指針,該指針中會儲存子裝置連結清單、用于添加到bus/driver/prent等裝置中的連結清單頭等等,具體可檢視源代碼。
  • kobj:該資料結構對應的struct kobject。
  • init_name:該裝置的名稱。在裝置模型中,名稱是一個非常重要的變量,任何注冊到核心中的裝置,都必須有一個合法的名稱,可以在初始化時給出,也可以由核心根據“bus name + device ID”的方式創造。
  • type:struct device_type結構是新版本核心新引入的一個結構,它和struct device的關系非常類似stuct kobj_type和struct kobject之間的關系。
  • bus:該device屬于哪個總線。
  • driver:該device對應的device driver。
  • platform_data:一個用于儲存具體平台相關的資料的指針。具體的driver子產品,可以将一些私有的資料暫存在這裡,需要使用的時候再拿出來,是以裝置模型并不關心該指針的實際含義。
  • power、pm_domain:電源管理相關的邏輯。
  • pins:"PINCTRL”功能,暫不描述。
  • numa_node:"NUMA”功能,暫不描述。
  • dma_mask~archdata:DMA相關的功能,暫不描述。
  • devt:dev_t是一個32位的整數,它由兩個部分(Major和Minor)組成,在需要以裝置節點的形式(字元裝置和塊裝置)向使用者空間提供接口的裝置中,作為裝置号使用。在這裡,該變量主要用于在sys檔案系統中,為每個具有裝置号的device,建立/sys/dev/* 下的對應目錄,如下:
#ls /sys/dev/char/1:                                                                       
1:1/  1:11/ 1:13/ 1:14/ 1:2/  1:3/  1:5/  1:7/  1:8/  1:9/   
#ls /sys/dev/char/1:1                                                                      
1:1/  1:11/ 1:13/ 1:14/  
# ls /sys/dev/char/1:1   
/sys/dev/char/1:1      
  • class:該裝置屬于哪個class。
  • groups:該裝置的預設attribute集合。将會在裝置注冊時自動在sysfs中建立對應的檔案。

2.2 struct device_driver

/* include/linux/device.h, line 213 */
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);
    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;
};      
  • device_driver就簡單多了(在早期的核心版本中driver的資料結構為"struct driver”,後來就改成device_driver了):
  • name:該driver的名稱。和device結構一樣,該名稱非常重要,後面會再詳細說明。
  • bus:該driver所驅動的裝置的總線裝置。為什麼driver需要記錄總線裝置的指針呢?因為核心要保證在driver運作前,裝置所依賴的總線能夠正确初始化。
  • owner、mod_name:內核module相關的變量。
  • suppress_bind_attrs:是否在sysfs中啟用bind和unbind attribute,如下:
# ls /sys/bus/platform/drivers/switch-gpio/ 
 bind   uevent unbind      

在kernel中,bind/unbind是從使用者空間手動為driver綁定/解綁定指定的裝置的機制。這種機制是在bus.c中完成的,後面會詳細解釋。

  • probe、remove:這兩個接口函數用于實作driver邏輯的開始和結束。driver是一段軟體code,是以會有開始和結束兩個代碼邏輯,就像PC程式,會有一個main函數,main函數的開始就是開始,return的地方就是結束。而核心driver卻有其特殊性:在裝置模型的結構下,隻有driver和device同時存在時,才需要開始執行driver的代碼邏輯。這也是probe和remove兩個接口名稱的由來:檢測到了裝置就調用probe接口,、移除了裝置(就調用remove)。
  • shutdown、suspend、resume、pm:電源管理相關的内容,會在電源管理專題中詳細說明。
  • groups:和struct device結構中的同名變量類似,driver也可以定義一些預設attribute,這樣在将driver注冊到核心中時,核心裝置模型部分的代碼(driver/base/driver.c)會自動将這些attribute添加到sysfs中。
  • p:driver core的私有資料指針,其它子產品不能通路。

3. 裝置模型架構下驅動開發的基本步驟

  • 在裝置模型架構下,裝置驅動的開發是一件很簡單的事情,主要包括2個步驟:
  1. 配置設定一個​

    ​struct device​

    ​類型的變量,填充必要的資訊後,把它注冊到核心中。
  2. 配置設定一個​

    ​struct device_driver​

    ​類型的變量,填充必要的資訊後,把它注冊到核心中。
  • 這兩步完成後,核心會在合适的時機調用struct device_driver變量中的probe、remove、suspend、resume等回調函數,進而觸發或者終結裝置驅動的執行。而所有的驅動程式邏輯,都會由這些回調函數實作,此時,驅動開發者眼中便不再有“裝置模型”,轉而隻關心驅動本身的實作。

以上兩個步驟的補充說明:

1. 一般情況下,Linux驅動開發很少直接使用device和device_driver,因為核心在它們之上又封裝了一層,如soc device、platform device等等,而這些層次提供的接口更為簡單、易用(也正是因為這個原因,本文并不會過多涉及device、device_driver等子產品的實作細節)。

2. 核心提供很多struct device結構的操作接口(具體可以參考include/linux/device.h和drivers/base/core.c的代碼),主要包括初始化(​

​device_initialize​

​​)、注冊到核心(​

​device_register​

​​)、配置設定存儲空間+初始化+注冊到核心(​

​device_create​

​)等等,可以根據需要使用。

3. device和device_driver必須具備相同的名稱,核心才能完成比對操作,進而調用device_driver中的相應接口。這裡的同名,作用範圍是同一個bus下的所有device和device_driver。

4. device和device_driver必須挂載在同一個bus之下,該bus可以是實際存在的,也可以是虛拟的。

5. driver開發者可以在struct device變量中,儲存描述裝置特征的資訊,如尋址空間、依賴的GPIOs等,因為device指針會在執行probe等接口時傳入,這時driver就可以根據這些資訊,執行相應的邏輯操作了。

4. 裝置驅動probe的時機

所謂的"probe”,是指在Linux核心中,如果存在相同名稱的device和device_driver(注:還存在其它方式,我們先不關注了),核心就會執行device_driver中的probe回調函數,而該函數就是所有driver的入口,可以執行諸如硬體裝置初始化、字元裝置注冊、裝置檔案操作ops注冊等動作("remove”是它的反操作,發生在device或者device_driver任何一方從核心登出時,其原理類似,就不再單獨說明了)。

裝置驅動prove的時機有如下幾種(分為自動觸發和手動觸發):

  • 将struct device類型的變量注冊到核心中時自動觸發(device_register,device_add,device_create_vargs,device_create)
  • 将struct device_driver類型的變量注冊到核心中時自動觸發(driver_register)
  • 手動查找同一bus下的所有device_driver,如果有和指定device同名的driver,執行probe操作(device_attach)
  • 手動查找同一bus下的所有device,如果有和指定driver同名的device,執行probe操作(driver_attach)
  • 自行調用driver的probe接口,并在該接口中将該driver綁定到某個device結構中----即設定dev->driver(device_bind_driver)
注意:
  • probe動作實際是由bus子產品實作的,這不難了解:device和device_driver都是挂載在bus這根線上,是以隻有bus最清楚應該為哪兩個device和driver配對。
  • 每個bus都有一個drivers_autoprobe變量,用于控制是否在device或者driver注冊時,自動probe。該變量預設為1(即自動probe),bus子產品将它開放到sysfs中了,因而可在使用者空間修改,進而控制probe行為。

5. 其它雜項

5.1 device_attribute和driver_attribute

前面我們有講到,大多數時候,attribute檔案的讀寫資料流為:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上層資料結構實作。

Linux核心中關于該内容的例證到處都是,device也不無例外的提供了這種例子,如下:

/* driver/base/core.c, line 118 */
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) 
{   
    struct device_attribute *dev_attr = to_dev_attr(attr);
    struct device *dev = kobj_to_dev(kobj);
    ssize_t ret = -EIO;

    if (dev_attr->show)
        ret = dev_attr->show(dev, dev_attr, buf);
    if (ret >= (ssize_t)PAGE_SIZE) {
        print_symbol("dev_attr_show: %s returned bad count\n", (unsigned long)dev_attr->show);
    }
    return ret;
}
 
static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
    struct device_attribute *dev_attr = to_dev_attr(attr);
    struct device *dev = kobj_to_dev(kobj);
    ssize_t ret = -EIO;

    if (dev_attr->store)
        ret = dev_attr->store(dev, dev_attr, buf, count);
    return ret;
}
 
static const struct sysfs_ops dev_sysfs_ops = {
    .show   = dev_attr_show,
    .store  = dev_attr_store,
};

/* driver/base/core.c, line 243 */
static struct kobj_type device_ktype = {
    .release    = device_release,
    .sysfs_ops  = &dev_sysfs_ops,
    .namespace = device_namespace,
};
 
/* include/linux/device.h, line 478 */
/* interface for exporting device attributes */
struct device_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};      

至于driver的attribute,則要簡單的多,其資料流為:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下

/* include/linux/device.h, line 247 */
/* sysfs interface for exporting driver attributes */ 
struct driver_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device_driver *driver, char *buf);
    ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);
};

#define DRIVER_ATTR(_name, _mode, _show, _store)\
    struct driver_attribute driver_attr_##_name =\
    __ATTR(_name, _mode, _show, _store)

/* include/linux/sysfs.h, line 41 */
#define __ATTR(_name,_mode,_show,_store) { \
    .attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE }, \
    .show   = _show, \
    .store  = _store,\
}      

5.2 device_type

device_type是内嵌在struct device結構中的一個資料結構,用于指明裝置的類型,并提供一些額外的輔助功能。它的的形式如下:

/* include/linux/device.h, line 467 */
struct device_type {
    const char *name;
    const struct attribute_group **groups;
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid);
    void (*release)(struct device *dev);

    const struct dev_pm_ops *pm;
};      
  • device_type的功能包括:
  • name:表示該類型的名稱,當該類型的裝置添加到核心時,核心會發出"DEVTYPE=‘name’”類型的uevent,告知使用者空間某個類型的裝置可用。
  • groups:該類型裝置的公共attribute集合。裝置注冊時,會同時注冊這些attribute。這就是面向對象中“繼承”的概念
  • uevent:所有相同類型的裝置,會有一些共有的uevent需要發送,由該接口實作。
  • devnode:devtmpfs有關的内容,暫不說明
  • release:如果device結構沒有提供release接口,就要查詢它所屬的type是否提供。用于釋放device變量所占的空間。

5.3 root device

/* include/linux/device.h, line 859 */
/*
 * Root device objects for grouping under /sys/devices
 */
extern struct device *__root_device_register(const char *name, struct module *owner);
 
/*
 * This is a macro to avoid include problems with THIS_MODULE,
 * just as per what is done for device_schedule_callback() above.
 */
#define root_device_register(name) \
  __root_device_register(name, THIS_MODULE)

extern void root_device_unregister(struct device *root);      

繼續閱讀