robot
最新文章(10)
Mqskit 和其它相關工具
CPython 的 GC 二、三事
寫 Mecurial Extension 是件快樂的事!
Mozilla 台灣辨公室徵人啟事
關於 Apple 的兩項專利
core dump 之前的 frame
怎麼發出 beep 聲?
先承認你要找的是奴才吧!
程式碼要清的多乾淨?
FreeBSD 的 Thread-Local Storage 實作
首頁
新編
最新留言
Entries RSS
重要關鍵字(10)
coding (122)
Python (93)
FreeBSD (71)
WEB (61)
URL (48)
hardware (46)
javascript (36)
Linux (34)
blog (30)
C++ (16)
所有關鍵字
新增 URL
FreeBSD Bus Sub-system 概念
by thinker
2 Columns
關鍵字:
FreeBSD
電腦系統上,除了 CPU 、 RAM 之外,還有許多不同的裝置透過 bus 與其連接。如: RTC 、 NIC 、 audio card 。因為歷史的原故,並依裝置的速度、成本、和方便性的考量,系統上有各式各樣、性質各異的 bus 。例如: PCI 、 ISA 、 USB 、 SCSI ...... 等等的。這些 bus 大致上以 tree 的拓樸形狀連接在一起。 Tree 的最頂端 (root) 是 system bus ,也就是從 CPU 直接送出的控制訊號。 System bus 連接著 PCI 、 AGP 等其它高速的 bus ,然後各自再連接其它 device 或著更低速的 bus 。 如: USB 、 RS-232 等。 $FreeBSD$ 依循這個結構,在系統內擬塑 (model) bus 和 device 之間的關係,使得 bus 和 device driver 能正確的驅動對應的 bus 或 device。這個擬塑出的來環境,稱為 `bus sub-system' 。 == Architecture == $FreeBSD$ 的每一個 device 都會配上一個 driver,以驅動硬體,作為軟體和硬體的介面。這些 device 包括終端裝置和各種 bus ,各自有對應的 driver 加以驅動。不只是一般的終端裝置需要 driver 驅動,除了 system bus 以外的其它 bus 也是需要 driver 加以驅動。 觀念上,越接近 system bus 的 bus 必需愈早被驅動,並透過已被驅動的 bus 發現、存取下游的其它 bus 和 device,並進一步驅動。經此一層接著一層的驅動流程,直到系統最下游的 bus 和 device ,使系統所有的裝置都可被通透、存取。驅動的順序,可看成是以 system bus 為 root , preorder/level-order travesal 依序驅動各級的 device 和 bus 。 [attach:bus-travel.png] 圖 (preorder 、 level-order ) 驅動各個 bus 的過程,必需由上游 bus 的 driver 主動發現下游的 device 或 bus,以驅動下游裝置 。進行發掘的行為,稱之為 `identify',方式因 bus 而異。例如 PCI bus 的設計,能主動發掘 attach 在 bus 的裝置。因此, PCI bus 的 driver ,就能主動發掘新的下游 device 或 bus,執行對應的 driver ,加以驅動。因此,當 bus 被 driver 所驅動時, driver 必需在適當的時機,主動發掘下游的裝置,並加以驅動,如此才能確實的完成 tree traversal。 $FreeBSD$ 將 device 依據 driver 名稱分類,稱為 `devclass' 。例如,網動 RealTek 網卡的 driver 稱為 `rl' ,該 device 即屬於 `rl' 這個 `devclass'。 driver 也一樣以 `devclass' 分類,但一個 driver 能同時屬於多個 `devclass'。driver 的分類。`devclass' 表示 driver 驅動的 device,可能接在哪些 bus 之下。因此, driver 需為每個可能的 bus 種類,登記為該 `devclass'。當 bus 的 driver 發現在 bus 上的一個 device 時, driver (呼叫 device_probe_and_attach()) 就從 `devclass' 和 bus device 的 `devclass' 相同的數個 driver 中,嘗試、選擇合適的 driver 。 `devclass' 可以有繼承關係, 這個繼承關係,其實就是 driver 的繼承。 driver 可以透過繼承的方式,延伸功能,並共用程式碼。當 bus 的 driver 正為接在其上的下游 device 找尋合適的 driver 時,會先從 bus device 本身所屬 `devclass' 開始找 driver 。若找不到合適者,會往 `devclass' 的 base class (baseclasses) 找,直到找到為止,或 `devclass' 不再有 base class 。尋找 driver 的過程,被稱為 `probe'。 [attach:device-class-driver.png] 圖 (devclass driver device) 不論是 bus 或一般的 device ,對 bus sub-system 而言,都是 device 。因此依照 driver 的驅動順序,所有的 device 組成一個 tree 形狀。例如有一張 realtek 的網路卡插在 PCI bus `pci0' 上,當 `pci0' 被驅動時, bus driver 會發現 `pci0' 上的某個 slot 插了一張卡,並將之加入為 `pci0' 的 children。這時 bus driver 會一一詢問屬於 `pci devclass' 的所有 driver ,找出能驅動這張卡的 driver。這時登記在 `pci devclass' 下的 `realtek driver (rl)' 就會表示能驅動這個 device,於是 bus driver 就將該 device 交付給 rl driver 進行驅動。於是該張網卡就變成 `rl0' (nameunit),由 `rl driver' 所驅動,編號 (unit) 為 0 的 device。在此例, rl driver 也屬於(但不限於) `pci devclass' ,但網卡的 device 屬於 `rl devclass'。 == Driver Interface == $FreeBSD$ 針對 bus 和 device,定義不同的 `interface', driver 依其實作的功能,實作對應的 interface 。一般的 device 只需 implement device interface,而 bus device (如 PIC bus) 的 driver,則需要 implement device itnerface 和 bus interface,因為 bus device 也是一種 device。不同的是, bus device 能連接其它 device ,作為下游 device 和 CPU 或其它 device 的溝通路徑。除了 bus interface 和 device interface , PCI 和 ISA 各自又有定義自己的 interface , driver 必需依據 device 連接的 bus,實作不同的 interface。 Interface 是一組 `operator' 的組合,透 operator 宣告 driver 的功能、行為和接受的命令。 driver 則負則實作 operator 。 $FreeBSD$ 以 `method' 稱呼這些 operator 的實作。 $FreeBSD$ 以 kobj, 建立一個 $object-oriented$ environment,提供多型 (polymorphism) 和繼承 (inheritance) 的功能。Interface 的宣告和實作,即以 kobj 為基礎。關於 kobj ,在文章的後頭會提到。 == Device Interface == $FreeBSD$ 為 device driver 定義了許多 operator , driver 透過 implement operator 相應的 method 和 bus sub-system 互動。對於驅動 device 而言,最重要的是下面三個 operator: * identify * probe * attach 並不是所有的 bus ,能主動發現下游所接的 device 。如: ISA 或 SPI bus ,就無法自動得知 child device 的存在。這些 bus 需要使用者介入;例如: 在 device.hints 裡設定 device 的 I/O address 、 IRQ 等資訊,以告知 bus driver。 當 bus 無法自動偵測下游 device 的存在時,bus driver 呼叫下游 device driver 的 `identify()' operator ,由下游 driver 進行偵測。下游 driver 可透過使用者的設定,或 driver 本身的個別偵測方式,得知 device 的存在,並建立對應的 device object ,向上游 bus 登記該 device (add_child()),成為 bus 的下游 device (child)。 [attach:identify.png] 圖 (bus 呼叫 identify 流程) 在完成偵察後,bus driver 會為每一個 child 呼叫下游所有 device driver 的 `probe()',來為 device 投票。下游 device driver 的 `probe()' 會傳回一個整數 (>0 error),數字越大 (最大為 0),代表 driver 和 device 的相容程度愈高。在投票之後,上游 bus driver 選擇數字最大的 driver ,用以驅動下游 device 。(device_set_driver() and device_set_devclass()) 在決定好每一個 device 的 driver 之後,就可以呼叫下游 device driver 的 `attach()' method,進行 device 驅動。(device_probe_and_attach()) [attach:probe-attach.png] 圖 (probe 、 attach 呼叫流程) 需要修改 probe() 傳回的數值。 另外幾個 operator * detach * shutdown * suspend * resume == Bus Interface == Bus 除了上面的 operator 之外,還要 implement 自己的 interface ,包括下面的 operator: * add_child * print_child * probe_nomatch * ...... 共有 19 個 operator。 == Boot == * root * nexus * legacy $FreeBSD$ 的所有 device 都起源於 `root' bus ,定義於 kern/subr_bus.c 。 `root' 是 tree 的最頂端,連接 `nexus' 這個 bus 。 `nexus' 相當於系統的 system bus ,負責 IRQ 、 DMA 、 port address 和 RAM 等連接於 system bus 的資源之分配和管理。 `nexus' 下面接著 `legacy' bus,連接一些直接接於 system bus,但不支援 ACPI 的 device。如 CPU 、 PCI bus、 ISA bus 等。透過 `nexus' 和 `legacy' 的延伸,使系統內所有的 device 得以連接成一個 tree 。 系統在開機時,所有的 driver 透過 `DRIVER_MODULE' 巨集,宣告和登記 driver 和 devclass 。同時 kern/subr_bus.c 裡,以 `DECLARE_MODULE' 宣告一個 module ,並使的 module handler `root_bus_module_handler()' 被執行。 `root_bus_module_handler()' 負責初始化 `root' bus (`root_bus'),作為 tree 的起點 (root)。由於 `root' bus 是以特殊的方法進行初始,以避免 bus system 必需先初始才能之後,才能初始 device 的「雞生蛋、蛋生雞」問題。 接著開始 travel 整個 bus tree ,驅動系統所有的 device 。 travel 的起點是 `configure_first()' 和 `configure()' (in i386/i386/autoconf.c for i386 platform) ,透過 `SYSINIT' 促使開機程序執行這兩個 function。 `configure_first()' 先被執行,增加一個 device object 為 root 的 child ,並指定 devclass 為 `nexus' 。如此這個 device 就會以 `nexus' driver 加以驅動。接著執行 `configure()' , `configure()' 會呼叫 `root_bus_configure()' (in kern/subr_bus.c) 。而 `root_bus_configure()' 的內容為 `probe' 和 `attach' 所有 `root' 的 children 。這時,先前加入為 `root' 的 child 的 `nexus' bus ,就會被驅動,進而 nexus 會依據前面偵測、driver probe 和 attach 的流程,驅動 nexus 以下所有的 device 。一層接著一層,使的整個系統依 device 離 CPU 的距離被驅動起來。 [attach:bus-boot.png] 圖 (boot up 的流程) == 定義 driver == 定義 driver 三步曲 # 定義 methods # 定義 driver object # 宣告 driver module 依據 driver 的功能性,每個 driver 必需 implement 不同的 interface。Implement interface 的方法,是實作 interface 所定義的 operator,也就是為 driver 定義 method。 以 driver foo 定義 `identify()' 和 `probe()' 兩個 method 為例: {{{#!cpp static void foo_identify(driver_t *driver, device_t parent) { ...... } static int foo_probe(device_t dev) { ...... } static device_method_t foo_methods[] = { DEVMETHOD(device_identify, foo_identify), DEVMETHOD(device_probe, foo_probe), ...... {0, 0} }; }}} 我們必需為 foo 定義一個 method 的列表。如上面的 foo_methods ,為 driver foo 定義了 `identify()' 和 `probe()' 兩個 method 。 device_identify 和 device_probe 則是 identify 和 probe 這兩個 operator 的 opeartor description。(詳見 kobj) 定義 driver 的第二步驟則是,定義 driver object;也就是 driver 的本體。 {{{#!cpp driver_t foo_driver = { "foo", /* driver name */ foo_methods, /* method table */ 1, /* size of softc */ }; }}} 一般只需定義前三個欄位就可以了,第三個欄位是 device `softc' 的大小。`softc' 是由 driver 自行定義的一塊記憶體,用以記錄和 device 相關的資訊。當 bus sub-system create 一個新的 device object 時,會依這個欄位指定的大小,為 device 分配 `softc' 。 driver 在執行時,就可以使用這塊空間,存放 device 相關資訊。 最後的一個步驟,則是宣告 driver module 的存在。 kernel 在開機時或載入 kernel module 時,會根據 driver module 的宣告,自動向 bus sub-system 登記 driver 。 driver module 的宣告如下: {{{#!cpp static devclass_t foo_devclass; DRIVER_MODULE(foo, legacy, foo_driver, foo_devclass, NULL, NULL); }}} `foo' 是 driver 的名稱,也是對應 device class 的名稱。`foo_devclass' 是一個指標,用來放來 foo driver 所對應的 device class 的位置。在 bus sub-system 為 driver 進行登記之後, foo_devclass 就會指向對應的 device class。`legacy' 則是 driver 所要掛的上游 bus 的名稱,於是 foo 這個 driver 就會被加入 `legacy' 這個 device class。`foo_driver' 則是前面定義 driver object 。最後面兩個 NULL , 是宣告 driver 載入時所要執行的一個 callback function 和傳遞給 callback function 的參數資料。 以上就是 driver 大致的面貌,但實際上,依不同的 driver ,還有不同的細節。如 bus driver 就要 implement 一些額外的 operator 等等的。 == kobj == `kobj' 請參考 kobj(9) , kernel 使用 kobj 製造出一個 `$object-oriented$' 的環境,提供多型(polymorphism) 和繼承(inheritance) 的功能。前面提到的 driver 和 device 皆和 kobj 有對應關係。 kobj 定義 `kobj_t' 為物件,是 `kobj_class_t' 的 instance 。 `kobj_class_t' 定義了許多 `method' ,以實作 `interface' 所定義的 `operator'。 Interface 是 kobj 極重要的概念,以功能性為基礎,針對完成特定功能所需之命令,定義 `interface' 的 `operator'。 `operator' 是指物件所必需提供的 command ,和外界互動的介面。例如前面提到的 device interface ,就定義了 device 必需實作 identify 、 probe 、 attach 、...... 等,六個 operator。 雖然 interface 是 kobj 很重要的概念,然而, kobj 並沒有為 `interface' 定義物件或 structure, 只是單純作為一種概念。在實務上,`interface' 是 `kobjop_desc' (operator description) 的組合。`kobjop_desc' 是 operator 的實體化,作為 operator 的視別 ID ,並儲放相關資訊。 `kobjop_desc' 是 operator 的實體化,而 `kobj_method_t' 則是每個 class (kobj_class_t) 個別對 operator 的實作。class 透過 method 實作 interface 所定義的 kobjop_desc ,以實作 interface。在定義 method 時, kobj_method_t 就有一欄位,指定 method 實作的 kobjop_desc。 [attach:kobj-class-method-desc.png] 圖 (kobj_t、kobj_class_t 與 kobjop_desc 的關係) Bus sub-system 的 `driver' 其實就對應到 `kobj_class_t' ,而 `device' 則對應到 `kobj_t'。然而這個對應並不完全, bus sub-system 有對 kboj 進行修改,並不完全採用 kobj 提供的方法。這種修改,猜想是為了達到更高的效能。 == Conclusion == Bus sub-system 是 $FreeBSD$ I/O 系統的主軸,所有的 driver 由此延伸而出,相互連接在一起。本文多以關念性的討論,未來有機會再提供實作$範例$。而本文充滿了許多細節,不易寫作,盡量輔以圖表說明,卻也難免有不足之處。若有疏漏、謬誤,歡迎糾正、指教。 == 更新 == * 2008/06/03
最後更新時間: 2008-06-04 01:13:26 CST |
引用
查詢:
COMMENTS: