1. 程式人生 > >Linux裝置驅動程式學習(13)

Linux裝置驅動程式學習(13)

匯流排

匯流排是處理器和一個或多個裝置之間的通道,在裝置模型中, 所有的裝置都通過匯流排相連, 甚至是內部的虛擬"platform"匯流排。匯流排可以相互插入。裝置模型展示了匯流排和它們所控制的裝置之間的實際連線。
在 Linux 裝置模型中, 匯流排由 bus_type 結構表示, 定義在 <linux/device.h>

struct bus_type {
    const char        * name;/*匯流排型別名稱*/
    struct module        * owner;/*指向模組的指標(如果有), 此模組負責操作這個匯流排*/

    struct

kset        subsys;/*與該匯流排相關的子系統*/
    struct kset        drivers;/*匯流排驅動程式的kset*/
    struct kset        devices;/* 掛在該匯流排的所有裝置的kset*/

    struct klist        klist_devices;/*與該匯流排相關的驅動程式連結串列*/
    struct klist        klist_drivers;/*掛接在該匯流排的裝置連結串列*/

    struct blocking_notifier_head bus_notifier;

    struct bus_attribute    *
bus_attrs; /*匯流排屬性*/
    struct device_attribute * dev_attrs; /*裝置屬性,指向為每個加入匯流排的裝置建立的預設屬性連結串列*/
    struct driver_attribute * drv_attrs; /*驅動程式屬性*/
    struct bus_attribute drivers_autoprobe_attr;/*驅動自動探測屬性*/
    struct bus_attribute drivers_probe_attr;/*驅動探測屬性*/

    int        (*match)(struct device * dev, struct
device_driver * drv);
    int        (*uevent)(struct device *dev, char **envp,
                 int num_envp, char *buffer, int buffer_size);
    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 (*suspend_late)(struct device * dev, pm_message_t state);
    int (*resume_early)(struct device * dev);
    nt (*resume)(struct device * dev);
/*處理熱插拔、電源管理、探測和移除等事件的方法*/
    unsigned int drivers_autoprobe: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)

呼叫可能失敗, 所以必須始終檢查返回值。若成功,新的匯流排子系統將被新增進系統,並可在 sysfs 的 /sys/bus 下看到。之後可以向匯流排新增裝置。 例如:

ret = bus_register(&ldd_bus_type);
if (ret)
 return ret;

當必須從系統中刪除一個匯流排時, 呼叫:

void bus_unregister(struct bus_type *bus);

匯流排方法

在 bus_type 結構中定義了許多方法,它們允許匯流排核心作為裝置核心和單獨的驅動程式之間提供服務的中介,主要介紹以下兩個方法:

int (*match)(struct device * dev, struct device_driver * drv);
/*當一個新裝置或者驅動被新增到這個匯流排時,這個方法會被呼叫一次或多次,若指定的驅動程式能夠處理指定的裝置,則返回非零值。必須在匯流排層使用這個函式, 因為那裡存在正確的邏輯,核心核心不知道如何為每個匯流排型別匹配裝置和驅動程式*/

int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
/*在為使用者空間產生熱插拔事件之前,這個方法允許匯流排新增環境變數(引數和 kset 的uevent方法相同)*/

lddbus的match和uevent方法:

static int ldd_match(struct device *dev, struct device_driver *driver)
{
 return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}/*僅簡單比較驅動和裝置的名字*/
/*當涉及實際硬體時, match 函式常常對裝置提供的硬體 ID 和驅動所支援的 ID 做比較*/

static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
 envp[0] = buffer;
 if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",
 Version) >= buffer_size)
 return -ENOMEM;
 envp[1] = NULL;
 return 0;
}/*在環境變數中加入 lddbus 原始碼的當前版本號*/

對裝置和驅動的迭代

若要編寫匯流排層程式碼, 可能不得不對所有已經註冊到匯流排的裝置或驅動進行一些操作,這可能需要仔細研究嵌入到 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 *));

/*這兩個函式迭代總線上的每個裝置或驅動程式, 將關聯的 device 或 device_driver 傳遞給 fn, 同時傳遞 data 值。若 start 為 NULL, 則從第一個裝置開始; 否則從 start 之後的第一個裝置開始。若 fn 返回非零值, 迭代停止並且那個值從 bus_for_each_dev 或bus_for_each_drv 返回。*/

匯流排屬性

幾乎 Linux 裝置模型中的每一層都提供新增屬性的函式, 匯流排層也不例外。bus_attribute 型別定義在 <linux/device.h> 如下:

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


可以看出struct bus_attribute 和struct attribute 很相似,其實大部分在 kobject 級上的裝置模型層都是以這種方式工作。

核心提供了一個巨集在編譯時建立和初始化 bus_attribute 結構:

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

例如建立一個包含原始碼版本號簡單屬性檔案方法如下:

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

/*在模組載入時建立屬性檔案:*/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
 printk(KERN_NOTICE "Unable to create version attribute/n");

/*這個呼叫建立一個包含 lddbus 程式碼的版本號的屬性檔案(/sys/bus/ldd/version)*/

裝置

在最底層, 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;/*代表該裝置並將其連線到結構體系中的 kobject; 注意:作為通用的規則, device->kobj->parent 應等於 device->parent->kobj*/
    char    bus_id[BUS_ID_SIZE];/*在總線上唯一標識該裝置的字串;例如: PCI 裝置使用標準的 PCI ID 格式, 包含:域, 匯流排, 裝置, 和功能號.*/
    struct device_type    *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;
    struct device_attribute uevent_attr;
    struct device_attribute *devt_attr;

    struct semaphore    sem;  /* semaphore to synchronize calls to its driver. */
    struct bus_type    * bus;     /*標識該裝置連線在何種型別的總線上*/
    struct device_driver *driver;    /*管理該裝置的驅動程式*/
    void        *driver_data;    /*該裝置驅動使用的私有資料成員*/
    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;
/* Like dma_mask, but for
                     alloc_coherent mappings as
                     not all hardware supports
                     64 bit addresses for consistent
                     allocations such descriptors. */


    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;

    /* class_device migration path */
    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 方法, 否則核心將列印錯誤資訊*/
};
/*在註冊 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");
/*一旦呼叫完成, 新匯流排會在 sysfs 中 /sys/devices 下顯示,任何掛到這個匯流排的裝置會在 /sys/devices/ldd0 下顯示*/

裝置屬性

sysfs 中的裝置入口可有屬性,相關的結構是:

/* interface for exporting device attributes 這個結構體和《LDD3》中的不同,已經被更新過了,請特別注意!*/
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);
};

/*裝置屬性結構可在編譯時建立, 使用以下巨集:*/
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);

裝置結構的嵌入

device 結構包含裝置模型核心用來模擬系統的資訊。但大部分子系統記錄了關於它們又擁有的裝置的額外資訊,所以很少單純用 device 結構代表裝置,而是,通常將其嵌入一個裝置的高層表示中。底層驅動幾乎不知道 struct device。

lddbus 驅動建立了它自己的 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 匯出的註冊和登出介面如下:

/*
 * LDD devices.
 */


/*
 * For now, no references to LDDbus devices go out which are not
 * tracked via the module reference count, so we use a no-op
 * release function.
 */

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);
    return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);

void unregister_ldd_device(struct ldd_device *ldddev)
{
    device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);


 sculld 驅動新增一個自己的屬性到它的裝置入口,稱為 dev, 僅包含關聯的裝置號,原始碼如下:

static ssize_t sculld_show_dev(struct device *ddev,struct device_attribute *attr, char *buf)
{
 struct sculld_dev *dev = ddev->driver_data;
 return print_dev_t(buf, dev->cdev.dev);
}

static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

/*接著, 在初始化時間, 裝置被註冊, 並且 dev 屬性通過下面的函式被建立:*/
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
 sprintf(dev->devname, "sculld%d", index);
 dev->ldev.name = dev->devname;
 dev->ldev.driver = &sculld_driver;
 dev->ldev.dev.driver_data = dev;
 register_ldd_device(&dev->ldev);
 if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
    printk( "Unable to create dev attribute ! /n");
} /*注意:程式使用 driver_data 成員來儲存指向我們自己的內部的裝置結構的指標。請檢查
device_create_file的返回值,否則編譯時會有警告。*/

裝置驅動程式

裝置模型跟蹤所有系統已知的驅動,主要目的是使驅動程式核心能協調驅動和新裝置之間的關係。一旦驅動在系統中是已知的物件就可能完成大量的工作。驅動程式的結構體 device_driver 定義如下:

/*定義在<linux/device.h>*/
struct device_driver {
    const char        * name;/*驅動程式的名字( 在 sysfs 中出現 )*/
    struct bus_type        * bus;/*驅動程式所操作的匯流排型別*/

    struct kobject        kobj;/*內嵌的kobject物件*/
    struct klist        klist_devices;/*當前驅動程式能操作的裝置連結串列*/
    struct klist_node    knode_bus;

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

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

/*註冊device_driver 結構的函式是:*/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

/*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