Linux設備模型(總線、設備、驅動程序和類)

之一:bus_type

   總線是處理器和一個或多個設備之間的通道,在設備模型中,全部的設備都經過總線相連,甚至是內部的虛擬"platform"總線。能夠經過ls -l /sys/bus看到系統加載的全部總線。
drwxr-xr-x root     root              1970-01-01 00:02 platform
drwxr-xr-x root     root              1970-01-01 00:02 spi
drwxr-xr-x root     root              1970-01-01 00:02 scsi
drwxr-xr-x root     root              1970-01-01 00:02 usb
drwxr-xr-x root     root              1970-01-01 00:02 serio
drwxr-xr-x root     root              1970-01-01 00:02 i2c
drwxr-xr-x root     root              1970-01-01 00:02 mmc
drwxr-xr-x root     root              1970-01-01 00:02 sdio
drwxr-xr-x root     root              1970-01-01 00:02 ac97
      總線能夠相互插入。設備模型展現了總線和它們所控制的設備之間的實際鏈接。在Linux 設備模型中,總線由bus_type 結構表示,定義在 <linux/device.h> :
struct bus_type {
    const char  *name;                            /*總線類型名稱*/
    struct bus_attribute *bus_attrs;       /*總線屬性*/
    struct device_attribute *dev_attrs;   /*設備屬性,指爲每一個加入總線的設備創建的默認屬性鏈表*/
    struct driver_attribute *drv_attrs;     /*驅動程序屬性*/ node

    int (*match)(struct device *dev, struct device_driver *drv);   /*總線操做函數*/
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev); linux

    int (*suspend)(struct device *dev, pm_message_t state);    /*電源管理函數*/
    int (*suspend_late)(struct device *dev, pm_message_t state);
    int (*resume_early)(struct device *dev);
    int (*resume)(struct device *dev);
    struct pm_ext_ops *pm;
    struct bus_type_private *p;
};
1,總線的註冊和刪除,總線的主要註冊步驟:
(1)申明和初始化bus_type 結構體。只有不多的bus_type 成員須要初始化,大部分都由設備模型核心控制。但必須爲總線指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match,
    .uevent = ldd_uevent,
};
(2)調用bus_register函數註冊總線。int bus_register(struct bus_type *bus),該調用可能失敗,因此必須始終檢查返回值。
ret = bus_register(&ldd_bus_type);
if (ret)
   return ret;
若成功,新的總線子系統將被添加進系統,以後能夠向總線添加設備。當必須從系統中刪除一個總線時,調用:
void bus_unregister(struct bus_type *bus); 數據結構


 2,總線方法
     在 bus_type 結構中定義了許多方法,它們容許總線核心做爲設備核心與單獨的驅動程序之間提供服務的中介,主要介紹如下兩個方法: int (*match)(struct device * dev, struct device_driver * drv);
/*當一個新設備或者驅動被添加到這個總線時,這個方法會被調用一次或屢次,若指定的驅動程序可以處理指定的設備,則返回非零值。*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
/*在爲用戶空間產生熱插拔事件以前,這個方法容許總線添加環境變量*/ ide

      對設備和驅動的迭代:若要編寫總線層代碼,可能不得不對全部已經註冊到總線的設備或驅動進行一些迭代操做,這可能須要仔細研究嵌入到 bus_type 結構中的其餘數據結構,但最好使用內核提供的輔助函數: 
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
/*這兩個函數迭代總線上的每一個設備或驅動程序(內部分別有next_device和next_driver),將關聯的device或device_driver傳遞給 fn,同時傳遞data 值。若start爲NULL,則從第一個設備開始;不然從start以後的第一個設備開始。若fn返回非零值,迭代中止而且那個值從bus_for_each_dev 或bus_for_each_drv 返回。*/ 函數

3,總線屬性 ui

     幾乎Linux 設備模型中的每一層都提供添加屬性的函數,總線層也不例外。bus_attribute 類型定義在<linux/device.h> 以下: this

struct bus_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct bus_type *, char * buf);
    ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};
     內核提供了一個宏在編譯時建立和初始化bus_attribute 結構: spa

BUS_ATTR(_name,_mode,_show,_store)/*這個宏聲明一個結構,將bus_attr_做爲給定_name 的前綴來建立總線的真正名稱*/
/*總線的屬性必須顯式調用bus_create_file 來建立:*/
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr); 
/*刪除總線的屬性調用:*/
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr); 
      例如建立一個包含源碼版本號簡單屬性方法以下: debug

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
      return snprintf(buf, PAGE_SIZE, "%s\n", Version);
} 指針

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); //獲得bus_attr_version

/*在模塊加載時建立屬性文件:*/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
      printk(KERN_NOTICE "Unable to create version attribute\n");

/*這個調用建立一個包含 lddbus 代碼的版本號的屬性文件(/sys/bus/ldd/version)*/

 

 

之二:device

在最底層,Linux 系統中的每一個設備由一個struct device 表明: 
struct device

{
    struct klist  klist_children;
    struct klist_node knode_parent; /* node in sibling list */
    struct klist_node knode_driver;
    struct klist_node knode_bus;
    struct device  *parent; * 設備的 "父" 設備,該設備所屬的設備,一般一個父設備是某種總線或者主控制器。若是 parent 是 NULL, 則該設備是頂層設備,較少見 */

    struct kobject kobj;
    char bus_id[BUS_ID_SIZE]; /* position on parent bus */
    const char  *init_name; /* initial name of the device */
    struct device_type *type;
    unsigned  uevent_suppress:1;
    struct semaphore sem; /* semaphore 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  *driver_data; /* data private to the driver */
    void  *platform_data; /* Platform specific data, device core doesn't touch it */
    struct dev_pm_info power;

#ifdef CONFIG_NUMA
    int  numa_node; /* NUMA node this device is close to */
#endif
    u64  *dma_mask; /* dma mask (if dma'able device) */
    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 */
    /* arch specific additions */
    struct dev_archdata archdata;
    spinlock_t  devres_lock;
    struct list_head devres_head;

    struct list_head node;
    struct class  *class;
    dev_t   devt; /* dev_t, creates the sysfs "dev" */
    struct attribute_group **groups; /* optional groups */
    void (*release)(struct device *dev);  /*當這個設備的最後引用被刪除時,內核調用該方法; 它從被嵌入的kobject的release 方法中調用。全部註冊到核心的設備結構必須有一個 release 方法, 不然內核將打印錯誤信息*/
};
(1)設備註冊
在註冊struct device 前,最少要設置parent, bus_id, bus, 和 release 成員,設備的註冊和註銷函數爲:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
      一個實際的總線也是一個設備,因此必須單獨註冊,如下爲lddbus註冊它的虛擬總線設備:
static void ldd_bus_release(struct device *dev)
{
      printk(KERN_DEBUG "lddbus release\n");
}
struct device ldd_bus = {
     .bus_id = "ldd0",
     .release = ldd_bus_release
}; /*這是頂層總線,parent 和 bus 成員爲 NULL*/
/*做爲第一個總線,它的名字爲ldd0,這個總線設備的註冊代碼以下:*/
ret = device_register(&ldd_bus);
if (ret)
   printk(KERN_NOTICE "Unable to register ldd0\n");
/*一旦調用完成, 新總線ldd0會在sysfs中/sys/devices下顯示,任何掛到這個總線的設備會在/sys/devices/ldd0 下顯示*/
(2)設備屬性
sysfs 中的設備入口可有屬性,相關的結構是: 
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);
};
/*設備屬性結構可以使用如下宏(與BUS類比):*/
DEVICE_ATTR(_name,_mode,_show,_store);
/*這個宏聲明一個結構, 將 dev_attr_ 做爲給定 _name 的前綴來命名設備屬性
/*屬性文件的實際處理使用如下函數:*/
 int device_create_file(struct device *device,  struct device_attribute * entry);
 void device_remove_file(struct device * dev,  struct device_attribute * attr);

一個實例是:終端執行:cd /sys/class/leds/lcd-backlight,

ls回顯:

uevent
subsystem
device
power
brightness

這些屬性可能都是經過device_create_file添加上去(至少brightness是這樣)。進入device目錄,再輸入pwd,

回顯:/sys/devices/platform/smdk-backlight。變換到devices目錄下了,可見設備模型的不一樣構成是指向同一個設備的。

      2.6下字符設備開始用struct cdev結構體表示,可是我想調用device_create_file(dev, &dev_attr_debug);函數在/sys中導出信息,device_create_file()的第一個入口參數類型爲struct device結構體。問題是struct cdev與struct device這兩個結構體沒有任何聯繫的地方?答案是能夠採用共同擁有的Kobjcet這個成員做爲紐帶,因此從子類cdev--->父類kobject--->子類device,推導獲得:container_of(kobj)-->list_entry(entry)->(struct device*)  。由於containerof是從結構體指針成員找到結構體地址,因此從cdev的kobj能夠找到父類kobject的地址,而全部的kobject的entery都是在一個鏈表裏面,遍歷這個鏈表,找到結構體成員爲特定device結構的那一項。
(3)設備結構的嵌入
      device 結構包含設備模型核心用來模擬系統的信息。但大部分子系統記錄了關於它們擁有的設備的額外信息,因此不多單純用device 結構表明設備,而是一般將其嵌入一個設備的高層結構體表示中。

      lddbus 驅動建立了它本身的 device 類型(也即每類設備會創建本身的設備結構體,其中至少一個成員是struct device類型,好比video_device),並指望每一個設備驅動使用這個類型來註冊它們的設備: 
struct ldd_device {
 char *name;
 struct ldd_driver *driver;
 struct device dev
}; 
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev); 
      lddbus 導出的註冊和註銷接口以下: 
static void ldd_dev_release(struct device *dev)
{ }

int register_ldd_device(struct ldd_device *ldddev)
{
      ldddev->dev.bus = &ldd_bus_type;   //依賴的總線
      ldddev->dev.parent = &ldd_bus;       //父設備
      ldddev->dev.release = ldd_dev_release;
      strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE); //設備名拷貝入device結構體中
      return device_register(&ldddev->dev);   //仍然用device_register註冊,只不過上層打包了
}
EXPORT_SYMBOL(register_ldd_device);
void unregister_ldd_device(struct ldd_device *ldddev)
{
       device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);

 

之三:device_driver

設備模型跟蹤全部系統已知的驅動,主要目的是使驅動程序核心能協調驅動和新設備之間的關係。一旦驅動在系統中是已知的對象就可能完成大量的工做。驅動程序的結構體device_driver 定義以下: 
struct device_driver {
     const char  *name;       /*驅動程序的名字( 在 sysfs 中出現 )*/
     struct bus_type  *bus;   /*驅動程序所操做的總線類型*/

     struct module  *owner; 
     const char   *mod_name; /* used for built-in modules */

     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);


     struct attribute_group **groups;

     struct pm_ops *pm;
     struct driver_private *p;
};
(1)驅動程序的註冊和註銷
/*註冊device_driver 結構的函數是:*/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
(2)驅動程序的屬性
/*driver的屬性結構在:*/
struct driver_attribute {
     struct attribute attr;
     ssize_t (*show)(struct device_driver *drv, char *buf);
     ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
};
DRIVER_ATTR(_name,_mode,_show,_store)

     *屬性文件建立的方法:*/
int driver_create_file(struct device_driver * drv, struct driver_attribute * attr);
void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr);
(3)驅動程序結構的嵌入
      對大多數驅動程序核心結構,device_driver 結構一般被嵌入到一個更高層的、總線相關的結構中。固然也有直接註冊驅動的,不用嵌入到高層結構體。如driver_register(&wm97xx_driver)。
       以lddbus 子系統爲例,它定義了ldd_driver 結構:

struct ldd_driver {
     char *version;
     struct module *module;
     struct device_driver driver;
     struct driver_attribute version_attr; 
}; 
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver); 
      lddbus總線中相關的驅動註冊和註銷函數是: 
static ssize_t show_version(struct device_driver *driver, char *buf)
{
    struct ldd_driver *ldriver = to_ldd_driver(driver);
    sprintf(buf, "%s\n", ldriver->version);
    return strlen(buf);
}
int register_ldd_driver(struct ldd_driver *driver)  //device_driver被嵌入到更高層結構體
{
      int ret;
      driver->driver.bus = &ldd_bus_type;
      ret = driver_register(&driver->driver);/*註冊底層的 device_driver 結構到核心*/
      if (ret) 
          return ret;
      driver->version_attr.attr.name = "version";/* driver_attribute 結構必須手工填充*/
      driver->version_attr.attr.owner = driver->module;
      driver->version_attr.attr.mode = S_IRUGO;
      driver->version_attr.show = show_version;
      driver->version_attr.store = NULL;
      return driver_create_file(&driver->driver, &driver->version_attr);/*創建版本屬性,由於這個屬性在運行時被建立,因此不能使用 DRIVER_ATTR 宏*/
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
      driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);

在sculld 中建立的 ldd_driver 結構以下: /* Device model stuff */
static struct ldd_driver sculld_driver = {
    .version = "$Revision: 1.21 $",
    .module = THIS_MODULE,
    .driver = {
        .name = "sculld",
    },
};/*只要一個簡單的 register_ldd_driver 調用就可添加它到系統中。一旦完成初始化, 驅動信息可在 sysfs 中顯示*/

 

之四:class_register

類是一個設備的高層視圖,它抽象出了底層的實現細節,從而容許用戶空間使用設備所提供的功能,而不用關心設備是如何鏈接和工做的。類成員一般由上層代碼所控制,而無需驅動的明確支持。但有些狀況下驅動也須要直接處理類。
      幾乎全部的類都顯示在/sys/class目錄中,能夠經過ls -l /sys/class來顯示。出於歷史的緣由,有一個例外:塊設備顯示在/sys/block目錄中。在許多狀況,類子系統是向用戶空間導出信息的最好方法。當類子系統建立一個類時,它將徹底擁有這個類,根本不用擔憂哪一個模塊擁有那些屬性,並且信息的表示也比較友好。爲了管理類,驅動程序核心導出了一些接口,其目的之一是提供包含設備號的屬性以便自動建立設備節點,因此udev的使用離不開類。類函數和結構與設備模型的其餘部分遵循相同的模式,可與前三篇文章類比
(1)管理類的接口和註冊註銷函數
     類由 struct class 的結構體來定義:
struct class {
     const char  *name; *每一個類須要一個惟一的名字, 它將顯示在 /sys/class 中*/
     struct module  *owner;

     struct class_attribute  *class_attrs;
     struct device_attribute  *dev_attrs;
     struct kobject   *dev_kobj;

     int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

     void (*class_release)(struct class *class); /* 刪除類自己的函數 */
     void (*dev_release)(struct device *dev); 
    
     int (*suspend)(struct device *dev, pm_message_t state);
     int (*resume)(struct device *dev);

     struct pm_ops *pm;
     struct class_private *p;
};

     /*類註冊函數:*/
int class_register(struct class *cls);
void class_unregister(struct class *cls);

/*類屬性的接口:*/
struct class_attribute {
     struct attribute attr;
     ssize_t (*show)(struct class *cls, char *buf);
     ssize_t (*store)(struct class *cls, const char *buf, size_t count); 
};

CLASS_ATTR(_name,_mode,_show,_store); 
int class_create_file(struct class *cls, const struct class_attribute *attr);
void class_remove_file(struct class *cls, const struct class_attribute *attr);、
(2)類設備,類存在的真正目的是給做爲類成員的各個設備提供一個容器,成員由struct class_device 來表示,暫沒用到。
(3)類接口
      類子系統有一個 Linux 設備模型的其餘部分找不到的附加概念,稱爲「接口」,可將它理解爲一種設備加入或離開類時得到信息的觸發機制,結構體以下: 
struct class_interface {
      struct list_head node;
      struct class  *class;

      int (*add_dev)  (struct device *, struct class_interface *);
      void (*remove_dev) (struct device *, struct class_interface *);
};
/*註冊或註銷接口的函數:*/
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
/*一個類可註冊多個接口*/

     設定class的好處:設備驅動通常在註冊的時候都會調用此類class的一些函數,主要做用就是在sys目錄裏面建立一些節點,好比cd到/sys/class下面能夠看到這一類的設備,與這個相關的就是一些kobjects。固然對於一個新設備,能夠註冊進一個class也能夠不註冊進去,若是存在對應class的話註冊進去更好。

相關文章
相關標籤/搜索