linux總線、設(shè)備和設(shè)備驅(qū)動的關(guān)系
總線是處理器和一個或多個設(shè)備之間的通道,在設(shè)備模型中,所有的設(shè)備都通過總線相連,甚至是內(nèi)部的虛擬"platform"總線??梢酝ㄟ^ls -l /sys/bus看到系統(tǒng)加載的所有總線。
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
總線可以相互插入。設(shè)備模型展示了總線和它們所控制的設(shè)備之間的實際連接。在Linux 設(shè)備模型中,總線由bus_type 結(jié)構(gòu)表示,定義在 :
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
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);
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 結(jié)構(gòu)體。只有很少的bus_type 成員需要初始化,大部分都由設(shè)備模型核心控制。但必須為總線指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
};
(2)調(diào)用bus_register函數(shù)注冊總線。int bus_register(struct bus_type *bus),該調(diào)用可能失敗,所以必須始終檢查返回值。
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
若成功,新的總線子系統(tǒng)將被添加進系統(tǒng),之后可以向總線添加設(shè)備。當必須從系統(tǒng)中刪除一個總線時,調(diào)用:
void bus_unregister(struct bus_type *bus);
2,總線方法
在 bus_type 結(jié)構(gòu)中定義了許多方法,它們允許總線核心作為設(shè)備核心與單獨的驅(qū)動程序之間提供服務(wù)的中介,主要介紹以下兩個方法: int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
對設(shè)備和驅(qū)動的迭代:若要編寫總線層代碼,可能不得不對所有已經(jīng)注冊到總線的設(shè)備或驅(qū)動進行一些迭代操作,這可能需要仔細研究嵌入到 bus_type 結(jié)構(gòu)中的其他數(shù)據(jù)結(jié)構(gòu),但最好使用內(nèi)核提供的輔助函數(shù):
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 *));
3,總線屬性
幾乎Linux 設(shè)備模型中的每一層都提供添加屬性的函數(shù),總線層也不例外。bus_attribute 類型定義在 如下:
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);
};
內(nèi)核提供了一個宏在編譯時創(chuàng)建和初始化bus_attribute 結(jié)構(gòu):
BUS_ATTR(_name,_mode,_show,_store)
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);
例如創(chuàng)建一個包含源碼版本號簡單屬性方法如下:
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s", 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");
之二:device
在最底層,Linux 系統(tǒng)中的每個設(shè)備由一個struct device 代表:
struct device
{
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent; * 設(shè)備的 "父" 設(shè)備,該設(shè)備所屬的設(shè)備,通常一個父設(shè)備是某種總線或者主控制器。如果 parent 是 NULL, 則該設(shè)備是頂層設(shè)備,較少見 */
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
const char *init_name;
struct device_type *type;
unsigned uevent_suppress:1;
struct semaphore sem;
struct bus_type *bus;
struct device_driver *driver;
void *driver_data;
void *platform_data;
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node;
#endif
u64 *dma_mask;
u64 coherent_dma_mask;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools;
struct dma_coherent_mem *dma_mem;
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
struct list_head node;
struct class *class;
dev_t devt;
struct attribute_group **groups;
void (*release)(struct device *dev);
};
(1)設(shè)備注冊
在注冊struct device 前,最少要設(shè)置parent, bus_id, bus, 和 release 成員,設(shè)備的注冊和注銷函數(shù)為:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
一個實際的總線也是一個設(shè)備,所以必須單獨注冊,以下為lddbus注冊它的虛擬總線設(shè)備:
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release");
}
struct device ldd_bus = {
.bus_id = "ldd0",
.release = ldd_bus_release
};
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE "Unable to register ldd0");
(2)設(shè)備屬性
sysfs 中的設(shè)備入口可有屬性,相關(guān)的結(jié)構(gòu)是:
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);
int device_create_file(struct device *device, struct device_attribute * entry);
void device_remove_file(struct device * dev, struct device_attribute * attr);
一個實例是:終端執(zhí)行:cd /sys/class/leds/lcd-backlight,
ls回顯:
uevent
subsystem
device
power
brightness
這些屬性可能都是通過device_create_file添加上去(至少brightness是這樣)。進入device目錄,再輸入pwd,
回顯:/sys/devices/platform/smdk-backlight。變換到devices目錄下了,可見設(shè)備模型的不同構(gòu)成是指向同一個設(shè)備的。
2.6下字符設(shè)備開始用struct cdev結(jié)構(gòu)體表示,但是我想調(diào)用device_create_file(dev, &dev_attr_debug);函數(shù)在/sys中導(dǎo)出信息,device_create_file()的第一個入口參數(shù)類型為struct device結(jié)構(gòu)體。問題是struct cdev與struct device這兩個結(jié)構(gòu)體沒有任何聯(lián)系的地方?答案是可以采用共同擁有的Kobjcet這個成員作為紐帶,所以從子類cdev--->父類kobject--->子類device,推導(dǎo)得到:container_of(kobj)-->list_entry(entry)->(struct device*) 。因為containerof是從結(jié)構(gòu)體指針成員找到結(jié)構(gòu)體地址,所以從cdev的kobj可以找到父類kobject的地址,而所有的kobject的entery都是在一個鏈表里面,遍歷這個鏈表,找到結(jié)構(gòu)體成員為特定device結(jié)構(gòu)的那一項。
(3)設(shè)備結(jié)構(gòu)的嵌入
device 結(jié)構(gòu)包含設(shè)備模型核心用來模擬系統(tǒng)的信息。但大部分子系統(tǒng)記錄了關(guān)于它們擁有的設(shè)備的額外信息,所以很少單純用device 結(jié)構(gòu)代表設(shè)備,而是通常將其嵌入一個設(shè)備的高層結(jié)構(gòu)體表示中。
lddbus 驅(qū)動創(chuàng)建了它自己的 device 類型(也即每類設(shè)備會建立自己的設(shè)備結(jié)構(gòu)體,其中至少一個成員是struct device類型,比如video_device),并期望每個設(shè)備驅(qū)動使用這個類型來注冊它們的設(shè)備:
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 導(dǎo)出的注冊和注銷接口如下:
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; //父設(shè)備
ldddev->dev.release = ldd_dev_release;
strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE); //設(shè)備名拷貝入device結(jié)構(gòu)體中
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
設(shè)備模型跟蹤所有系統(tǒng)已知的驅(qū)動,主要目的是使驅(qū)動程序核心能協(xié)調(diào)驅(qū)動和新設(shè)備之間的關(guān)系。一旦驅(qū)動在系統(tǒng)中是已知的對象就可能完成大量的工作。驅(qū)動程序的結(jié)構(gòu)體device_driver 定義如下:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name;
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)驅(qū)動程序的注冊和注銷
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
(2)驅(qū)動程序的屬性
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)
*屬性文件創(chuàng)建的方法:*/
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)驅(qū)動程序結(jié)構(gòu)的嵌入
對大多數(shù)驅(qū)動程序核心結(jié)構(gòu),device_driver 結(jié)構(gòu)通常被嵌入到一個更高層的、總線相關(guān)的結(jié)構(gòu)中。當然也有直接注冊驅(qū)動的,不用嵌入到高層結(jié)構(gòu)體。如driver_register(&wm97xx_driver)。
以lddbus 子系統(tǒng)為例,它定義了ldd_driver 結(jié)構(gòu):
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總線中相關(guān)的驅(qū)動注冊和注銷函數(shù)是:
static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct ldd_driver *ldriver = to_ldd_driver(driver);
sprintf(buf, "%s", ldriver->version);
return strlen(buf);
}
int register_ldd_driver(struct ldd_driver *driver) //device_driver被嵌入到更高層結(jié)構(gòu)體
{
int ret;
driver->driver.bus = &ldd_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name = "version";
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);
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);
在sculld 中創(chuàng)建的 ldd_driver 結(jié)構(gòu)如下:
static struct ldd_driver sculld_driver = {
.version = "$Revision: 1.21 $",
.module = THIS_MODULE,
.driver = {
.name = "sculld",
},
};
之四:class_register
類是一個設(shè)備的高層視圖,它抽象出了底層的實現(xiàn)細節(jié),從而允許用戶空間使用設(shè)備所提供的功能,而不用關(guān)心設(shè)備是如何連接和工作的。類成員通常由上層代碼所控制,而無需驅(qū)動的明確支持。但有些情況下驅(qū)動也需要直接處理類。
幾乎所有的類都顯示在/sys/class目錄中,可以通過ls -l /sys/class來顯示。出于歷史的原因,有一個例外:塊設(shè)備顯示在/sys/block目錄中。在許多情況,類子系統(tǒng)是向用戶空間導(dǎo)出信息的最好方法。當類子系統(tǒng)創(chuàng)建一個類時,它將完全擁有這個類,根本不用擔心哪個模塊擁有那些屬性,而且信息的表示也比較友好。為了管理類,驅(qū)動程序核心導(dǎo)出了一些接口,其目的之一是提供包含設(shè)備號的屬性以便自動創(chuàng)建設(shè)備節(jié)點,所以udev的使用離不開類。類函數(shù)和結(jié)構(gòu)與設(shè)備模型的其他部分遵循相同的模式,可與前三篇文章類比。
(1)管理類的接口和注冊注銷函數(shù)
類由 struct class 的結(jié)構(gòu)體來定義:
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)類設(shè)備,類存在的真正目的是給作為類成員的各個設(shè)備提供一個容器,成員由struct class_device 來表示,暫沒用到。
(3)類接口
類子系統(tǒng)有一個 Linux 設(shè)備模型的其他部分找不到的附加概念,稱為“接口”,可將它理解為一種設(shè)備加入或離開類時獲得信息的觸發(fā)機制,結(jié)構(gòu)體如下:
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);
設(shè)定class的好處:設(shè)備驅(qū)動一般在注冊的時候都會調(diào)用此類class的一些函數(shù),主要作用就是在sys目錄里面創(chuàng)建一些節(jié)點,比如cd到/sys/class下面可以看到這一類的設(shè)備,與這個相關(guān)的就是一些kobjects。當然對于一個新設(shè)備,可以注冊進一個class也可以不注冊進去,如果存在對應(yīng)class的話注冊進去更好。
評論