一、前言
Linux內(nèi)核支持大量的硬件設(shè)備杨名,且這個(gè)數(shù)量一直在增加脏榆。那么代碼內(nèi)部的拓?fù)浜蛷?fù)雜性等都在急劇上升,這會(huì)導(dǎo)致代碼變得雜亂和提升管理難度台谍。為了做好設(shè)備驅(qū)動(dòng)的管理须喂,降低驅(qū)動(dòng)的開發(fā)難度,還要兼容設(shè)備的熱插拔和電源管理等,Linux內(nèi)核需要?dú)w納和分類硬件設(shè)備坞生,抽象出一套標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu)和接口仔役,而這就是 統(tǒng)一設(shè)備模型
二、抽象統(tǒng)一設(shè)備驅(qū)動(dòng)
我們可以通過下面這個(gè)圖來簡(jiǎn)單的理解內(nèi)核是如何組織設(shè)備及驅(qū)動(dòng)的
在圖中是己,我們可以抽象出幾個(gè)概念:總線(Bus)又兵、設(shè)備(Device)、驅(qū)動(dòng)(Driver) 和 類(Class)卒废。
- 總線:為了而昂 CPU 和 多個(gè) 設(shè)備 之間進(jìn)行信息交互的通道沛厨,由此抽象出總線。所有的設(shè)備都連接到 總線(無論CPU的外設(shè)總線和虛擬總線platform Bus) 上升熊。
- 類:Linux內(nèi)核中的 類 不是 對(duì)面對(duì)象程序設(shè)計(jì)中的類俄烁,而是指 具有相似功能或?qū)傩缘脑O(shè)備。由于設(shè)備之間的功能或?qū)傩韵嗤兑埃钥梢栽诙鄠€(gè)設(shè)備之間抽象出一套 統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)和接口页屠,這就是 類。從屬于 相同類的設(shè)備驅(qū)動(dòng)程序 就不再需要重復(fù)定義公共屬性蓖柔,直接從類中繼承即可辰企。
- 設(shè)備:將系統(tǒng)中所有硬件設(shè)備的共同屬性,比如 名字况鸣、 屬性牢贸、從屬總線 和 類 等信息抽象出來,即成為 設(shè)備
- 驅(qū)動(dòng):Linux內(nèi)核使用 驅(qū)動(dòng) 來描述硬件設(shè)備的驅(qū)動(dòng)程序镐捧,驅(qū)動(dòng)包含了 設(shè)備初始化潜索、電源管理接口 和 設(shè)備操作接口,而驅(qū)動(dòng)開發(fā)基本圍繞這些規(guī)定的接口進(jìn)行開發(fā)懂酱。
2.1 kobject
kobject 是 Linue統(tǒng)一模型 的基礎(chǔ)竹习,也是比較難理解的一個(gè)部分。前面提到的 4 個(gè)數(shù)據(jù)結(jié)構(gòu)可以將大量的硬件設(shè)備組織起來列牺,所以需要大量的數(shù)據(jù)結(jié)構(gòu)來描述 硬件整陌。這些 數(shù)據(jù)結(jié)構(gòu) 擁有一些共同的功能,為了將這些功能抽象統(tǒng)一瞎领,所以誕生了 kobject泌辫。
PS:每個(gè) kobject都會(huì)在 sysfs系統(tǒng) 以 目錄的形式 出現(xiàn)。
kobject 支持以下功能:
- 對(duì)象的引用計(jì)數(shù):用于跟蹤內(nèi)核對(duì)象的聲明周期九默。當(dāng)內(nèi)核中沒有驅(qū)動(dòng)或者代碼引用對(duì)象時(shí)好渠,說明對(duì)象的聲明周期已經(jīng)結(jié)束拳恋,可以刪除薪介。
- sysfs表述:sysfs 顯示的每一個(gè)對(duì)象都對(duì)應(yīng)一個(gè) kobject,用于應(yīng)用層與內(nèi)核進(jìn)行交互
- 數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián):在Linux內(nèi)核中會(huì)有大量的設(shè)備連接伍俘,由此會(huì)形成一個(gè) 多層次的體系結(jié)構(gòu),kobject 實(shí)現(xiàn)將多個(gè)設(shè)備組織起來并行程體系勉躺。
- 熱插拔事件:內(nèi)核中使用 kobject 實(shí)現(xiàn) 熱插拔功能癌瘾。當(dāng)系統(tǒng)中有硬件發(fā)生 熱插拔 時(shí),將產(chǎn)生事件并通知到 用戶空間
一般情況下饵溅,kobject 不會(huì)單獨(dú)出現(xiàn)妨退。它主要是嵌入到一個(gè)數(shù)據(jù)結(jié)構(gòu)中。把 高級(jí)對(duì)象 接入到 統(tǒng)一設(shè)備模型 中蜕企,比如 cdev咬荷。
struct cdev {
struct kobject kobj;//內(nèi)嵌到cdev中的kobject
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
}
內(nèi)核通過組織 kobject,將所有的內(nèi)核對(duì)象(包括上面提到的 4 個(gè)數(shù)據(jù)結(jié)構(gòu)) 組成成樹狀圖轻掩。通過訪問樹狀上的 kobject節(jié)點(diǎn) 即可訪問到指定 高級(jí)對(duì)象幸乒。
kobject 支持以下的一些基本接口:
struct kobject {
const char *name;//在sysfs中顯示的目錄名臣
struct list_head entry;//用于鏈入到kset
struct kobject *parent;//指向其parent kobject,由此形成層次結(jié)構(gòu)
struct kset *kset;//指向其所在的kset
struct kobj_type *ktype;//執(zhí)行該kobject所屬的ktype
......
unsigned int state_initialized:1;//指示該kobject是否已經(jīng)初始化
unsigned int state_in_sysfs:1;//指示該kobject是否已經(jīng)顯示在sysfs
unsigned int state_add_uevent_sent:1;//記錄是否已經(jīng)想用戶空間發(fā)送add uevent時(shí)間
unsigned int state_remove_uevent_sent:1;//記錄是否已經(jīng)想用戶空間發(fā)送remove uevent時(shí)間
unsigned int uevent_suppress:1;//如果該字段為1唇牧,則忽略所有需要上報(bào)的uevent時(shí)間
};
/*
補(bǔ)充:uevent提供了想用戶空間通知的功能罕扎。當(dāng)發(fā)生了kobjetct的添加、修改和刪除動(dòng)作時(shí)會(huì)通知用戶空間
*/
/* 初始化kobject丐重,將引用計(jì)數(shù)設(shè)置為1 */
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
/* 設(shè)置kobject的名字腔召,用于在sysfs中顯示 */
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
/* 增加kobject的引用計(jì)數(shù),引用成功將返回kobject的指針扮惦,否則返回NULL */
struct kobject *kobject_get(struct kobject *kobj)
/* 減少kobject的引用計(jì)數(shù),當(dāng)計(jì)數(shù)減小到為0時(shí)釋放該kobject */
void kobject_put(struct kobject *kobj)
2.2 kobj_type
kobject 大多數(shù)的使用場(chǎng)景是內(nèi)嵌在大型的數(shù)據(jù)結(jié)構(gòu)中崖蜜,如 kset、device_driver 等豫领。這些數(shù)據(jù)結(jié)構(gòu)是動(dòng)態(tài)分配和 動(dòng)態(tài)釋放 的。那么在什么時(shí)候釋放呢?每個(gè) kobject 需要在 引用計(jì)數(shù) 為 0 時(shí)進(jìn)行釋放搏明,但內(nèi)嵌著 kobject 的大型數(shù)據(jù)結(jié)構(gòu)如何釋放呢鼠锈?
ktype 中的 release回調(diào)函數(shù) 負(fù)責(zé) 釋放kobject(甚至是包含Kobject的數(shù)據(jù)結(jié)構(gòu)) 的內(nèi)存空間。為了能夠釋放上層的大型數(shù)據(jù)結(jié)構(gòu)星著,所以 ktype及其內(nèi)部函數(shù) 是由 上層數(shù)據(jù)結(jié)構(gòu) 所在的模塊實(shí)現(xiàn)购笆。因?yàn)橹挥兴约翰胖廊绾瓮ㄟ^ kobject指針 找到需要釋放的生成數(shù)據(jù)結(jié)構(gòu)指針,從而進(jìn)行釋放虚循。
kobject結(jié)構(gòu) 本身并沒有包含 release函數(shù)同欠,而是由一個(gè) kobj_type結(jié)構(gòu) 來負(fù)責(zé)對(duì)該類型進(jìn)行跟蹤样傍,我們也可以在 kobject結(jié)構(gòu) 中看到一個(gè)名為 ktype 的成員,其原型為:
struct kobj_type {
void (*release)(struct kobject *kobj);//kobject的release方法
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
2.3 kset
內(nèi)核通常會(huì)使用 kobject 將多個(gè)對(duì)象鏈接起來形成一個(gè)分層的結(jié)構(gòu)體系铺遂。有 2 種機(jī)制用于實(shí)現(xiàn)這種鏈接衫哥,即 parent指針 和 kset。
kobject結(jié)果 中的 parent成員 是一個(gè) kobject結(jié)構(gòu)類型的指針襟锐,指向了分成結(jié)構(gòu)總的上一層節(jié)點(diǎn)撤逢。對(duì) parent指針 來說,最重要的用途就是在 sysfs 中定位對(duì)象粮坞。
kset 是嵌入了 相同類型結(jié)構(gòu)的kobject集合蚊荣。從數(shù)據(jù)結(jié)構(gòu)來看,kset 像是 kobj_type結(jié)構(gòu) 的擴(kuò)充莫杈。但 kset 關(guān)心的是 對(duì)象的集合互例,而 kobj_type 關(guān)心的是 對(duì)象的類型。
其原型如下:
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
可以看到 kset 本身也內(nèi)嵌了一個(gè) kobject筝闹,所以 kobject 的操作也同樣適用于 kset媳叨。另外 kset 中的 list成員 用以鏈接 kobject,每個(gè)在 kset 中的 kobject元素 都會(huì)在 sysfs 中呈現(xiàn)丁存,前提是 kset 已經(jīng)被設(shè)置并添加到系統(tǒng)中肩杈。而單獨(dú)的 kojbect 未必在 sysfs 中出現(xiàn)。
kset 在一個(gè)標(biāo)準(zhǔn)的內(nèi)核鏈表中保存了它的子節(jié)點(diǎn)解寝,鏈表上的這些節(jié)點(diǎn)(即 kobject)會(huì)將它們的 parent指針 指向?qū)?yīng)的 kset 的 kobject成員聋伦,如下圖所示:
一般情況下:
- kobject 的 kset指針成員 指向了其所在的 kset觉增。
- kobject 的 parent指針成員 指向了其所在 kset 的 kobject指針成員逾礁。但未必每一個(gè) kobject 都未必是指向其所在的 kset嘹履,即使這種情況非常少見。
kset 中也有一個(gè) kobj_type指針成員幼苛,指向了對(duì)應(yīng)的 kobj_type結(jié)構(gòu)體舶沿。該 kobj_type結(jié)構(gòu)體 用來描述它所包含的 kobject。需要注意的是高镐,一般優(yōu)先使用 kobject 本身的 kobj_type成員避消,如果當(dāng) kobject 中的 kobj_type指針為空岩喷,則使用其所在的 kset 的 kobj_type成員监憎。
三、sysfs屬性文件
kobject 和 kset 都會(huì)在 sysfs機(jī)制 中顯現(xiàn)它們的功能作用偷霉。對(duì)于每個(gè) kobject褐筛,在 sysfs 中都有對(duì)應(yīng)的目錄渔扎,每個(gè)目下的文件對(duì)應(yīng)為 kobject 的屬性晃痴,這些內(nèi)容有內(nèi)核實(shí)現(xiàn)。
只要我們往內(nèi)核中添加一個(gè) kobject泣侮,sysfs 也會(huì)顯示對(duì)應(yīng)的目錄活尊,對(duì)于 kset 也是同理漏益。關(guān)于 sysfs 與 kset遭庶、kobject 的關(guān)系大致總結(jié)如下:
- kobject 在 sysfs 中的表示始終是一個(gè)目錄峦睡。因此,往系統(tǒng)添加 kobject 時(shí)將會(huì)在 sysfs 中創(chuàng)建一個(gè)目錄煎谍,該目錄下一般含有一個(gè)或多個(gè)屬性
- 分配給 kobject 名字就是在 sysfs 中的 目錄名呐粘。因此作岖,在 sysfs 處于同一層的 kobject 名字必須是 唯一的五芝,且分配給 kobject 的名字必須是合法的 目錄名(比如不能含 反斜杠)。
- kobject 在 sysfs 中的位置與其 parent指針 有關(guān)沉删。如果其 parent指針為空而kset指針不為空 矾瑰,則它會(huì)被添加到其所在 kset 所在的目錄層次殴穴。如果 parent和kset都為空嵌屎,則會(huì)被創(chuàng)建在 sysfs的 最高層。
前面說到 kobject 可以擁有許多 屬性植榕,這些 屬性 保存在其 kobj_type成員 中尊残,如下:
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;//屬性列表
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
struct attribute {
const char *name;//屬性名寝衫,該名字會(huì)在sysfs中顯示
umode_t mode;//屬性的保護(hù)位慰毅,通常有只讀S_IRUGO汹胃、只寫S_IWUSR和讀寫等幾種操作模式,相似可以查看<linux/stat.h>
}
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
可以在 kobj_type 中看到 default_attrs成員 和 sysfs_ops成員 :
- default_attrs:是一個(gè) 二級(jí)指針犀农,用于保存由一個(gè)或多個(gè)屬性組成的 屬性列表呵哨。
- sysfs_ops:提供 屬性 的實(shí)現(xiàn)方法孟害,有 show 和 store 兩種方法纹坐。
根據(jù) LDD3 的說法:當(dāng)用戶在應(yīng)用層對(duì) 屬性文件 進(jìn)行讀寫時(shí)耘子,對(duì)調(diào)用 sysfs_ops 中的方法谷誓,并將屬性對(duì)應(yīng)的指針傳遞給方法捍歪。從而可以讓 sysfs_opfs 判斷當(dāng)前讀寫的是屬性從而進(jìn)行相關(guān)的操作糙臼。
根據(jù)筆者的理解变逃,這種用法目前比較少見揽乱,在常見的驅(qū)動(dòng)框架中添加屬性可以通過其他方式凰棉。
- 使用 內(nèi)核宏 定義 屬性 并實(shí)現(xiàn) show方法 和 store方法
- 使用將定義好的屬性組成為 屬性列表
- 定義一個(gè) 屬性組撒犀,將 屬性列表 添加進(jìn) 屬性組
- 將 屬性組 注冊(cè)進(jìn)某一 驅(qū)動(dòng)框架 下或舞,由內(nèi)核生成 sysfs 下的屬性節(jié)點(diǎn)
下面為內(nèi)核中常見的屬性定義宏
/* from <include/linux/device.h> */
/* 對(duì)設(shè)備的使用 */
#define DEVICE_ATTR(_name, _mode, _show, _store) struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* 對(duì)總線使用 */
#define BUS_ATTR(_name, _mode, _show, _store) struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* 對(duì)類使用 */
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), _name##_show, _name##_store)
#define CLASS_ATTR_RW(_name) struct class_attribute class_attr_##_name = __ATTR_RW(_name)
/* 對(duì)設(shè)備使用 */
#define DRIVER_ATTR_RW(_name) struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
/* 生成屬性組 */
#define ATTRIBUTE_GROUPS(_name) \
static const struct attribute_group _name##_group = { \
.attrs = _name##_attrs, \
};
四、總線、設(shè)備和驅(qū)動(dòng)
編寫或者移植 設(shè)備驅(qū)動(dòng) 時(shí)魏宽,我們一般不需要直接接觸 內(nèi)核總線類型队询,一般總線驅(qū)動(dòng)在芯片廠家提供的 bsp或sdk 中蚌斩,我們直接使用即可送膳。本節(jié)內(nèi)容主要是為了梳理內(nèi)核 統(tǒng)一設(shè)備模型 的架構(gòu)丑蛤,進(jìn)一步理解內(nèi)核受裹。
4.1 總線
總線 的用于在多個(gè)設(shè)備之間進(jìn)行連接棉饶,以互相通信照藻。而在 統(tǒng)一設(shè)備模型 中岩梳,總線(Bus) 本身也是一類特殊的 設(shè)備冀值,它用于連接 處理器 和 外設(shè)滑蚯。Linux內(nèi)核 規(guī)定每個(gè)設(shè)備都需要掛接在一個(gè) 總線(Bus) 上。當(dāng)然了坤次,這個(gè) 總線 不一定就是實(shí)際存在的總線(如 I2C、USB等)疤剑,也有可能是虛擬總線(如 platform Bus)隘膘。
在 統(tǒng)一設(shè)備模型 中纵势,使用以下結(jié)構(gòu)來描述 總線:
/* from <inlcude/linux/device.h> */
struct bus_type {
/* 總線名稱 */
const char *name;
/* 總線上的設(shè)備名稱钦铁,當(dāng)總線上的設(shè)備沒有命名時(shí)將使用該成員 */
const char *dev_name;
/* 總線匹配device和driver時(shí)的回調(diào)函數(shù)育瓜,如果匹配成功返回非0值以便后續(xù)進(jìn)行處理 */
int (*match)(struct device *dev, struct device_driver *drv);
/*
一個(gè)由具體的bus driver實(shí)現(xiàn)的回調(diào)函數(shù)躏仇。
當(dāng)任何屬于該Bus的device焰手,發(fā)生添加怀喉、移除或者其它動(dòng)作時(shí)躬拢,
Bus模塊的核心邏輯就會(huì)調(diào)用該接口聊闯,以便bus driver能夠修改環(huán)境變量
*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
/*
如果該總線上的device需要probe話菱蔬,需要保證該device所在的bus是被初始化過篷帅、確保能正確工作的史侣。
所以需要在執(zhí)行driver的probe前,先執(zhí)行它所在bus的probe魏身。
remove的過程相反惊橱。
*/
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
/*
一些電源相關(guān)的接口
*/
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
/* bus自身的私有數(shù)據(jù) */
struct subsys_private *p;
};
struct subsys_private {
/* subsys是該總線在sysfs下的表現(xiàn)形式,代表了該總線 */
struct kset subsys;
/*
devices_kset和drivers_kset則是bus下面的兩個(gè)kset
分別包括該bus下所有的device和device_driver箭昵。
*/
struct kset *devices_kset;
struct kset *drivers_kset;
/*
klist_devices和klist_drivers是兩個(gè)鏈表税朴,
分別保存了本bus下所有的device和device_driver的指針,以方便查找掉房。
*/
struct klist klist_devices;
struct klist klist_drivers;
/* 用于控制該bus下的drivers或者device是否自動(dòng)probe */
unsigned int drivers_autoprobe:1;
/* bus和class指針诅病,分別保存上層的bus或者class指針讨阻。 */
struct bus_type *bus;
struct class *class;
};
Linux內(nèi)核總線 一般有以下的行為及功能:
- 完成 總線bus 的 注冊(cè) 和 注銷
- 實(shí)現(xiàn)該 總線bus 的 device 或者 device_driver 的添加和刪除(在 添加 時(shí)會(huì)在 device 在 sysfs 中的位置鏈接到該 總線bus 下的 device目錄。而在設(shè)備的 sysfs 目錄中也會(huì)創(chuàng)建一個(gè)鏈接指向其所屬的 bus目錄)
- 實(shí)現(xiàn)該 總線bus 的 device_drivers 的 probe
- 管理 總線bus 下的所有 device 和 device_driver
值得注意的是,bus 中的 drivers_autoprobe變量(默認(rèn)為1),用于控制 是否在device或者driver注冊(cè)時(shí),自動(dòng)probe宋距。bus模塊 將它開放到 sysfs 中了沸版,因而可在 用戶空間 修改细办,進(jìn)而控制 probe行為钓觉。
4.2 設(shè)備和驅(qū)動(dòng)
在 Linux 中設(shè)備和驅(qū)動(dòng)往往是分不開批幌。通常來講,設(shè)備更多的是表現(xiàn)為 硬件信息信姓,而驅(qū)動(dòng)表現(xiàn)為 驅(qū)動(dòng)代碼从诲。下面將分別對(duì)這 2 個(gè)基本單位進(jìn)行講述。
在 Linux 底層定页,每一個(gè)設(shè)備都使用 device結(jié)構(gòu) 來描述,該結(jié)構(gòu)非常復(fù)雜,這里參照參考鏈接中的文章做一些省略:
struct device {
/* 設(shè)備的父設(shè)備,一般是指其所在的總線或者控制器 */
struct device *parent;
/* 私有數(shù)據(jù)指針,用于保運(yùn)子設(shè)備鏈表 */
struct device_private *p;
/* 設(shè)備對(duì)應(yīng)的kobject */
struct kobject kobj;
/* 設(shè)備初始化名稱 */
const char *init_name;
/* type類似于kobject和kobj_type的關(guān)系 */
const struct device_type *type;
/* 該設(shè)備所屬的總線 */
struct bus_type *bus;
/* 該設(shè)備所屬的驅(qū)動(dòng) */
struct device_driver *driver;
/*
一般用于保存具體的驅(qū)動(dòng)數(shù)據(jù)附井。某些驅(qū)動(dòng)程序可以將一些私有的數(shù)據(jù)暫存在這里卷雕。
需要使用的時(shí)候再拿出來峰鄙,因此設(shè)備并不關(guān)心該指針的實(shí)際含義
*/
void *platform_data;
/* 大家所熟悉的設(shè)備號(hào) */
dev_t devt;
/* 設(shè)備所屬的類 */
struct class *class;
/* 設(shè)備的屬性組吩翻,會(huì)在sysfs下顯示 */
const struct attribute_group **groups;
};
/*
device_type是內(nèi)嵌在struct device結(jié)構(gòu)中的一個(gè)數(shù)據(jù)結(jié)構(gòu),用于指明設(shè)備的類型雪侥。
提供一些額外的輔助功能。
*/
struct device_type {
/*
表示device_type的名稱襟己。
當(dāng)帶有該類型的設(shè)備添加到內(nèi)核時(shí),內(nèi)核會(huì)發(fā)出 DEVTYPE=name類型的uevent仿吞。
用以告知用戶空間某個(gè)類型的設(shè)備available了
*/
const char *name;
/*
帶有該類型設(shè)備的屬性組你虹。
設(shè)備注冊(cè)時(shí)傅物,會(huì)同時(shí)注冊(cè)這些attribute。跟device本身的groups類似。
*/
const struct attribute_group **groups;
/*
所有相同類型的設(shè)備谴供,會(huì)有一些共有的uevent需要發(fā)送,由ueventh函數(shù)實(shí)現(xiàn)
*/
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode,
kuid_t *uid, kgid_t *gid);
/*
如果device本身沒有注冊(cè)release接口,就要查詢它所屬的類型是否有提供。
用于釋放device變量所占的空間
*/
void (*release)(struct device *dev);
};
而每個(gè)驅(qū)動(dòng)怎用 device_driver 來描述蛮瞄,如下所示:
struct device_driver {
/* 驅(qū)動(dòng)名,用于匹配設(shè)備 */
const char *name;
/* 設(shè)備所屬總線 */
struct bus_type *bus;
/*
是否啟動(dòng)sysfs中的bind和unbind attribute機(jī)制伺糠,
該機(jī)制可以在用戶空間手動(dòng)為驅(qū)動(dòng)解綁/綁定指定的設(shè)備
*/
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
/*
probe函數(shù)是匹配到設(shè)備后需要執(zhí)行的函數(shù)
remove函數(shù)是移除設(shè)備是需要執(zhí)行的函數(shù)
*/
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
/*
shutdown、suspend和resume是電源管理相關(guān)的函數(shù)
*/
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
/* 驅(qū)動(dòng)的屬性組灶挟,在sysfs下顯示 */
const struct attribute_group **groups;
/* 私有數(shù)據(jù)指針 */
struct driver_private *p;
};
在設(shè)備模型框架下墅垮,一般開發(fā)包括 2 個(gè)步驟:
- 分配一個(gè) struct device類型 的變量并填充必要的信息后算色,把它注冊(cè)到內(nèi)核中拉鹃。按著筆者的理解腻脏,在帶有 設(shè)備樹 和 platform driver 的情況下肯腕,一般會(huì)在設(shè)備初始化時(shí)由系統(tǒng)轉(zhuǎn)化為 設(shè)備數(shù)據(jù)結(jié)構(gòu)献宫。
- 分配一個(gè) struct device_driver類型 的變量并填充必要的信息后,把它注冊(cè)到內(nèi)核中实撒。
- 實(shí)現(xiàn) driver 的 probe姊途、remove等函數(shù),從而觸發(fā) 初始化和移除等操作知态。
關(guān)于 probe 的執(zhí)行時(shí)機(jī)捷兰,筆者介紹 I2C框架 的文章里面有做一些流程說明,有興趣的讀者可以前往閱讀负敏。當(dāng)然了贡茅,那只是一個(gè)例子,只是以點(diǎn)見面其做,幫助大家理解友扰。
其實(shí) device 和 device_driver 很少會(huì)直接使用彤叉,一般都會(huì)在其上封裝一層數(shù)據(jù)結(jié)構(gòu),比如 platform_device村怪。
這里需要主題的是秽浇,device 和 device_driver 必須掛在同一個(gè) bus 下。這樣才可以觸發(fā) probe 等函數(shù)甚负。一般當(dāng)系統(tǒng)初始化時(shí)會(huì)根據(jù) 設(shè)備樹 的解析結(jié)果柬焕,將相應(yīng)的掛到某一條 總線 下,該 總線 可以是虛擬的或者實(shí)際存在的梭域,通常是 platform_bus斑举,而設(shè)備會(huì)被解析成 platform_device。而在編寫驅(qū)動(dòng)時(shí)我們則是對(duì) platform_driver 進(jìn)行操作病涨。
五富玷、類
類(class) 是一種 設(shè)備 的高層視圖,它抽象出底層的實(shí)現(xiàn)細(xì)節(jié)既穆。它將一系列功能類似的設(shè)備抽象出來赎懦,比如一些相似的 設(shè)備 需要向用戶空間提供相似的 接口,如果每個(gè)將設(shè)備的驅(qū)動(dòng)都實(shí)現(xiàn)一遍的話幻工,就會(huì)導(dǎo)致內(nèi)核有大量的冗余代碼励两,這就是極大的浪費(fèi)。此時(shí)類就可以幫助我們對(duì)設(shè)備進(jìn)行抽象囊颅,節(jié)省代碼当悔。
所有的類都在 /sys/class
目錄下,class 的代碼結(jié)構(gòu)如下:
struct class {
/* 類的名稱踢代,顯示在/sys/class/目錄下 */
const char *name;
struct module *owner;
class_atrrs盲憎,x。
/* 類的默認(rèn)屬性組胳挎,會(huì)在類注冊(cè)到內(nèi)核時(shí)饼疙,會(huì)自動(dòng)在/sys/class/xxx_class下創(chuàng)建對(duì)應(yīng)的屬性文件 */
const struct attribute_group **class_groups;
/* 該類下每個(gè)設(shè)備的的默認(rèn)屬性組,會(huì)在類注冊(cè)到內(nèi)核時(shí)串远,會(huì)自動(dòng)在該類下的設(shè)備目錄創(chuàng)建對(duì)應(yīng)的屬性文件 */
const struct attribute_group **dev_groups;
/*
表示該類下的設(shè)備在/sys/dev/下的目錄
現(xiàn)在一般有char和block兩個(gè)宏多,如果dev_kobj為NULL儿惫,則默認(rèn)選擇char
*/
struct kobject *dev_kobj;
/* 當(dāng)該類類下有設(shè)備發(fā)生變化時(shí)澡罚,會(huì)調(diào)用類的uevent回調(diào)函數(shù) */
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
/* 用于release類自身的回調(diào)函數(shù)。 */
void (*class_release)(struct class *class);
/*
該回調(diào)函數(shù)用于release該類下的設(shè)備肾请。在device_release接口中留搔,會(huì)依次檢查device、device_type以及device所屬的類是否注冊(cè)release接口铛铁,如果有則調(diào)用相應(yīng)的release接口release設(shè)備指針隔显。
*/
void (*dev_release)(struct device *dev);
};
類 提供了在 設(shè)備加入或這離開類時(shí)獲取信息 的觸發(fā)機(jī)制却妨,該機(jī)制稱為 類接口,其原型如下:
struct class_interface {
struct list_head node;
struct class *class;
/*
當(dāng)設(shè)備加入類中括眠,都將調(diào)用下面的add或remove接口彪标。
當(dāng)加入時(shí),函數(shù)為設(shè)備做一些其他的必要設(shè)置掷豺,通常不是添加屬性捞烟,但也可以做其他工作
當(dāng)離開時(shí),可以做一些必要的清理工作
*/
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
類 在 sysfs 下的處理邏輯一般表現(xiàn)為:
- 在
/sys/class/
目錄下当船,創(chuàng)建一個(gè) 類 的目錄 - 在 類目錄 下题画,創(chuàng)建每一個(gè)屬于該類的 設(shè)備符號(hào)鏈接,通過這種方式可以在該 類 下的目錄訪問屬于該類的 設(shè)備的所有屬性
- 同時(shí)德频,設(shè)備在 sysfs 中也會(huì)創(chuàng)建一個(gè)符號(hào)鏈接來鏈接到 所屬類 的目錄
六苍息、結(jié)語
本文簡(jiǎn)單的說明了 Linux統(tǒng)一設(shè)備類型 的基本結(jié)構(gòu),以此增進(jìn)對(duì)設(shè)備驅(qū)動(dòng)的理解壹置。通過對(duì)設(shè)備類型的架構(gòu)理解竞思,可以比較清楚的知道Linux如何組織設(shè)備及驅(qū)動(dòng),有助于我們理解驅(qū)動(dòng)設(shè)備模塊的編寫蒸绩。在編寫本文時(shí)結(jié)合了 《LLD3》 中的內(nèi)容衙四,書中的內(nèi)容已經(jīng)落后于現(xiàn)在的 Linux源碼,所以沒有把各個(gè)接口都羅列出來患亿。讀者們可以去閱讀 include/linux/device.h
頭文件來獲取接口的相關(guān)知識(shí)传蹈。這篇文章鴿了挺久的,因?yàn)樽罱P者事情比較多步藕,所以產(chǎn)出速度也慢一些惦界,可能質(zhì)量也會(huì)受到一些影響。希望讀者多多包涵咙冗,如果有錯(cuò)誤的地方也歡迎指正沾歪。
七、參考鏈接
- 統(tǒng)一設(shè)備模型
- 《LLD3》