官术网_书友最值得收藏!

2.6 驅(qū)動(dòng)模型對(duì)象

在上面介紹了內(nèi)核對(duì)象和syfs文件系統(tǒng),本節(jié)分析Linux驅(qū)動(dòng)模型。Linux驅(qū)動(dòng)模型適用于Linux各種子系統(tǒng),它描述了總線類(lèi)型、設(shè)備、驅(qū)動(dòng)以及類(lèi)和接口之間的關(guān)系。每個(gè)子系統(tǒng)都有屬于自己的唯一的總線類(lèi)型,它上面鏈接了注冊(cè)到這一總線類(lèi)型上的多個(gè)設(shè)備,另外它還將注冊(cè)到這個(gè)總線類(lèi)型的多個(gè)驅(qū)動(dòng)鏈接在一起。每個(gè)設(shè)備可能暫時(shí)還未綁定到驅(qū)動(dòng),或者可能被綁定到最多一個(gè)驅(qū)動(dòng)。而驅(qū)動(dòng)則可以暫時(shí)未綁定任何設(shè)備,也可能已經(jīng)綁定了一個(gè)或多個(gè)設(shè)備。

總線類(lèi)型、設(shè)備、驅(qū)動(dòng)這三者的關(guān)系,使得子系統(tǒng)支持熱插拔變得非常容易。早期的計(jì)算機(jī)系統(tǒng)不支持熱插拔,驅(qū)動(dòng)必須在設(shè)備被發(fā)現(xiàn)之前已經(jīng)被加載。而支持熱插拔的系統(tǒng)還可以允許在驅(qū)動(dòng)已經(jīng)加載的情況下安裝設(shè)備。事實(shí)上,Linux驅(qū)動(dòng)模型的處理方式是,在設(shè)備被發(fā)現(xiàn)時(shí),它被加入所注冊(cè)總線類(lèi)型的設(shè)備鏈表,然后在該總線類(lèi)型的驅(qū)動(dòng)鏈表中查找可以處理這個(gè)設(shè)備的驅(qū)動(dòng)并綁定。類(lèi)似地,在驅(qū)動(dòng)被加載時(shí),被加入到它所注冊(cè)的總線類(lèi)型的驅(qū)動(dòng)鏈表,同時(shí)在該總線類(lèi)型的設(shè)備鏈表中查找并綁定可以處理的設(shè)備。顯然,在這樣的實(shí)現(xiàn)思路下,設(shè)備發(fā)現(xiàn)和驅(qū)動(dòng)加載的順序不再那么重要了。

從上面的描述我們看到,設(shè)備最多可以屬于一個(gè)驅(qū)動(dòng)。在某些情況下,這個(gè)限制過(guò)于嚴(yán)苛。比如,SCSI磁盤(pán),它作為一個(gè)磁盤(pán)類(lèi)設(shè)備,被唯一地綁定到SCSI磁盤(pán)驅(qū)動(dòng)。但是,有時(shí)我們希望把它作為一個(gè)普通的SCSI設(shè)備,向它發(fā)送INQUIRY等SCSI命令。Linux驅(qū)動(dòng)模型對(duì)此的解決方案是,引入類(lèi)和接口。每個(gè)設(shè)備還可以唯一屬于某個(gè)類(lèi),設(shè)備被鏈入到類(lèi)的設(shè)備鏈表。類(lèi)還有另一個(gè)接口鏈表,用于鏈接注冊(cè)到該類(lèi)的所有接口。在設(shè)備被發(fā)現(xiàn),或接口被添加時(shí),無(wú)論何種順序,只要它們屬于同一個(gè)類(lèi),那么接口注冊(cè)的回調(diào)函數(shù)都會(huì)被作用在設(shè)備之上。

Linux驅(qū)動(dòng)模型實(shí)現(xiàn)的對(duì)象關(guān)系如圖2-11所示,圖中各種關(guān)系的功能如下。

圖2-11 Linux驅(qū)動(dòng)模型對(duì)象之間的關(guān)系

? bus_type和bus_type_private共同描述總線類(lèi)型。

? device_driver和driver_private共同描述驅(qū)動(dòng)。

? device和device_private共同描述設(shè)備。

? class和class_private共同描述類(lèi)。

? class_interface表示的接口。

2.6.1 總線類(lèi)型

Linux驅(qū)動(dòng)模型中的總線類(lèi)型結(jié)構(gòu)使用bus_type和bus_type_private兩個(gè)結(jié)構(gòu)體來(lái)表示。之所以這樣,純粹是從編程的角度來(lái)考慮的,程序員在實(shí)現(xiàn)一個(gè)新的總線類(lèi)型時(shí),只需要定義bus_type結(jié)構(gòu)體,并實(shí)現(xiàn)其中的方法和函數(shù),而不必關(guān)注那些完全被驅(qū)動(dòng)模型核心使用的內(nèi)部域。

但是,需要注意的是,這兩個(gè)結(jié)構(gòu)體是同時(shí)存在的,并且有一一對(duì)應(yīng)關(guān)系。事實(shí)上,兩個(gè)結(jié)構(gòu)體各自有一個(gè)指針域指向?qū)Ψ健us_type結(jié)構(gòu)中的域和bus_type_private結(jié)構(gòu)中的域分別如表2-12和表2-13所示。

表2-12 bus_type結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)

表2-13 bus_type_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)

每個(gè)總線類(lèi)型被注冊(cè)后,將在/sys/bus下創(chuàng)建一個(gè)和其名字對(duì)應(yīng)的子目錄,在該子目錄下又有兩個(gè)子目錄:drivers和devices。和總線類(lèi)型子目錄對(duì)應(yīng)的內(nèi)嵌的kset,即subsys域,而drivers_kset和devices_kset是指向和drivers子目錄以及devices子目錄對(duì)應(yīng)的kset的指針。

總線類(lèi)型有兩個(gè)鏈表:一個(gè)是在該總線類(lèi)型上發(fā)現(xiàn)的設(shè)備鏈表,另一個(gè)是在該總線類(lèi)型上加載的驅(qū)動(dòng)鏈表。注意這里用作鏈表的表頭和連接件結(jié)構(gòu)分別為klist和klist_node,它們實(shí)際上也是Linux內(nèi)核雙循環(huán)鏈表結(jié)構(gòu)list_head的封裝,具體實(shí)現(xiàn)的分析留給讀者。

某些子系統(tǒng)(或模塊)會(huì)定義自己的總線類(lèi)型,這時(shí)初始化的一個(gè)主要工作就是向Linux內(nèi)核注冊(cè)其總線類(lèi)型,對(duì)于某些子系統(tǒng)(或模塊),它甚至是初始化的全部工作。

Linux驅(qū)動(dòng)模型核心提供了bus_register函數(shù)(其代碼如程序2-12所示),被用來(lái)注冊(cè)總線類(lèi)型。子系統(tǒng)(或模塊)調(diào)用該函數(shù)前,必須已經(jīng)聲明了一個(gè)bus_type類(lèi)型的變量,調(diào)用時(shí)將其指針作為參數(shù)傳入。

程序2-12 函數(shù)bus_register()代碼(摘自文件drivers/base/bus.c)

bus_register()

880int bus_register(struct bus_type *bus)
881{
882    int retval;
883    struct bus_type_private *priv;
884
885    priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
886    if (!priv)
887        return -ENOMEM;
888
889    priv->bus = bus;
890    bus->p = priv;
891
892    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
893
894    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
895    if (retval)
896        goto out;
897
898    priv->subsys.kobj.kset = bus_kset;
899    priv->subsys.kobj.ktype = &bus_ktype;
900    priv->drivers_autoprobe = 1;
901
902    retval = kset_register(&priv->subsys);
903    if (retval)
904        goto out;
905
906    retval = bus_create_file(bus, &bus_attr_uevent);
907    if (retval)
908        goto bus_uevent_fail;
909
910    priv->devices_kset = kset_create_and_add("devices", NULL,
911                         &priv->subsys.kobj);
912    if (!priv->devices_kset) {
913        retval = -ENOMEM;
914        goto bus_devices_fail;
915   }
916
917    priv->drivers_kset = kset_create_and_add("drivers", NULL,
918                         &priv->subsys.kobj);
919    if (!priv->drivers_kset) {
920        retval = -ENOMEM;
921        goto bus_drivers_fail;
922   }
923
924    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
925    klist_init(&priv->klist_drivers, NULL, NULL);
926
927    retval = add_probe_files(bus);
928    if (retval)
929        goto bus_probe_files_fail;
930
931    retval = bus_add_attrs(bus);
932    if (retval)
933        goto bus_attrs_fail;
934
935    pr_debug("bus: '%s': registered\n", bus->name);
936    return 0;
937
938bus_attrs_fail:
939    remove_probe_files(bus);
940bus_probe_files_fail:
941    kset_unregister(bus->p->drivers_kset);
942bus_drivers_fail:
943    kset_unregister(bus->p->devices_kset);
944bus_devices_fail:
945    bus_remove_file(bus, &bus_attr_uevent);
946bus_uevent_fail:
947    kset_unregister(&bus->p->subsys);
948    kfree(bus->p);
949out:
950    bus->p = NULL;
951    return retval;
952}

總線類(lèi)型注冊(cè)的主要任務(wù)是分配和初始化相關(guān)的結(jié)構(gòu),以及在sysfs文件系統(tǒng)中創(chuàng)建相應(yīng)的子目錄樹(shù)。

我們說(shuō)過(guò),在調(diào)用這個(gè)函數(shù)前,用戶已經(jīng)聲明了一個(gè)bus_type類(lèi)型的變量,但是bus_type_private結(jié)構(gòu)的空間還沒(méi)有分配,第885行分配之。第889和890行讓兩個(gè)結(jié)構(gòu)相互關(guān)聯(lián)起來(lái)。

接下來(lái)對(duì)總線類(lèi)型私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kset的內(nèi)嵌kobject進(jìn)行初始化,包括如下內(nèi)容。

? 設(shè)置其名字為總線類(lèi)型名字(第894行)。

? 設(shè)置其kset為bus_kset,而后者在系統(tǒng)初始化過(guò)程的buses_init函數(shù)(見(jiàn)文件drivers/base/bus.c)中調(diào)用kset_create_and_add創(chuàng)建,它在sysfs文件系統(tǒng)中對(duì)應(yīng)的目錄為sys/bus,它的kset_uevent_ops為bus_uevent_ops(第895行)。

? 設(shè)置其ktype為bus_ktype(第896行)。

做過(guò)上面的設(shè)置后,調(diào)用kset_register一次性初始化總線類(lèi)型私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kset,并將它添加到sysfs文件系統(tǒng)。這將在sys/bus/下創(chuàng)建一個(gè)以總線類(lèi)型名字(為敘述方便,假設(shè)為###)為名字的子目錄,即sys/bus/###/。

第906~908行調(diào)用bus_create_file在該子目錄下添加uevent屬性文件,即sys/bus/###/uevent。這是一個(gè)只寫(xiě)的屬性文件,這用于手動(dòng)觸發(fā)熱插拔。熱插拔機(jī)制需要用戶空間服務(wù)程序(例如udevd)的配合,該程序偵聽(tīng)內(nèi)核空間發(fā)出的uevent事件,匹配規(guī)則做相應(yīng)的動(dòng)作。在系統(tǒng)啟動(dòng)后內(nèi)核注冊(cè)了很多設(shè)備,但用戶空間還沒(méi)有啟動(dòng),所以這些事件沒(méi)有機(jī)會(huì)被處理。為此,在每個(gè)注冊(cè)的設(shè)備文件夾下生成一個(gè)uevent屬性文件,當(dāng)服務(wù)程序啟動(dòng)后,可以掃描sysfs文件系統(tǒng)中所有uevent屬性文件,向其寫(xiě)入add命令,從而觸發(fā)uevent事件,這樣服務(wù)程序就有機(jī)會(huì)處理這些事件了。

第910~915行在該子目錄下添加devices子目錄(即sys/bus/###/devices),對(duì)應(yīng)的kset被保存在devices_kset域。以便將來(lái)在該總線類(lèi)型下發(fā)現(xiàn)設(shè)備的時(shí)候,向?qū)?yīng)的子目錄中添加設(shè)備的符號(hào)鏈接。

第917~922行在該子目錄下添加drivers子目錄(即sys/bus/###/drivers),對(duì)應(yīng)的kset被保存在drivers_kset域。以便將來(lái)在該總線類(lèi)型下加載驅(qū)動(dòng)的時(shí)候,向?qū)?yīng)的子目錄中添加驅(qū)動(dòng)的子目錄。

第924行和第925行分別初始化總線類(lèi)型的設(shè)備鏈表和驅(qū)動(dòng)鏈表。

第927~929行調(diào)用add_probe_files在該總線類(lèi)型的子目錄下添加drivers_probe和drivers_autoprobe兩個(gè)文件,即sys/bus/###/drivers_probe和sys/bus/###/drivers_autoprobe。后者用來(lái)顯示和修改總線類(lèi)型描述符的drivers_ autoprobe域,而前者是一個(gè)只寫(xiě)的屬性文件,向其中寫(xiě)任何值都會(huì)觸發(fā)對(duì)總線類(lèi)型對(duì)它的設(shè)備重新掃描。

最后,第931~933行調(diào)用bus_add_attrs在總線類(lèi)型子目錄下為聲明bus_type時(shí)給出的總線類(lèi)型屬性添加文件。例如對(duì)于PCI總線,只定義了一個(gè)這樣的屬性:rescan。

和bus_register對(duì)應(yīng)的函數(shù)是bus_unregister,用于總線類(lèi)型注銷(xiāo)時(shí),它執(zhí)行如下一些相反的操作。

? 調(diào)用bus_remove_attrs為總線類(lèi)型刪除公有屬性文件,這些屬性由bus_type類(lèi)型變量的bus_attrs域指針?biāo)赶颉?/p>

? 調(diào)用remove_probe_files從總線類(lèi)型下刪除probe和autoprobe兩個(gè)屬性文件。

? 調(diào)用kset_unregister從子樹(shù)中刪除drivers子目錄。

? 調(diào)用kset_unregister從子樹(shù)中刪除devices子目錄。

? 調(diào)用bus_remove_file從總線類(lèi)型中刪除uevent屬性文件。

? 調(diào)用kset_unregister從系統(tǒng)中注銷(xiāo)總線類(lèi)型內(nèi)嵌的kset。

Linux驅(qū)動(dòng)模型核心提供了遍歷每個(gè)鏈表的輔助函數(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 *));

這兩個(gè)輔助函數(shù)的使用會(huì)在后面看到,它們都在成功時(shí)返回0,“錯(cuò)誤”時(shí)返回非零值。

bus_for_each_dev函數(shù)遍歷總線類(lèi)型的設(shè)備鏈表,bus_for_each_drv函數(shù)遍歷總線類(lèi)型的驅(qū)動(dòng)鏈表,對(duì)于鏈表中每個(gè)設(shè)備或驅(qū)動(dòng),調(diào)用傳入的回調(diào)函數(shù)fn。回調(diào)函數(shù)返回零就繼續(xù)處理鏈表的后一個(gè)設(shè)備或驅(qū)動(dòng),返回非零值則退出循環(huán)。這主要用在設(shè)備和驅(qū)動(dòng)的匹配,即在設(shè)備鏈表中查找驅(qū)動(dòng)可以綁定的設(shè)備,或者在驅(qū)動(dòng)鏈表中查找可以綁定設(shè)備的驅(qū)動(dòng)。

我們有必要對(duì)“錯(cuò)誤”的引號(hào)做一下說(shuō)明。記住設(shè)備只能被綁定到最多一個(gè)驅(qū)動(dòng)。用設(shè)備在總線類(lèi)型的驅(qū)動(dòng)鏈表上尋求匹配的時(shí)候,無(wú)論是否找到匹配項(xiàng),都是正常的。因?yàn)橐欢ㄒ獏^(qū)分,我們將匹配作為一種“錯(cuò)誤”,出現(xiàn)時(shí)就退出循環(huán),不需要繼續(xù)處理鏈表中余下的驅(qū)動(dòng)了。

對(duì)比地看,驅(qū)動(dòng)可以綁定一個(gè)或多個(gè)設(shè)備。用驅(qū)動(dòng)在總線類(lèi)型的設(shè)備鏈表上進(jìn)行匹配的時(shí)候,應(yīng)該處理完所有的設(shè)備,除非出現(xiàn)真正意義上的錯(cuò)誤。而且對(duì)每個(gè)設(shè)備,無(wú)論是否匹配,都是正常的。這里的錯(cuò)誤用法采用的是正常的思維。

更具體的解釋請(qǐng)參看后面關(guān)于device_attach和driver_attach函數(shù)的跟蹤。

2.6.2 設(shè)備

Linux驅(qū)動(dòng)模型中的設(shè)備,我們有時(shí)簡(jiǎn)稱為驅(qū)動(dòng)模型設(shè)備,使用device和device_private兩個(gè)結(jié)構(gòu)體來(lái)表示。兩個(gè)結(jié)構(gòu)體相互關(guān)聯(lián),各自有一個(gè)域指向?qū)Ψ健_@兩個(gè)結(jié)構(gòu)體中的域分別如表2-14和表2-15所示。

表2-14 device結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)

表2-15 device_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)

在Linux驅(qū)動(dòng)模型下,每個(gè)設(shè)備被歸到一種總線類(lèi)型,通過(guò)bus域指向它。設(shè)備也可以屬于唯一的類(lèi),由class域指向。如果設(shè)備被綁定,則通過(guò)driver域指向?qū)?yīng)的驅(qū)動(dòng)。同內(nèi)核對(duì)象一樣,設(shè)備也被組織成層次結(jié)構(gòu),通過(guò)parent域指向父親設(shè)備。對(duì)應(yīng)地,設(shè)備被加入到如下四個(gè)鏈表。

? 所屬總線類(lèi)型的設(shè)備鏈表,總線類(lèi)型的klist_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_bus域?yàn)殒溔氪随湵淼倪B接件。

? 所屬類(lèi)的設(shè)備鏈表,類(lèi)的class_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_class域?yàn)殒溔氪随湵淼倪B接件。

? 綁定它的驅(qū)動(dòng)的設(shè)備鏈表,驅(qū)動(dòng)的klist_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_driver域?yàn)殒溔氪随湵淼倪B接件。

? 父設(shè)備的孩子鏈表,父設(shè)備的klist_children域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_parent域?yàn)殒溔氪随湵淼倪B接件。

在實(shí)際使用中,驅(qū)動(dòng)模型設(shè)備被內(nèi)嵌到業(yè)務(wù)子系統(tǒng)的設(shè)備結(jié)構(gòu)中。后者通過(guò)驅(qū)動(dòng)模型設(shè)備被鏈接到業(yè)務(wù)子系統(tǒng)的總線類(lèi)型實(shí)例,或者被鏈接到業(yè)務(wù)子系統(tǒng)的類(lèi)實(shí)例,兩者取其一,也就是說(shuō)每個(gè)驅(qū)動(dòng)模型設(shè)備只會(huì)使用knode_bus和knode_class兩個(gè)域中的一個(gè)。如果業(yè)務(wù)子系統(tǒng)的設(shè)備結(jié)構(gòu)需要同時(shí)被鏈接到總線類(lèi)型實(shí)例和類(lèi)實(shí)例,則需要包含兩個(gè)內(nèi)嵌的驅(qū)動(dòng)模型設(shè)備,用于鏈接到總線類(lèi)型實(shí)例的驅(qū)動(dòng)模型設(shè)備被稱為通用設(shè)備,而用于鏈接到類(lèi)實(shí)例的驅(qū)動(dòng)模型設(shè)備被稱為類(lèi)設(shè)備。

業(yè)務(wù)子系統(tǒng)可以借助于驅(qū)動(dòng)模型中的鏈表實(shí)現(xiàn)對(duì)設(shè)備的遍歷,在其總線類(lèi)型實(shí)例或類(lèi)實(shí)例中查找滿足某些條件的業(yè)務(wù)子系統(tǒng)設(shè)備。例如PCI子系統(tǒng)中的for_each_pci_dev宏,還有SCSI子系統(tǒng)中的scsi_host_lookup函數(shù)。

同樣,類(lèi)似于kobject,每個(gè)device都屬于一種設(shè)備類(lèi)型,其結(jié)構(gòu)為device_type(該結(jié)構(gòu)中的域如表2-16所示),定義了這種類(lèi)型設(shè)備的公共方法和成員。

表2-16 device_type結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)

設(shè)備被發(fā)現(xiàn)后,需要向Linux驅(qū)動(dòng)模型注冊(cè)。設(shè)備注冊(cè)的函數(shù)為device_register(其代碼如程序2-13所示),它有唯一的參數(shù),即指向device描述符的指針。調(diào)用者已經(jīng)為這個(gè)device描述符分配好了空間,實(shí)際上,device描述符往往被嵌入其他結(jié)構(gòu)中,它的空間隨著其他結(jié)構(gòu)分配。

程序2-13 函數(shù)device_register()代碼(摘自文件drivers/base/core.c)

device_register()

1043int device_register(struct device *dev)
1044{
1045    device_initialize(dev);
1046    return device_add(dev);
1047}

設(shè)備注冊(cè)分配兩個(gè)步驟:初始化設(shè)備,以及將設(shè)備添加到sysfs。分別調(diào)用device_initialize(代碼如程序2-14所示)和device_add函數(shù)(代碼如程序2-15所示)。

程序2-14 函數(shù)device_initialize()代碼(摘自文件drivers/base/core.c)

device_register()→device_initialize()

557void device_initialize(struct device *dev)
558{
559    dev->kobj.kset = devices_kset;
560    kobject_init(&dev->kobj, &device_ktype);
561    INIT_LIST_HEAD(&dev->dma_pools);
562    init_MUTEX(&dev->sem);
563    spin_lock_init(&dev->devres_lock);
564    INIT_LIST_HEAD(&dev->devres_head);
565    device_init_wakeup(dev, 0);
566    device_pm_init(dev);
567    set_dev_node(dev, -1);
568}

device_initializate是設(shè)備注冊(cè)的第一步。我們只關(guān)注前面兩行代碼。第559行將設(shè)備內(nèi)嵌的kobject關(guān)聯(lián)到內(nèi)核對(duì)象集devices_kset,而后者在系統(tǒng)初始化過(guò)程的devices_init函數(shù)(見(jiàn)文件drivers/base/core.c)中調(diào)用kset_create_and_add創(chuàng)建,它在sysfs文件系統(tǒng)中對(duì)應(yīng)的目錄為sys/devices。此外,devices_kset的uevent操作表并賦值為device_uevent_ops。如前所述,這將控制內(nèi)核中設(shè)備狀態(tài)發(fā)生變化時(shí)向用戶空間發(fā)送uevent的方式。

第560行將設(shè)備的內(nèi)嵌對(duì)象的對(duì)象類(lèi)型設(shè)置為device_ktype,其中定義了設(shè)備的公共屬性操作表,以及釋放方法。后者確保在對(duì)設(shè)備的引用計(jì)數(shù)減到0時(shí),銷(xiāo)毀整個(gè)device結(jié)構(gòu)。

程序2-15 函數(shù)device_add()代碼(摘自文件drivers/base/core.c)

device_register()→device_add()

890int device_add(struct device *dev)
891{
892    struct device *parent = NULL;
893    struct class_interface *class_intf;
894    int error = -EINVAL;
895
896    dev = get_device(dev);
897    if (!dev)
898        goto done;
899
900    if (!dev->p) {
901        error = device_private_init(dev);
902        if (error)
903            goto done;
904   }
905
906    /*
907     * for statically allocated devices, which should all be converted
908     * some day, we need to initialize the name. We prevent reading back
909     * the name, and force the use of dev_name()
910     */
911    if (dev->init_name) {
912        dev_set_name(dev, "%s", dev->init_name);
913        dev->init_name = NULL;
914   }
915
916    if (!dev_name(dev)) {
917        error = -EINVAL;
918        goto name_error;
919   }
920
921    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
922
923    parent = get_device(dev->parent);
924    setup_parent(dev, parent);
925
926    /* use parent numa_node */
927    if (parent)
928        set_dev_node(dev, dev_to_node(parent));
929
930    /* first, register with generic layer. */
931    /* we require the name to be set before, and pass NULL */
932    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
933    if (error)
934        goto Error;
935
936    /* notify platform of device entry */
937    if (platform_notify)
938        platform_notify(dev);
939
940    error = device_create_file(dev, &uevent_attr);
941    if (error)
942        goto attrError;
943
944    if (MAJOR(dev->devt)) {
945        error = device_create_file(dev, &devt_attr);
946        if (error)
947            goto ueventattrError;
948
949        error = device_create_sys_dev_entry(dev);
950        if (error)
951            goto devtattrError;
952
953        devtmpfs_create_node(dev);
954   }
955
956    error = device_add_class_symlinks(dev);
957    if (error)
958        goto SymlinkError;
959    error = device_add_attrs(dev);
960    if (error)
961        goto AttrsError;
962    error = bus_add_device(dev);
963    if (error)
964        goto BusError;
965    error = dpm_sysfs_add(dev);
966    if (error)
967        goto DPMError;
968    device_pm_add(dev);
969
970    /* Notify clients of device addition. This call must come
971     * after dpm_sysf_add() and before kobject_uevent().
972     */
973    if (dev->bus)
974        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
975                       BUS_NOTIFY_ADD_DEVICE, dev);
976
977    kobject_uevent(&dev->kobj, KOBJ_ADD);
978    bus_probe_device(dev);
979    if (parent)
980        klist_add_tail(&dev->p->knode_parent,
981                &parent->p->klist_children);
982
983    if (dev->class) {
984        mutex_lock(&dev->class->p->class_mutex);
985        /* tie the class to the device */
986        klist_add_tail(&dev->knode_class,
987                &dev->class->p->class_devices);
988
989        /* notify any interfaces that the device is here */
990        list_for_each_entry(class_intf,
991                  &dev->class->p->class_interfaces, node)
992            if (class_intf->add_dev)
993                class_intf->add_dev(dev, class_intf);
994        mutex_unlock(&dev->class->p->class_mutex);
995   }
996done:
997    put_device(dev);
998    return error;
999 DPMError:
1000    bus_remove_device(dev);
1001 BusError:
1002    device_remove_attrs(dev);
1003 AttrsError:
1004    device_remove_class_symlinks(dev);
1005 SymlinkError:
1006    if (MAJOR(dev->devt))
1007        devtmpfs_delete_node(dev);
1008    if (MAJOR(dev->devt))
1009        device_remove_sys_dev_entry(dev);
1010 devtattrError:
1011    if (MAJOR(dev->devt))
1012        device_remove_file(dev, &devt_attr);
1013 ueventattrError:
1014    device_remove_file(dev, &uevent_attr);
1015 attrError:
1016    kobject_uevent(&dev->kobj, KOBJ_REMOVE);
1017    kobject_del(&dev->kobj);
1018 Error:
1019    cleanup_device_parent(dev);
1020    if (parent)
1021        put_device(parent);
1022name_error:
1023    kfree(dev->p);
1024    dev->p = NULL;
1025    goto done;
1026}

device_add是設(shè)備注冊(cè)的第二步,負(fù)責(zé)將設(shè)備添加到系統(tǒng)。

設(shè)備的私有數(shù)據(jù)結(jié)構(gòu)可能還沒(méi)有被分配,在第900~904行分配。在第911~919行設(shè)置并驗(yàn)證設(shè)備的名字。然后,調(diào)用setup_parent設(shè)置設(shè)備的父親,同時(shí)確定了設(shè)備在sysfs中的位置。這是一個(gè)比較復(fù)雜的過(guò)程,我們后面會(huì)專門(mén)討論它。

第932~934行,調(diào)用kobject_add將設(shè)備的內(nèi)嵌kobject添加到系統(tǒng),這將在sys/devices/目錄下添加一個(gè)以設(shè)備名為名字的子目錄;

然后在sysfs文件系統(tǒng)中創(chuàng)建屬性文件:第940~942行創(chuàng)建uevent屬性對(duì)應(yīng)的文件,這是一個(gè)只寫(xiě)的屬性文件,可以從用戶空間寫(xiě)事件到這個(gè)文件,從而手動(dòng)產(chǎn)生事件。

第944~954行,如果設(shè)備有設(shè)備號(hào):

? 在設(shè)備目錄下創(chuàng)建dev屬性對(duì)應(yīng)的文件,這是一個(gè)只讀的屬性文件,用戶空間讀取這個(gè)文件時(shí),將返回設(shè)備的設(shè)備號(hào),以##:##的形式;

? 調(diào)用device_create_sys_dev_entry函數(shù)(文件drivers/base/core.c)在sys/block/或sys/char/下創(chuàng)建一個(gè)到設(shè)備所在目錄的符號(hào)鏈接,鏈接名為##:##的形式的設(shè)備號(hào)。具體選擇目錄取決于設(shè)備所屬的類(lèi)。如果屬于block_class,即為塊設(shè)備,將在sys/block下創(chuàng)建;如果屬于其他類(lèi),或者不屬于任何類(lèi),都會(huì)在sys/char/下創(chuàng)建。

第956~958行,調(diào)用device_add_class_symlinks,如果設(shè)備屬于某個(gè)類(lèi),則為設(shè)備創(chuàng)建和類(lèi)相關(guān)的符號(hào)鏈接。這又包括下面的步驟:

? 在設(shè)備目錄下,創(chuàng)建到所屬類(lèi)的符號(hào)鏈接,鏈接名為subsystem;

? 在所屬類(lèi)的目錄下,創(chuàng)建到設(shè)備的符號(hào)鏈接,鏈接名為設(shè)備名;

? 如果設(shè)備有父設(shè)備,并且設(shè)備不為分區(qū),在設(shè)備目錄下,創(chuàng)建到其父設(shè)備的符號(hào)鏈接,鏈接名為device。

第959~961行,調(diào)用device_add_attrs為設(shè)備的默認(rèn)屬性添加對(duì)應(yīng)的文件。

第962~964行,調(diào)用bus_add_device,如果設(shè)備有指定總線類(lèi)型,則會(huì)將設(shè)備添加到該總線類(lèi)型。這又包括下面的步驟:

? 為設(shè)備添加該總線類(lèi)型下設(shè)備的公有屬性文件,這些屬性由設(shè)備所屬bus_type類(lèi)型變量的dev_attrs域指針?biāo)赶颍?/p>

? 在總線類(lèi)型子目錄的devices目錄下創(chuàng)建到該設(shè)備的符號(hào)鏈接,鏈接名為設(shè)備名;

? 在設(shè)備的目錄下創(chuàng)建到總線類(lèi)型的符號(hào)鏈接,鏈接名為subsystem;

? 將設(shè)備連接到總線類(lèi)型的設(shè)備鏈表。

在bus_add_device函數(shù)過(guò)程中,還會(huì)調(diào)用make_deprecated_bus_links函數(shù),后者被早期的版本用于在設(shè)備目錄下創(chuàng)建另外一個(gè)到總線類(lèi)型的符號(hào)鏈接,鏈接名為bus。如果內(nèi)核編譯未指定CONFIG_SYSFS_DEPRECATED選項(xiàng),那么這個(gè)函數(shù)為空,什么也不做。

至此,設(shè)備自身的添加工作已經(jīng)完成,第977行調(diào)用kobject_uevent向用戶空間報(bào)告KOBJ_ADD事件。

然后調(diào)用bus_probe_device試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng),這個(gè)過(guò)程我們也在后面專門(mén)討論。

第979~981行,如果指定了parent,則將設(shè)備添加到它的孩子鏈表中。

此外,第983~995行,如果設(shè)備設(shè)置了class域,則將設(shè)備添加到類(lèi)的設(shè)備鏈表。而且,對(duì)于注冊(cè)到該類(lèi)的每個(gè)接口,如果定義了add_dev回調(diào)函數(shù),則調(diào)用它。這個(gè)動(dòng)作是系統(tǒng)對(duì)設(shè)備熱插處理在類(lèi)上的體現(xiàn)。

到現(xiàn)在,設(shè)備注冊(cè)過(guò)程已經(jīng)比較完整了,除了第924行的setup_parent函數(shù)和第978行的bus_probe_device函數(shù)。它們的作用都很重要,并且過(guò)程也比較復(fù)雜,下面用專門(mén)的篇幅來(lái)討論。

1.設(shè)備在sysfs的位置

setup_parent函數(shù)的根本目的就是確定設(shè)備在sysfs文件系統(tǒng)中的位置。之所以說(shuō)它重要,是因?yàn)槔斫鈨?nèi)核對(duì)象之間的層次關(guān)系就是理解Linux驅(qū)動(dòng)模型的一個(gè)關(guān)鍵。

分析device的結(jié)構(gòu),可以看到有兩個(gè)parent域:

? device描述符的parent域,指向設(shè)備的父設(shè)備;

? device描述符內(nèi)嵌的kobject的parent域,決定了設(shè)備在sysfs文件系統(tǒng)中的位置。

setup_parent函數(shù)(其代碼如程序2-16所示)的實(shí)質(zhì)就是為后者賦值。它傳入兩個(gè)參數(shù):第一個(gè)為指向設(shè)備本身的device描述符的指針;第二個(gè)為指向其父設(shè)備的device描述符的指針。

程序2-16 函數(shù)setup_parent()代碼(摘自文件drivers/base/core.c)

device_register()→device_add()→setup_parent()

676static void setup_parent(struct device *dev, struct device *parent)
677{
678    struct kobject *kobj;
679    kobj = get_device_parent(dev, parent);
680    if (kobj)
681        dev->kobj.parent = kobj;
682}

setup_parent函數(shù)將主要工作交給get_device_parent函數(shù)(其代碼如程序2-17所示)。若后者的返回值非NULL,則保存在設(shè)備的內(nèi)嵌對(duì)象的parent域。

程序2-17 函數(shù)get_device_parent()代碼(摘自文件drivers/base/core.c)

device_register()→device_add()→setup_parent()→get_device_parent()

599static struct kobject *get_device_parent(struct device *dev,
600                     struct device *parent)
601{
602    int retval;
603
604    if (dev->class) {
605        static DEFINE_MUTEX(gdp_mutex);
606        struct kobject *kobj = NULL;
607        struct kobject *parent_kobj;
608        struct kobject *k;
609
610        /*
611         * If we have no parent, we live in "virtual".
612         * Class-devices with a non class-device as parent, live
613         * in a "glue" directory to prevent namespace collisions.
614         */
615        if (parent == NULL)
616            parent_kobj = virtual_device_parent(dev);
617        else if (parent->class)
618            return &parent->kobj;
619        else
620            parent_kobj = &parent->kobj;
621
622        mutex_lock(&gdp_mutex);
623
624        /* find our class-directory at the parent and reference it */
625        spin_lock(&dev->class->p->class_dirs.list_lock);
626        list_for_each_entry(k, &dev->class->p->class_dirs.list, entry)
627            if (k->parent == parent_kobj) {
628                kobj = kobject_get(k);
629                break;
630           }
631        spin_unlock(&dev->class->p->class_dirs.list_lock);
632        if (kobj) {
633            mutex_unlock(&gdp_mutex);
634            return kobj;
635       }
636
637        /* or create a new class-directory at the parent device */
638        k = kobject_create();
639        if (!k) {
640            mutex_unlock(&gdp_mutex);
641            return NULL;
642       }
643        k->kset = &dev->class->p->class_dirs;
644        retval = kobject_add(k, parent_kobj, "%s", dev->class->name);
645        if (retval < 0) {
646            mutex_unlock(&gdp_mutex);
647            kobject_put(k);
648            return NULL;
649       }
650        /* do not emit an uevent for this simple "glue" directory */
651        mutex_unlock(&gdp_mutex);
652        return k;
653   }
654
655    if (parent)
656        return &parent->kobj;
657    return NULL;
658}

決定device描述符的內(nèi)嵌kobject的父kobject的因素有如下三個(gè):

? 設(shè)備本身device描述符的parent域;

? 設(shè)備本身device描述符的class域;

? 父設(shè)備device描述符的class域(如果父設(shè)備存在的話)。

Linux當(dāng)前版本的處理方法和以前版本稍有不同。我們只考慮當(dāng)前版本的處理規(guī)則,即代碼中未定義CONFIG_SYS_DEPRECATED的部分。設(shè)置設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象的情況有6種,如圖2-12所示,具體說(shuō)明如下。

? device有class,有parent,且parent有class,不管parent->class和device->class是否相同,parent_kobj都為parent內(nèi)嵌的kobject;這分別對(duì)應(yīng)圖中的情況①和②。

? device有class,有parent,且parent沒(méi)有class,則parent_obj為新創(chuàng)建的一個(gè)kobject,保存在class的class_dirs(這是個(gè)kset)里。這個(gè)新創(chuàng)建的kobject的parent為parent內(nèi)嵌的kobject,名稱為device的class_name,也就是說(shuō),它對(duì)應(yīng)的sysfs目錄為sys/devices/.../parent_name/ class_name/。這對(duì)應(yīng)圖中的情況③。

圖2-12 設(shè)置設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象

? device有class,沒(méi)有parent,則parent_kobj為新創(chuàng)建的一個(gè)kobject,保存在class的class_dirs(這是個(gè)kset)里。這個(gè)新創(chuàng)建的kobject的parent為sys/devices/virtual/目錄對(duì)應(yīng)的kobject,名稱為device的class_name,也就是說(shuō),它對(duì)應(yīng)的sys目錄為sys/devices/virtual/class_name。這對(duì)應(yīng)圖中的情況④。

? device無(wú)class,有parent,則device->kobj.parent = parent.kobj。這對(duì)應(yīng)圖中的情況⑤。

? device無(wú)class,無(wú)parent,則device->kobj.parent為空。這對(duì)應(yīng)圖中的情況⑥。應(yīng)該說(shuō)這是一種理論上的可能,在Linux代碼中沒(méi)有找到這樣的例子(萬(wàn)一出現(xiàn),設(shè)備目錄將直接建在sys/之下)。我們將它列舉在這里,純粹出于完整性的考慮。

對(duì)于情況③和④的補(bǔ)充說(shuō)明:class_dirs是用于保存那些為了將其父設(shè)備為空,或其父設(shè)備的class為空的設(shè)備掛入sys/devices/子樹(shù),而新創(chuàng)建的內(nèi)核對(duì)象,它將這些內(nèi)核對(duì)象鏈接在一起。這些內(nèi)核對(duì)象將作為前述設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象。

綜合上述情況,deivce在sysfs中的目錄有以下幾種形式:

? sys/devices/.../parent_name/device_name;

? sys/devices/.../parent_name/class_name/device_name;

? sys/devices/virtual/class_name/device_name。

如果采用新規(guī)則,所有的設(shè)備都放在sys/devices/目錄下(這不同于舊規(guī)則將設(shè)備分散在sys/devices/和sys/class/兩個(gè)目錄中),這個(gè)系統(tǒng)的設(shè)備樹(shù)統(tǒng)一在sys/devices/一個(gè)目錄中。

2.將設(shè)備綁定到驅(qū)動(dòng)

將設(shè)備添加到系統(tǒng),還需要嘗試將設(shè)備綁定到驅(qū)動(dòng)。我們現(xiàn)在來(lái)看對(duì)bus_probe_device函數(shù)(其代碼如程序2-18所示)的調(diào)用。它只有一個(gè)參數(shù),即指向設(shè)備本身的device描述符的指針;

程序2-18 函數(shù)bus_probe_device()代碼(摘自文件drivers/base/bus.c)

device_register()→device_add()→bus_probe_device()

509void bus_probe_device(struct device *dev)
510{
511    struct bus_type *bus = dev->bus;
512    int ret;
513
514    if (bus && bus->p->drivers_autoprobe) {
515        ret = device_attach(dev);
516        WARN_ON(ret < 0);
517   }
518}

從字面上理解,這個(gè)函數(shù)的功能是自動(dòng)探測(cè)設(shè)備,探測(cè)的主體是總線類(lèi)型上的驅(qū)動(dòng)。探測(cè)需要設(shè)備所在總線類(lèi)型的支持。如果設(shè)備不屬于任何總線類(lèi)型,或者總線類(lèi)型沒(méi)有自動(dòng)探測(cè)能力(即driver_autoprobe域?yàn)?),則什么也不需要做;否則調(diào)用device_attach函數(shù),其代碼如程序2-19所示。

程序2-19 函數(shù)device_attach()代碼(摘自文件drivers/base/dd.c)

device_register()→device_add()→bus_probe_device()→device_attach()

238int device_attach(struct device *dev)
239{
240    int ret = 0;
241
242    device_lock(dev);
243    if (dev->driver) {
244        ret = device_bind_driver(dev);
245        if (ret == 0)
246            ret = 1;
247        else {
248            dev->driver = NULL;
249            ret = 0;
250       }
251   } else {
252        pm_runtime_get_noresume(dev);
253        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
254        pm_runtime_put_sync(dev);
255   }
256    device_unlock(dev);
257    return ret;
258

device_attach函數(shù)的目的是試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng),返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng)。在調(diào)用device_attach時(shí),存在兩種情況:

? 已經(jīng)為設(shè)備指定了驅(qū)動(dòng),即設(shè)備的driver域已經(jīng)設(shè)置,就直接調(diào)用device_bind_driver函數(shù)。如果它返回錯(cuò)誤,說(shuō)明設(shè)備沒(méi)辦法被綁定到這個(gè)驅(qū)動(dòng),那么就清零設(shè)備的driver域,將錯(cuò)誤報(bào)告給調(diào)用者;

? 否則遍歷總線類(lèi)型的驅(qū)動(dòng)鏈表,調(diào)用__device_attach函數(shù)(其代碼如程序2-20所示)嘗試將設(shè)備綁定到這些驅(qū)動(dòng)。如果設(shè)備不能被綁定到任何驅(qū)動(dòng),那么bus_for_each_drv宏最終會(huì)返回0,我們直接回傳給調(diào)用者,報(bào)告錯(cuò)誤。

我們只跟蹤后面一種情況的執(zhí)行流程。事實(shí)上,前面一種情況,所做的工作只是其中的一部分。

程序2-20 函數(shù)__device_attach()代碼(摘自文件drivers/base/dd.c)

device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach()

214static int __device_attach(struct device_driver *drv, void *data)
215{
216    struct device *dev = data;
217
218    if (!driver_match_device(drv, dev))
219        return 0;
220
221    return driver_probe_device(drv, dev);
222}

記住,__device_attach函數(shù)試圖綁定具體驅(qū)動(dòng)和具體設(shè)備。同樣,它返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng)。這個(gè)返回值被bus_for_each_drv宏進(jìn)行了非正常思維的解釋:返回1被當(dāng)成一種“錯(cuò)誤”,遇到即退出循環(huán),放棄處理驅(qū)動(dòng)鏈表中余下的驅(qū)動(dòng)。

它調(diào)用driver_match_device函數(shù)(其代碼如程序2-21所示)檢查是否匹配,匹配失敗,返回0。若匹配成功,則調(diào)用driver_probe_device函數(shù)(其代碼如程序2-22所示)進(jìn)一步探測(cè),并向調(diào)用者傳遞它的返回值:成功時(shí)為1;否則為0。

程序2-21 函數(shù)driver_match_device()代碼(摘自文件drivers/base/base.h)

device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach()
→driver_match_device()

120static inline int driver_match_device(struct device_driver *drv,
121                   struct device *dev)
122{
123    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
124}

驅(qū)動(dòng)和設(shè)備的匹配是子系統(tǒng)特定的,Linux驅(qū)動(dòng)模型必須通過(guò)回調(diào)函數(shù)來(lái)實(shí)現(xiàn)。子系統(tǒng)在聲明總線類(lèi)型時(shí),可以給出其match方法。它以device和device_driver作為其參數(shù),在具體實(shí)現(xiàn)中,需要先轉(zhuǎn)換為對(duì)應(yīng)的設(shè)備和驅(qū)動(dòng)描述符(例如pci_dev和pci_driver)。match回調(diào)函數(shù)判斷驅(qū)動(dòng)是否能“匹配”設(shè)備。若是,返回1;否則返回0。如果沒(méi)定義match回調(diào)函數(shù),我們直接返回1。認(rèn)為是總線類(lèi)型自動(dòng)匹配了驅(qū)動(dòng)和設(shè)備。

程序2-22 函數(shù)driver_probe_device()代碼(摘自文件drivers/base/dd.c)

device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach()
→driver_probe_device()

196int driver_probe_device(struct device_driver *drv, struct device *dev)
197{
198    int ret = 0;
199
200    if (!device_is_registered(dev))
201        return -ENODEV;
202
203    pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
204         drv->bus->name, __func__, dev_name(dev), drv->name);
205
206    pm_runtime_get_noresume(dev);
207    pm_runtime_barrier(dev);
208    ret = really_probe(dev, drv);
209    pm_runtime_put_sync(dev);
210
211    return ret;
212}

進(jìn)一步探測(cè)設(shè)備要求設(shè)備已經(jīng)注冊(cè)。如果設(shè)備被注冊(cè),對(duì)本驅(qū)動(dòng)以及后續(xù)驅(qū)動(dòng)的匹配都沒(méi)有意義,返回負(fù)的錯(cuò)誤碼,這是一種真正意義上的錯(cuò)誤。

這個(gè)函數(shù)調(diào)用了多個(gè)與電源管理有關(guān)的函數(shù),因?yàn)殡娫垂芾聿皇潜緯?shū)的重點(diǎn),我們跳過(guò)它們,直接跟蹤第208行的really_probe函數(shù),其代碼如程序2-23所示。

程序2-23 函數(shù)really_probe()代碼(摘自文件drivers/base/dd.c)

device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach()
→driver_probe_device()→really_probe()

104static int really_probe(struct device *dev, struct device_driver *drv)
105{
106    int ret = 0;
107
108    atomic_inc(&probe_count);
109    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
110         drv->bus->name, __func__, drv->name, dev_name(dev));
111    WARN_ON(!list_empty(&dev->devres_head));
112
113    dev->driver = drv;
114    if (driver_sysfs_add(dev)) {
115        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
116            __func__, dev_name(dev));
117        goto probe_failed;
118   }
119
120    if (dev->bus->probe) {
121        ret = dev->bus->probe(dev);
122        if (ret)
123            goto probe_failed;
124   } else if (drv->probe) {
125        ret = drv->probe(dev);
126        if (ret)
127            goto probe_failed;
128   }
129
130    driver_bound(dev);
131    ret = 1;
132    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
133         drv->bus->name, __func__, dev_name(dev), drv->name);
134    goto done;
135
136probe_failed:
137    devres_release_all(dev);
138    driver_sysfs_remove(dev);
139    dev->driver = NULL;
140
141    if (ret != -ENODEV && ret != -ENXIO) {
142        /* driver matched but the probe failed */
143        printk(KERN_WARNING
144            "%s: probe of %s failed with error %d\n",
145            drv->name, dev_name(dev), ret);
146   }
147    /*
148     * Ignore errors returned by ->probe so that the next driver can try
149     * its luck.
150     */
151    ret = 0;
152done:
153    atomic_dec(&probe_count);
154    wake_up(&probe_waitqueue);
155    return ret;
156}

最終,如果設(shè)備可以綁定到驅(qū)動(dòng),則需要執(zhí)行下列操作:

? 第113行將設(shè)備的driver域指向驅(qū)動(dòng);

? 第114~118行,調(diào)用driver_sysfs_add在sysfs文件系統(tǒng)中“綁定”,即在驅(qū)動(dòng)對(duì)應(yīng)的目錄下創(chuàng)建目標(biāo)為設(shè)備的符號(hào)鏈接,鏈接名使用設(shè)備名,同時(shí)在設(shè)備對(duì)應(yīng)的目錄下創(chuàng)建目標(biāo)為驅(qū)動(dòng)的符號(hào)鏈接,鏈接名為“driver”;

? 第120~128行,如果設(shè)備的總線類(lèi)型給出了probe方法,則調(diào)用之;否則如果設(shè)備的驅(qū)動(dòng)給出了probe方法,調(diào)用之。probe函數(shù)返回0表示成功;否則返回錯(cuò)誤碼;

? 第130行調(diào)用driver_bound將設(shè)備鏈入驅(qū)動(dòng)的設(shè)備鏈表。

如果一切正常,really_probe函數(shù)返回1,表示匹配成功。如果中間出現(xiàn)問(wèn)題,回滾已經(jīng)做過(guò)的操作,向調(diào)用者返回0。

我們?cè)倏匆幌驴偩€類(lèi)型和驅(qū)動(dòng)的probe回調(diào)函數(shù)(如圖2-13所示)。這兩個(gè)函數(shù)都是在設(shè)備可以被綁定到驅(qū)動(dòng)的情況下,用來(lái)對(duì)設(shè)備進(jìn)行初始化。若成功,返回0;否則返回錯(cuò)誤碼。

對(duì)于PCI子系統(tǒng),總線類(lèi)型的probe回調(diào)函數(shù)是pci_device_probe,驅(qū)動(dòng)的probe回調(diào)函數(shù)為NULL。PCI設(shè)備的初始化是具體HBA特定的,比如PCI-SCSI適配器要在這時(shí)開(kāi)始SCSI設(shè)備掃描,因此pci_device_probe進(jìn)而會(huì)調(diào)用PCI驅(qū)動(dòng)的probe回調(diào)函數(shù),即具體HBA驅(qū)動(dòng)的探測(cè)方法實(shí)現(xiàn)。注意,這里驅(qū)動(dòng)的probe回調(diào)函數(shù)是指device_driver結(jié)構(gòu)的probe域;而PCI驅(qū)動(dòng)的probe回調(diào)函數(shù)是指pci_driver結(jié)構(gòu)的probe域。

對(duì)于SCSI子系統(tǒng),總線類(lèi)型的probe回調(diào)函數(shù)為NULL,驅(qū)動(dòng)的probe回調(diào)函數(shù)隨具體SCSI驅(qū)動(dòng)的定義提供。例如對(duì)于SCSI磁盤(pán)驅(qū)動(dòng),該回調(diào)函數(shù)被定義為sd_probe,它將為這個(gè)SCSI磁盤(pán)設(shè)備分配通用磁盤(pán)描述符。

總結(jié)一下,Linux驅(qū)動(dòng)模型會(huì)為設(shè)備在sysfs文件系統(tǒng)中創(chuàng)建的符號(hào)鏈接列舉如下。相應(yīng)代碼分散在前面的函數(shù)里,集中整理于此。

類(lèi)相關(guān)的符號(hào)鏈接:

? device對(duì)應(yīng)的目錄下,名稱為subsystem,指向device->class對(duì)應(yīng)的目錄;

圖2-13 PCI驅(qū)動(dòng)和SCSI驅(qū)動(dòng)的probe回調(diào)函數(shù)

? device->class對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄,僅當(dāng)device對(duì)應(yīng)的目錄不在device->class對(duì)應(yīng)的目錄下,并且device不是塊設(shè)備分區(qū)的情況下才創(chuàng)建;

? device對(duì)應(yīng)的目錄下,名稱為device,指向device->parent對(duì)應(yīng)的目錄,僅當(dāng)device有parent并且device不是塊設(shè)備分區(qū)的情況下才創(chuàng)建。

總線類(lèi)型相關(guān)的符號(hào)鏈接:

? device->bus的devices_kset對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄;

? device對(duì)應(yīng)的目錄下,名稱為subsystem,指向device->bus對(duì)應(yīng)的目錄。

驅(qū)動(dòng)相關(guān)的符號(hào)鏈接:

? device->driver對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄;

? device對(duì)應(yīng)的目錄下,名稱為driver,指向device->driver對(duì)應(yīng)的目錄。

注意:在device對(duì)應(yīng)的目錄下,名稱為subsystem的符號(hào)鏈接,可能指向device->class或是device->bus對(duì)應(yīng)的目錄。這里并不會(huì)出現(xiàn)沖突的情況,即便對(duì)應(yīng)SCSI設(shè)備(可能屬于總線類(lèi)型scsi_bus_type或類(lèi)sg_sysfs_class)。它在scsi_device結(jié)構(gòu)中定義了兩個(gè)內(nèi)嵌對(duì)象域(內(nèi)嵌通用對(duì)象域和內(nèi)嵌類(lèi)對(duì)象域)。

和device_register對(duì)應(yīng)的函數(shù)是device_unregister,用于從系統(tǒng)注銷(xiāo)設(shè)備。

2.6.3 驅(qū)動(dòng)

同總線類(lèi)型和設(shè)備一樣,在Linux驅(qū)動(dòng)模型中表示一個(gè)驅(qū)動(dòng)也需要兩個(gè)結(jié)構(gòu)體:device_driver和driver_private。這兩個(gè)結(jié)構(gòu)體同時(shí)存在,并且一一對(duì)應(yīng)。程序員在遵循驅(qū)動(dòng)模型編程時(shí)只需要實(shí)現(xiàn)device_driver,其結(jié)構(gòu)中的域如表2-17所示,而結(jié)構(gòu)體driver_private中的域如表2-18所示。

表2-17 device_driver結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)

表2-18 driver_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)

驅(qū)動(dòng)和總線類(lèi)型與設(shè)備的關(guān)系是:驅(qū)動(dòng)的bus域指向所屬的總線類(lèi)型,以knode_bus域?yàn)檫B接件鏈入到它以klist_drivers域?yàn)楸眍^的驅(qū)動(dòng)鏈表。驅(qū)動(dòng)還有一個(gè)綁定到它的設(shè)備鏈表,以klist_devices為表頭,設(shè)備鏈入此鏈表的連接件為knode_driver域。

在驅(qū)動(dòng)被加載時(shí),需要向Linux驅(qū)動(dòng)模型注冊(cè),為此調(diào)用它提供的公共函數(shù)driver_register(其代碼如程序2-24所示)。這個(gè)函數(shù)具有唯一的參數(shù),指向device_driver描述符的指針。

程序2-24 函數(shù)driver_register()代碼(摘自文件drivers/base/driver.c)

driver_register()

222int driver_register(struct device_driver *drv)
223{
224    int ret;
225    struct device_driver *other;
226
227    BUG_ON(!drv->bus->p);
228
229    if ((drv->bus->probe && drv->probe) ||
230      (drv->bus->remove && drv->remove) ||
231      (drv->bus->shutdown && drv->shutdown))
232        printk(KERN_WARNING "Driver '%s' needs updating - please use "
233            "bus_type methods\n", drv->name);
234
235    other = driver_find(drv->name, drv->bus);
236    if (other) {
237        put_driver(other);
238        printk(KERN_ERR "Error: Driver '%s' is already registered, "
239            "aborting...\n", drv->name);
240        return -EBUSY;
241   }
242
243    ret = bus_add_driver(drv);
244    if (ret)
245        return ret;
246    ret = driver_add_groups(drv, drv->groups);
247    if (ret)
248        bus_remove_driver(drv);
249    return ret;
250}

在注冊(cè)驅(qū)動(dòng)前,必須為驅(qū)動(dòng)指定了總線類(lèi)型,并且該總線類(lèi)型已經(jīng)注冊(cè)到Linux驅(qū)動(dòng)模型,第227行確保這一點(diǎn)。

第229~233行給出一個(gè)警告消息,如果總線類(lèi)型和驅(qū)動(dòng)都定義了相同名字的函數(shù)。實(shí)際上,Linux驅(qū)動(dòng)模型只會(huì)使用其中的一個(gè)。前面,在really_probe函數(shù)中,我們已經(jīng)看到過(guò)了。

第235~241行調(diào)用driver_find在總線類(lèi)型的驅(qū)動(dòng)鏈表中查找該驅(qū)動(dòng)名,如果找到,表明這個(gè)驅(qū)動(dòng)名已經(jīng)在使用中,我們只好返回錯(cuò)誤。

否則繼續(xù),在第243行調(diào)用bus_add_driver添加驅(qū)動(dòng)到系統(tǒng),我們馬上會(huì)詳細(xì)分析它。

如果一切正常,第246行添加驅(qū)動(dòng)的屬性組文件并結(jié)束驅(qū)動(dòng)注冊(cè)過(guò)程。

就驅(qū)動(dòng)注冊(cè)來(lái)講,函數(shù)bus_add_driver(該函數(shù)的代碼如程序2-25所示)無(wú)疑是最主要的函數(shù),因?yàn)榇蟛糠止ぷ鬟€是要在總線類(lèi)型上做的。這個(gè)函數(shù)返回0表示成功,否則表示錯(cuò)誤。

程序2-25 函數(shù)bus_add_driver代碼(摘自文件drivers/base/bus.c)

driver_register()→bus_add_driver()

648int bus_add_driver(struct device_driver *drv)
649{
650    struct bus_type *bus;
651    struct driver_private *priv;
652    int error = 0;
653
654    bus = bus_get(drv->bus);
655    if (!bus)
656        return -EINVAL;
657
658    pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
659
660    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
661    if (!priv) {
662        error = -ENOMEM;
663        goto out_put_bus;
664   }
665    klist_init(&priv->klist_devices, NULL, NULL);
666    priv->driver = drv;
667    drv->p = priv;
668    priv->kobj.kset = bus->p->drivers_kset;
669    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
670                   "%s", drv->name);
671    if (error)
672        goto out_unregister;
673
674    if (drv->bus->p->drivers_autoprobe) {
675        error = driver_attach(drv);
676        if (error)
677            goto out_unregister;
678   }
679    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
680    module_add_driver(drv->owner, drv);
681
682    error = driver_create_file(drv, &driver_attr_uevent);
683    if (error) {
684        printk(KERN_ERR "%s: uevent attr (%s) failed\n",
685            __func__, drv->name);
686   }
687    error = driver_add_attrs(bus, drv);
688    if (error) {
689        /* How the hell do we get out of this pickle? Give up */
690        printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
691            __func__, drv->name);
692   }
693
694    if (!drv->suppress_bind_attrs) {
695        error = add_bind_files(drv);
696        if (error) {
697            /* Ditto */
698            printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
699                __func__, drv->name);
700       }
701   }
702
703    kobject_uevent(&priv->kobj, KOBJ_ADD);
704    return 0;
705
706out_unregister:
707    kobject_put(&priv->kobj);
708    kfree(drv->p);
709    drv->p = NULL;
710out_put_bus:
711    bus_put(bus);
712    return error;
713}

因?yàn)橐羊?qū)動(dòng)添加到總線類(lèi)型,自然驅(qū)動(dòng)描述符的bus域應(yīng)該已經(jīng)設(shè)置。第654~656行的代碼確保這一點(diǎn)。

一般來(lái)說(shuō),驅(qū)動(dòng)的私有數(shù)據(jù)結(jié)構(gòu)應(yīng)該還沒(méi)有被分配,在第660~664行進(jìn)行分配,并在第666和667行將它和驅(qū)動(dòng)結(jié)構(gòu)相互關(guān)聯(lián)在一起。

然后,第668行,將驅(qū)動(dòng)通過(guò)私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kobj關(guān)聯(lián)到總線類(lèi)型的drivers_kset,接下來(lái),第669行執(zhí)行kobject_init_and_add會(huì)添加驅(qū)動(dòng)到sysfs文件系統(tǒng),將它放在總線類(lèi)型所對(duì)應(yīng)目錄的drivers子目錄下。

第673~678行,如果總線類(lèi)型支持自動(dòng)探測(cè),我們調(diào)用driver_attach。這個(gè)函數(shù)(其代碼如程序2-26所示)在后面分析。

第679行將驅(qū)動(dòng)添加到總線類(lèi)型的驅(qū)動(dòng)鏈表。

接下來(lái),在驅(qū)動(dòng)目錄下創(chuàng)建各種屬性文件:

? 第682~685行,調(diào)用driver_create_file在驅(qū)動(dòng)子目錄下添加uevent屬性文件,即sys/bus/###/drivers/###/ uevent。這是一個(gè)只寫(xiě)的屬性文件,可以從用戶空間寫(xiě)事件到這個(gè)文件,從而手動(dòng)產(chǎn)生事件;

? 第687~692行,調(diào)用driver_add_attrs為驅(qū)動(dòng)的默認(rèn)屬性添加對(duì)應(yīng)的文件;

? 第694~701行,如果允許通過(guò)sysfs文件系統(tǒng)從用戶空間綁定/松綁,則調(diào)用add_bind_files,這會(huì)在驅(qū)動(dòng)目錄下創(chuàng)建bind和unbind兩個(gè)屬性文件。這兩個(gè)文件都是只寫(xiě)的,被用來(lái)手動(dòng)綁定某個(gè)設(shè)備,或者松綁某個(gè)設(shè)備。

至此,驅(qū)動(dòng)自身的添加工作已經(jīng)完成,第703行調(diào)用kobject_uevent函數(shù)向用戶空間報(bào)告KOBJ_ADD事件。

程序2-26 函數(shù)driver_attach()代碼(摘自文件drivers/base/dd.c)

driver_register()→bus_add_driver()→driver_attach()

299int driver_attach(struct device_driver *drv)
300{
301    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
302}

driver_attach函數(shù)的目的是試圖將驅(qū)動(dòng)去綁定所有可能的設(shè)備,成功返回0,否則返回非零值。

它調(diào)用bus_for_each_dev遍歷總線類(lèi)型的設(shè)備鏈表,調(diào)用是傳入__driver_attach作為回調(diào)函數(shù)(其代碼如程序2-27所示)。

程序2-27 函數(shù)__driver_attach()代碼(摘自文件drivers/base/dd.c)

driver_register()→bus_add_driver()→driver_attach()→__driver_attach()

261static int __driver_attach(struct device *dev, void *data)
262{
263    struct device_driver *drv = data;
264
265    /*
266     * Lock device and try to bind to it. We drop the error
267     * here and always return 0, because we need to keep trying
268     * to bind to devices and some drivers will return an error
269     * simply if it didn't support the device.
270     *
271     * driver_probe_device() will spit a warning if there
272     * is an error.
273     */
274
275    if (!driver_match_device(drv, dev))
276        return 0;
277
278    if (dev->parent)    /* Needed for USB */
279        device_lock(dev->parent);
280    device_lock(dev);
281    if (!dev->driver)
282        driver_probe_device(drv, dev);
283    device_unlock(dev);
284    if (dev->parent)
285        device_unlock(dev->parent);
286
287    return 0;
288}

到了__driver_attach函數(shù),我們又碰到了具體驅(qū)動(dòng)和具體設(shè)備的綁定問(wèn)題。除了為某些USB設(shè)備所加入的特定鎖代碼,大體流程和__device_attach是相同的。這里也調(diào)用了driver_match_device和driver_probe_device兩個(gè)函數(shù)。需要注意,這個(gè)函數(shù)只返回0,因?yàn)楫?dāng)前沒(méi)有一種錯(cuò)誤可以阻止我們讓驅(qū)動(dòng)繼續(xù)嘗試匹配設(shè)備鏈表的下一個(gè)設(shè)備。

關(guān)于device_attach和driver_attach函數(shù),做一個(gè)小結(jié):

? device_attach函數(shù)的目的是試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng)。它以回調(diào)函數(shù)__device_attach調(diào)用bus_for_each_drv。device_attach和__device_attach函數(shù)都用返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng);

? driver_attach函數(shù)的目的是試圖將驅(qū)動(dòng)去綁定所有可能的設(shè)備。它以回調(diào)函數(shù)__driver_attach調(diào)用bus_for_each_dev。driver_attach和__driver_attach函數(shù)都用返回0表示成功,返回1表示錯(cuò)誤。但實(shí)際上__driver_attach函數(shù)只會(huì)返回0。

和driver_register對(duì)應(yīng)的函數(shù)是driver_unregister,用于從系統(tǒng)注銷(xiāo)驅(qū)動(dòng)。

2.6.4 類(lèi)

Linux驅(qū)動(dòng)模型中的類(lèi)用相互關(guān)聯(lián)的class和class_private域表示。class結(jié)構(gòu)中的域如表2-19所示,class_private結(jié)構(gòu)中的域如表2-20所示。

表2-19 class結(jié)構(gòu)中的域(來(lái)自文件include/linux/class.h)

表2-20 class_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)

每個(gè)類(lèi)有一個(gè)內(nèi)嵌的kset,即class_subsys域,因?yàn)樗陬?lèi)被注冊(cè)后,會(huì)在/sys/class下創(chuàng)建一個(gè)和類(lèi)名對(duì)應(yīng)的子目錄。類(lèi)有兩個(gè)鏈表,一個(gè)鏈接屬于該類(lèi)的所有設(shè)備,另一個(gè)鏈接注冊(cè)到這個(gè)類(lèi)的接口。另外,需要說(shuō)明的是class_dirs域,它的作用在前面已經(jīng)介紹過(guò)。

子系統(tǒng)要注冊(cè)類(lèi),需調(diào)用Linux驅(qū)動(dòng)模型提供的class_register方法。它被定義為一個(gè)宏,在文件include/linux/device.h,是__class_register的簡(jiǎn)單封裝。我們直接看__class_register,其函數(shù)的代碼如程序2-28所示。

程序2-28 函數(shù)__class_register()代碼(摘自文件drivers/base/class.c)

__class_register()

154int __class_register(struct class *cls, struct lock_class_key *key)
155{
156    struct class_private *cp;
157    int error;
158
159    pr_debug("device class '%s': registering\n", cls->name);
160
161    cp = kzalloc(sizeof(*cp), GFP_KERNEL);
162    if (!cp)
163        return -ENOMEM;
164    klist_init(&cp->class_devices, klist_class_dev_get, klist_class_dev_put);
165    INIT_LIST_HEAD(&cp->class_interfaces);
166    kset_init(&cp->class_dirs);
167    __mutex_init(&cp->class_mutex, "struct class mutex", key);
168    error = kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name);
169    if (error) {
170        kfree(cp);
171        return error;
172   }
173
174    /* set the default /sys/dev directory for devices of this class */
175    if (!cls->dev_kobj)
176        cls->dev_kobj = sysfs_dev_char_kobj;
177
178#if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK)
179    /* let the block class directory show up in the root of sysfs */
180    if (cls != &block_class)
181        cp->class_subsys.kobj.kset = class_kset;
182#else
183    cp->class_subsys.kobj.kset = class_kset;
184#endif
185    cp->class_subsys.kobj.ktype = &class_ktype;
186    cp->class = cls;
187    cls->p = cp;
188
189    error = kset_register(&cp->class_subsys);
190    if (error) {
191        kfree(cp);
192        return error;
193   }
194    error = add_class_attrs(class_get(cls));
195    class_put(cls);
196    return error;
197}

第161~163行分配類(lèi)的私有數(shù)據(jù)結(jié)構(gòu),接著進(jìn)行一些初始化。第168~172行設(shè)置類(lèi)的class_subsys域的內(nèi)嵌kobject的名字為類(lèi)的名字,這就是顯示在/sys/class/下的類(lèi)的目錄名。

早期的類(lèi)被分開(kāi)放置。block類(lèi)位于/sys/目錄下,而其他類(lèi)則位于/sys/class/目錄下,最新版本的內(nèi)核作了統(tǒng)一。所有類(lèi)都被放在/sys/class/目錄下,這就是第178~184行代碼產(chǎn)生的效果。

第186和187行,將類(lèi)結(jié)構(gòu)和類(lèi)私有數(shù)據(jù)結(jié)構(gòu)相互關(guān)聯(lián)起來(lái)。

第189~193行調(diào)用kset_register注冊(cè)類(lèi)的class_subsys,這最終創(chuàng)建類(lèi)在sysfs文件系統(tǒng)中的對(duì)應(yīng)目錄。第194行,調(diào)用add_class_attrs在類(lèi)的目錄下創(chuàng)建默認(rèn)的類(lèi)屬性文件。

和class_register對(duì)應(yīng)的函數(shù)是class_unregister,用于從系統(tǒng)注銷(xiāo)類(lèi)。

2.6.5 接口

相對(duì)于前面的結(jié)構(gòu),接口比較簡(jiǎn)單,它用class_interface(其結(jié)構(gòu)中的域如表2-21所示)來(lái)表示。

表2-21 class_interface結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)

每個(gè)接口都是相對(duì)于某個(gè)類(lèi)的,通過(guò)class域指向它。接口被鏈接到類(lèi)的接口鏈表,類(lèi)的class_interfaces域?yàn)楸眍^,接口的node域?yàn)檫B接件。

Linux驅(qū)動(dòng)模型提供用于類(lèi)接口注冊(cè)的公共函數(shù)為class_interface_register,該函數(shù)的代碼如程序2-29所示。

程序2-29 函數(shù)class_interface_register()代碼(摘自文件drivers/base/class.c)

class_interface_register()

447int class_interface_register(struct class_interface *class_intf)
448{
449    struct class *parent;
450    struct class_dev_iter iter;
451    struct device *dev;
452
453    if (!class_intf || !class_intf->class)
454        return -ENODEV;
455
456    parent = class_get(class_intf->class);
457    if (!parent)
458        return -EINVAL;
459
460    mutex_lock(&parent->p->class_mutex);
461    list_add_tail(&class_intf->node, &parent->p->class_interfaces);
462    if (class_intf->add_dev) {
463        class_dev_iter_init(&iter, parent, NULL, NULL);
464        while ((dev = class_dev_iter_next(&iter)))
465            class_intf->add_dev(dev, class_intf);
466        class_dev_iter_exit(&iter);
467   }
468    mutex_unlock(&parent->p->class_mutex);
469
470    return 0;
471}

函數(shù)要求傳入的類(lèi)接口不為NULL,并且設(shè)置了class域,即被關(guān)聯(lián)到某個(gè)類(lèi)(第453~454行)。

注冊(cè)類(lèi)接口的具體操作只有兩個(gè):

? 將類(lèi)接口添加到類(lèi)的接口鏈表,這是第461行;

? 如果類(lèi)接口提供了add_dev方法,對(duì)類(lèi)的設(shè)備鏈表上的每個(gè)設(shè)備調(diào)用之,這是第462~467行。

需要強(qiáng)調(diào)的是后面一點(diǎn),也是“熱插拔”的一種表現(xiàn)。類(lèi)接口雖然不綁定到設(shè)備,但它可以通過(guò)自定義的回調(diào)方法作用于設(shè)備,并且無(wú)論是先發(fā)現(xiàn)設(shè)備,還是先注冊(cè)接口。

和class_interface_register對(duì)應(yīng)的函數(shù)是class_interface_unregister,用于從系統(tǒng)注銷(xiāo)接口。

主站蜘蛛池模板: 淮安市| 桦甸市| 和田县| 邵阳县| 兴义市| 会泽县| 临潭县| 横峰县| 扎兰屯市| 吉林市| 买车| 永善县| 武定县| 繁昌县| 关岭| 嘉黎县| 茌平县| 河东区| 滕州市| 临沧市| 桦南县| 丹棱县| 蓝山县| 龙胜| 扎鲁特旗| 安福县| 武山县| 阿拉善右旗| 富蕴县| 启东市| 晋宁县| 灵台县| 洛隆县| 盐津县| 德阳市| 馆陶县| 新河县| 赤壁市| 陆河县| 独山县| 桑植县|