1. 程式人生 > >匯流排和裝置

匯流排和裝置

一、匯流排

匯流排是處理器與裝置之間的通道。在linux裝置模型中,所有的裝置都通過匯流排相連,匯流排可能是實際的匯流排,比如usb匯流排,pci匯流排,也可能是虛擬的匯流排。

1.1 資料結構

linux使用bus_type來表示匯流排。其定義如下:
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	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 (*resume)(struct device *dev);
	const struct dev_pm_ops *pm;
	struct iommu_ops *iommu_ops;
	struct subsys_private *p;
};
  • name:匯流排的名稱
  • dev_name:用於列舉匯流排下的裝置時的匯流排名稱的名稱。示例程式碼dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id)
  • bus_attrs:匯流排自己的一系列屬性。struct bus_attribute是一個自定義的sysfs屬性,其中包括了struct attribute以及該屬性的show和store方法。
  • dev_attrs:匯流排下裝置的一系列屬性。類似於匯流排自己的屬性,這也是一個自定義的sysfs屬性,包括了struct attribute以及該屬性的show和store方法。在bus_add_device->device_add_attrs中被加入dev目錄下。
  • drv_attrs:匯流排下驅動的一系列屬性。類似於匯流排自己的屬性和匯流排下的裝置屬性。在bus_add_driver->driver_add_attrs中被加入driver目錄下。
  • match:用於檢測指定的裝置是否可以被指定的驅動處理。在往該匯流排新增新的驅動或者裝置時會呼叫該函式。如果指定的裝置能夠被指定的驅動處理,則應該返回一個非0值,否則返回0。
  • uevent:用於處理uevent事件,典型的比如裝置的熱插拔。函式用於匯流排對uevent的環境變數新增,但在匯流排下裝置的dev_uevent處理函式也有對它的呼叫。
  • probe:當新的裝置或者驅動被新增到一個匯流排,並且match返回成功時會呼叫該函式對新的裝置進行初始化,實際上這是一個初始化函式。bus->probe和drv->probe中只會有一個生效,如果兩個都存在,則使用bus->probe。
  • remove:當從匯流排解除安裝裝置或者驅動時,會呼叫該函式。類似於probe,如果bus->remove和drv->remove都存在,則使用bus->remove。
  • shutdown:在所有裝置都關閉時會被呼叫。類似於remove和probe,如果bus->shutdown和drv->shutdown都存在,則使用bus->shutdown。
  • suspend:當總線上一個裝置想要休眠時呼叫該函式。
  • resume:用於喚醒總線上的一個休眠的裝置。
  • pm:電源管理的函式
  • iommu_ops:特定於該總想的和IOMMU 操作函式,用於將IOMMU驅動的實現和匯流排關聯起來,從而允許驅動在做IOMMU時可以進行特定於匯流排的操作。
  • p:只能由驅動核心使用的私有資料。
subsys_private定義如下:
struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset;
	struct list_head interfaces;
	struct mutex mutex;
	struct kset *drivers_kset;
	struct klist klist_devices;
	struct klist klist_drivers;
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;
	struct kset glue_dirs;
	struct class *class;
};
該結構將匯流排同裝置、驅動、class以及sysfs關聯了起來。其各個域的含義:
  • subsys:該匯流排在sysfs中的表示。
  • devices_kset:該匯流排目錄下的devices子目錄。
  • interfaces:該匯流排關聯的subsysfs_interface介面的連結串列頭。subsysfs_interface提供了一種向裝置新增介面的方式,這種介面往往代表一種特定的功能,它不控制裝置。其定義也很簡單,其中包括了一個add_dev的函式指標和一個remove_dev的函式指標,具體的可以參看實際的程式碼。
  • mutex:用於保護interfaces連結串列的互斥鎖
  • drivers_kset:該匯流排目錄下的drivers子目錄。
  • klist_devices:總線上的裝置連結串列。
  • klist_drivers:總線上的驅動連結串列。
  • bus_notifier:用於傳送匯流排變化通告的通知鏈。
  • driver_autoprobe:是否允許device和driver自動匹配,如果允許會在device或者driver註冊時就進行匹配工作。
  • bus:指向匯流排的bus_type型別。
  • class:指向該結構關聯的class。

1.2 新增、刪除匯流排

可以呼叫bus_register來新增一個匯流排到系統中,如果要從系統中刪除一個匯流排,需要呼叫bus_unregister。為了可以在sysfs中識別出來匯流排,在新增匯流排時要給它指定一個名字,新增匯流排可能失敗,所以需要檢查返回值。在註冊匯流排時,bus_register會完成:
  1. 建立並初始化匯流排的subsys_private
  2. 將匯流排的kset設定為bus_kset(即p->subsys.kobj.kset域)
  3. 將匯流排的kobj_type設定為bus_ktype(即p->subsys.kobj.ktype域)
  4. 預設使能驅動的自動探測(即設定p->drivers_autoprobe為1)
  5. 初始化匯流排的kset
  6. 在sysfs中為匯流排建立bus_attr_uevent檔案,該屬性檔案用於傳送uevent事件。
  7. 建立並初始化匯流排下的裝置的kset和驅動的kset
  8. 初始化匯流排下的裝置連結串列和驅動連結串列
  9. 如果支援熱插拔,則在sysfs中為匯流排建立bus_attr_drivers_probe和bus_attr_drivers_autoprobe屬性檔案,其中bus_attr_drivers_probe這個檔案用於在熱插拔時為指定名字的裝置查詢並載入驅動,實際上即是載入裝置(寫該檔案執行函式store_drivers_probe,它會試圖找到指定名字的裝置並找到其驅動)。而bus_attr_drivers_autoprobe用於修改匯流排的私有資料中的drivers_autoprobe,即是否允許自動載入裝置。
  10. 在sysfs中為匯流排結構中的其它匯流排屬性建立屬性檔案
刪除匯流排基本就是該過程的一個逆過程
匯流排的kset為全域性定義的bus_kset,它在匯流排系統初始化時(buses_init)被建立和初始化。buses_init完成的工作包括:
  1. 在sys下建立一個名為bus的目錄,其kset_uevent_ops被設定為bus_uevent_ops。bus_uevent_ops就提供了一個filter函式。
  2. 在sys下建立一個名為system的目錄,其kset_uevent_ops被設定為NULL,並且設定其父kobject為devices_kset的kobject。
匯流排的kobj_type是全域性定義的一個bus_ktype,它的show和store函式分別設定為bus_attr_show和bus_attr_store。這兩個函式的處理很簡單:
  1. 首先獲得bus_attribute
  2. 檢測是否有show(store)函式,如果有就呼叫。

1.3 匯流排的sysfs屬性

匯流排屬性定義如下
struct bus_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct bus_type *bus, char *buf);
	ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
該結構很簡單,沒什麼特別之處。匯流排系統提供了巨集BUS_ATTR用於頂一個匯流排屬性,其原型如下:
#define BUS_ATTR(_name, _mode, _show, _store) struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
linux裝置模型的各個部分都對在sysfs下新增屬性檔案做了封裝,匯流排系統提供的api如下:
int __must_check bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);
它們分別被用於向sysfs中新增屬性檔案和刪除屬性檔案,分別會呼叫sysfs框架的sysfs_create_file和sysfs_remove_file函式。

1.4 匯流排的其它操作

1.4.1 遍歷裝置或者驅動

為了方便使用,匯流排提供了兩個巨集,分別用於列舉匯流排下的裝置和驅動,其原型如下:
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 *))

1.4.2 查詢裝置

匯流排框架也提供了在匯流排下查詢裝置的功能,其原型如下:
struct device *bus_find_device(struct bus_type *bus, struct device *start, void *data, int (*match)(struct device *dev, void *data))

1.4.3 匯流排引用計數

匯流排的引用計數維護在匯流排的私有資料結構的kobject中,最終使用的是其私有資料結構中的kset的kobject中的引用計數。相關API如下:
struct bus_type *bus_get(struct bus_type *bus)
void bus_put(struct bus_type *bus)
引用計數是linux核心中廣泛使用的一項計數,並且實現都是類似的,這也使得我們只要瞭解其中一個引用計數的實現就可以瞭解其它引用計數的實現。這裡的原理也是相同的,不過需要說明的一點是,在為匯流排指定kobj_type時,bus_ktype並不包括一個用於釋放的函式,這可能是因為匯流排不支援熱插拔吧:))。

1.4.4 裝置連結串列的保護

匯流排下的裝置連結串列和驅動連結串列都是一個klist型別的連結串列,這種連結串列可以新增get和put的保護機制,匯流排為其中的裝置連結串列提供了保護函式。這些保護函式在對連結串列進行初始化時提供給了klist,然後在使用klist的api進行遍歷操作的時候,klist的程式碼會自動使用相關的get和put函式。匯流排為裝置連結串列提供的保護函式分別為:
void klist_devices_get(struct klist_node *n)
void klist_devices_put(struct klist_node *n)

二、裝置

在linux驅動模型中,struct device用於表示所有的裝置,裝置可能是真實的物理裝置,也可能是虛擬的裝置。

2.1 資料結構

struct device的定義如下:
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_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 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
	/* arch specific additions */
	struct dev_archdata	archdata;
	struct device_node	*of_node; /* associated device tree 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);
};
其中的關鍵成員及其含義如下:
  • parent:該裝置的父裝置,即該裝置所附著到的那個裝置。通常是某種匯流排或者控制器。如果某個裝置的父裝置被設定為NULL,則它為一個頂層裝置。
  • p: 該裝置的私有資料
  • kobj:代表該裝置的kobject,用於將該裝置新增到kobject層次結構中,通過它就將一個裝置新增到了sysfs中。通常,device->kobj->parent和device->parent->kobj是相同的。
  • init_name: 裝置的初始化名字
  • type: 裝置的型別標識。類似於kobject和kobject_type的關係,它儲存了某些型別的裝置所共有的資訊,這樣就不必為每個裝置指定這些共有資訊,而只需要指定一份,然後將裝置的type域指向相應的type即可。
  • mutex:用於互斥該裝置驅動的使用。
  • bus:該裝置位於那種總線上
  • driver:管理該裝置的驅動
  • platform_data: 特定於平臺的資料
  • power:用於電源管理的資料結構
  • pm_domain:用於電源管理的函式集
  • numa_node:該裝置使用的UMBA節點
  • of_node: 該裝置關聯的裝置樹節點
  • devt: 裝置的devt編號,用於在sysfs中建立dev
  • id: 裝置的例項編號
  • devres_lock: 用於保護該裝置所使用資源連結串列的自旋鎖
  • devres_head: 該裝置的資源連結串列
  • knode_class: 用於將裝置新增到它所屬的class的連結串列的節點
  • class: 該裝置所屬的class
  • groups: 裝置的可選屬性組,包括了裝置的可選屬性。具體的結構定義可以檢視device.h
  • release:當裝置的引用計數為0時,也就是不再需要改裝置時用於釋放該裝置的函式。裝置的relase函式會被device_release呼叫,而device_release是device_ktype的release函式。在裝置模型中,每個裝置在初始化時都會將device_ktype設定為自己的kobj_type,因而裝置模型的引用計數也可以使用struct device中的kobject進行,在kobject進行relase時會最終呼叫到裝置自己的release函式。
struct device_private定義如下:
struct device_private {
	struct klist klist_children;
	struct klist_node knode_parent;
	struct klist_node knode_driver;
	struct klist_node knode_bus;
	struct list_head deferred_probe;
	void *driver_data;
	struct device *device;
};
  • klist_children:該裝置的所有子裝置所在的連結串列
  • knode_parent:用於將該裝置連結到它的父裝置的子連結串列(即父裝置的klist_children)中
  • knode_driver:用於將該裝置連結到它所使用的驅動的裝置連結串列中
  • knode_bus:用於將該裝置連結到它所在匯流排的裝置連結串列中
  • deferred_probe:由於缺乏資源而沒有載入驅動的裝置連結串列
  • driver_data:用於裝置驅動的私有資料指標
  • device:指向該裝置的device結構
struct attribute_group定義如下
struct attribute_group {
	const char		*name;
	umode_t			(*is_visible)(struct kobject *, struct attribute *, int);
	struct attribute	**attrs;
};
  • name:屬性組的名字
  • is_visible:用於控制attrs中的某個屬性是否可見,即是否為其在sysfs中建立一個檔案,如果可見,它就返回建立的檔案的模式,因而它控制了屬性檔案的可見性以及訪問模式。
  • attrs:屬性陣列
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, mode_t *mode);  
    void (*release)(struct device *dev);    
    const struct dev_pm_ops *pm;  
};  
  • name:型別名字
  • goups:型別屬性陣列,會在device_add時被新增到sysfs中
  • uevent:該型別的uevent函式,用於傳送uevent時準備環境變數
  • devnode:獲得裝置的devnode
  • release:該型別的釋放函式
  • pm:該型別裝置的電源管理函式集

2.2 新增、刪除裝置

可以呼叫device_register來新增一個匯流排到系統中,如果要從系統中刪除一個匯流排,需要呼叫device_unregister。device_register需要一個struct device的指標作為引數,在呼叫它之前,應該保證除了顯式設定的域外其它域都為0.其完成的工作包括:
  • 呼叫device_initialize完成
    • 將裝置的kobj.kset設定為devices_kset
    • 將裝置的kobj的kobj_type設定為device_ktype,不同於匯流排子系統的是這裡的device_ktype不僅包括了屬性的讀寫函式還包括了包括了用於kobject釋放的relase函式。
    • 初始化裝置的其它域
  • 呼叫device_add完成
    • 獲取裝置的引用計數,如果失敗就返回,否則
    • 如果裝置的私有資料結構還沒有初始化,就呼叫device_private_init初始化
    • 設定裝置的名字(可能來自於初始化名字,也可能來自於匯流排的dev_name,初始化名字的優先順序更高)。如果沒有設定初始化名字,就會嘗試根據裝置所在的匯流排為裝置生成名字。如果設定名字失敗就會失敗返回。
    • 初始化裝置和其父裝置的關係
    • 呼叫kobject_add將裝置新增到sysfs中
    • 在sysfs中為裝置建立屬性檔案uevent_attr
    • 如果主裝置號不為0,則在sysfs中為其建立屬性檔案devt_attr
    • 在sysfs中建立相關的符號連結
    • 呼叫device_add_attrs在sysfs中為裝置新增屬性檔案
    • 呼叫bus_add_device將裝置新增到匯流排中
    • 呼叫dpm_sysfs_add在sysfs中未裝置建立電源管理相關的屬性檔案
    • 呼叫device_pm_add初始化裝置的電源管理資訊
    • 如果裝置是依附於匯流排的,就呼叫blocking_notifier_call_chain傳送匯流排變化通告
    • 呼叫kobject_uevent傳送KOBJ_ADD通知給使用者空間
    • 呼叫bus_probe_device為該裝置探測驅動
    • 如果該裝置有父裝置,就將該裝置新增到其父裝置的子裝置連結串列中
    • 如果裝置屬於某個class,則將裝置新增到該class的裝置連結串列中,並呼叫該class中的所有介面的add_dev函式。
    • 釋放裝置的引用計數

如果主裝置號不為0,則除了建立devt_attr屬性檔案外,還會做一些其它的工作:

  • 呼叫device_create_sys_dev_entry以在/sys/dev下為裝置建立相應的sysfs連線檔案,該函式會首先獲得該裝置的kobject,然後再建立。裝置真正的sysfs存在於/sys/devices下
  • 呼叫devtmpfs_create_node在/dev目錄下為裝置建立裝置檔案。這是通過向devtmpfsd核心執行緒傳送一個新增請求實現的。devtmpfsd是一個核心執行緒,用於維護基於tmpfs的/dev的內容。

刪除裝置基本上是新增裝置的逆過程。

devices_kset在裝置子系統初始化時(devices_init)建立並初始化,devices_init完成:
  • 建立並初始化kset devices_kset,其kset_uevent_ops被設定為device_uevent_ops,這會在/sys下建立一個devices目錄。device_uevent_ops包含了對相關的kobject進行uevent處理的相關函式。device_uevent_ops中的uevent事件處理函式被設定為dev_uevent,該函式會檢查並呼叫裝置所屬的匯流排、所屬的類以及裝置自身的uevent處理函式。
  • 建立並初始化kobject dev_kobj,這會在/sys下建立一個dev目錄。
  • 建立並初始化kobject sysfs_dev_block_kobj,其父目錄指定為dev_kobj,這會在/sys/dev下建立一個block目錄
  • 建立並初始化kobject sysfs_dev_char_kobj,其父目錄指定為dev_kobj,這會在/sys/dev下建立一個char目錄
device_ktype指定了kobject的釋放函式以及屬性的讀寫函式。其定義如下:
static struct kobj_type device_ktype = {
	.release	= device_release,
	.sysfs_ops	= &dev_sysfs_ops,
	.namespace	= device_namespace,
};
在device_release中,它會依次檢查裝置的release成員,裝置的type->release成員,裝置的class->dev_release並使用第一個找到的來完成釋放前的清理工作。最後再釋放資料結構自身。

屬性的讀寫函式也很簡單,就是查詢屬性自己的讀寫函式,然後呼叫即可。namespace用於獲取裝置的名稱空間,這是通過呼叫裝置所屬的class的名稱空間函式完成的。

除了上述API之外,裝置子系統還提供了一個簡單的API來建立裝置檔案,其定義如下:

struct device *device_create(struct class *class, struct device *parent,  dev_t devt, void *drvdata, const char *fmt, ...);

該函式會完成裝置資料結構的建立並初始化該裝置,同時將裝置新增到sysfs中,這個是最簡單的API了。

2.3 裝置的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);
};
該結構很簡單,沒有什麼特別之處。裝置系統提供了巨集DEVICE_ATTR用來定義一個裝置屬性,其原型如下:
#define DEVICE_ATTR(_name, _mode, _show, _store) struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
linux裝置模型的各個部分都對在sysfs下新增屬性檔案做了封裝,裝置系統提供的api如下:
int device_create_file(struct device *device, const struct device_attribute *entry);
void device_remove_file(struct device *dev, const struct device_attribute *attr);
int __must_check device_create_bin_file(struct device *dev,	const struct bin_attribute *attr);
void device_remove_bin_file(struct device *dev, const struct bin_attribute *attr);
它們分別被用於向sysfs中新增(二進位制)屬性檔案和刪除(二進位制)屬性檔案,分別會呼叫sysfs框架的sysfs_create(_bin)_file和sysfs_remove(_bin)_file函式。在驅動模型之中,只有裝置提供了二進位制屬性的封裝,也就是說只有裝置子系統支援二進位制屬性,這是因為二進位制屬性往往是用於韌體升級的目地,否則很少有需求產生一些不可讀的資訊,而韌體是和裝置緊密相關聯的,因而採用了這種設計。

2.4 裝置的其它操作

2.4.1 遍歷子裝置或者查詢子裝置

為了方便使用,裝置提供了查詢其子裝置以及遍歷子裝置連結串列的API。其原型如下:
int device_for_each_child(struct device *dev, void *data, int (*fn)(struct device *dev, void *data));
extern struct device *device_find_child(struct device *dev, void *data, int (*match)(struct device *dev, void *data));

2.4.2 裝置引用計數

裝置的引用計數維護在裝置的kobject中,最終使用的是其kobject中的引用計數。相關API如下:
struct device *get_device(struct device *dev)
void put_device(struct device *dev)
這就是一個常規的引用計數,沒什麼特別之處。

2.4.3 子裝置連結串列的保護

裝置私有資料結構中的子裝置連結串列是一個klist型別的連結串列,這種連結串列可以新增get和put的保護機制,裝置系統為該連結串列提供了保護函式。這些保護函式在對連結串列進行初始化時提供給了klist,然後在使用klist的api進行遍歷操作的時候,klist的程式碼會自動使用相關的get和put函式。相關保護函式為:
void klist_children_get(struct klist_node *n)
void klist_children_put(struct klist_node *n)

2.4.4 重新命名

如果要修改一個裝置的名字,可以使用如下API:
int device_rename(struct device *dev, const char *new_name);