Fuchsia 驅動程式是共用程式庫,會在使用者空間的驅動程式代管程序中動態載入。載入驅動程式庫的程序是由驅動程式管理器控管。如要進一步瞭解驅動程式庫代管程序、驅動程式管理器工具,以及驅動程式庫和裝置生命週期,請參閱「裝置模型」。
目錄結構
您可以在整個原始碼樹狀結構中,按照原始碼版面配置文件指定的區域,在 driver 子目錄下找到驅動程式。大多數 Fuchsia 驅動程式都位於 //src/devices/ 底下。並依據實作的通訊協定分組。驅動程式通訊協定定義於 ddk/include/lib/ddk/protodefs.h。舉例來說,USB 乙太網路驅動程式庫會進入 //src/connectivity/ethernet/drivers/,而不是 //src/devices/usb/drivers/,因為它會實作乙太網路通訊協定。不過,實作 USB 堆疊的驅動程式位於 //src/devices/usb/drivers/,因為這些驅動程式會實作 USB 通訊協定。
驅動程式庫應在 BUILD.gn 中看到 fuchsia_driver_component 目標。
如要讓驅動程式庫顯示在 /boot/driver 下方,應將其納入 //boards 下相關主機板檔案的 board_bootfs_labels 清單中。如要讓它顯示在 /system/driver 中,應使用 driver_package 建構目標將其新增至系統套件,然後由 //boards 下的相關板檔案參照。驅動程式管理器會先在 /boot/driver 中尋找可載入的驅動程式,然後在 /system/driver/ 中尋找。
建立新驅動程式庫
使用「建立工具」即可自動建立新驅動程式庫。只要執行下列指令即可:
fx create driver --path <PATH> --lang cpp
這會建立包含空白驅動程式庫的 <PATH> 目錄,其中 <PATH> 的最後一個區隔是驅動程式庫名稱和 GN 目標名稱。執行這項指令後,請按照下列步驟操作:
- 在正確的位置加入
fuchsia_driver_component或driver_package建構目標,將驅動程式納入系統。 - 如果是封裝的驅動程式,則應將
driver_package建構目標新增至//boards或//vendor/<foo>/boards的相關主機板檔案,以做為xx_package_labelsGN 引數。 - 如果是啟動驅動程式,應將
fuchsia_driver_component建構目標新增至//boards中的相關板檔案,或新增至//vendor/<foo>/boards的board_bootfs_labelsGN 引數。 - 在
<PATH>:tests建構目標中加入tests建構目標,將測試納入 CQ。 - 在
meta/<NAME>.bind中新增適當的繫結規則。 - 在「
meta/<NAME>-info.json」中新增驅動程式庫資訊。檔案必須包含至少一個與//build/drivers/areas.txt所列區域相符的short_description和areas。 - 編寫驅動程式庫的功能。
宣告驅動程式庫
驅動程式庫至少應包含驅動程式庫宣告,並實作 bind() 驅動程式庫作業。
當驅動程式管理器成功為裝置找到相符的驅動程式庫時,就會載入驅動程式並繫結至裝置。驅動程式會透過繫結規則宣告相容的裝置,這些規則應與驅動程式庫一起放在 .bind 檔案中。繫結編譯器會編譯這些規則,並在 C 標頭檔案中建立包含這些規則的驅動程式庫宣告巨集。下列繫結規則會宣告 AHCI 驅動程式:
using fuchsia.pci;
using fuchsia.pci.massstorage;
fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PCI_CLASS == fuchsia.pci.BIND_PCI_CLASS.MASS_STORAGE;
fuchsia.BIND_PCI_SUBCLASS == fuchsia.pci.massstorage.BIND_PCI_SUBCLASS_SATA;
fuchsia.BIND_PCI_INTERFACE == 0x01;
fuchsia.BIND_COMPOSITE == 1;
這些繫結規則指出,驅動程式庫會繫結至 BIND_PROTOCOL 屬性與 pci 命名空間中的 DEVICE 相符,且具有指定 PCI 類別/子類別/介面的裝置。pci 命名空間是從第一行的 fucnsia.pci 程式庫匯入。詳情請參閱繫結說明文件。
如要產生包含這些繫結規則的驅動程式庫宣告巨集,就必須有對應的 bind_rules 建構目標。這會宣告與繫結檔案中「using」陳述式對應的依附元件。
driver_bind_rules("bind") {
rules = "meta/ahci.bind"
bind_output = "ahci.bindbc"
deps = [
"//src/devices/bind/fuchsia.pci",
"//src/devices/bind/fuchsia.pci.massstorage",
]
}
驅動程式現在可以加入產生的標頭,並使用下列巨集宣告自身。"zircon" 是供應商 ID,"0.1" 是驅動程式庫版本。
#include <lib/ddk/binding_driver.h>
...
ZIRCON_DRIVER(ahci, ahci_driver_ops, "zircon", "0.1");
PCI 驅動程式會發布具有下列屬性的相符裝置:
zx_device_str_prop_t pci_device_props[] = {
ddk::MakeStrProperty(bind_fuchsia::PCI_VID,
static_cast<uint32_t>(device.info.vendor_id)),
ddk::MakeStrProperty(bind_fuchsia::PCI_DID,
static_cast<uint32_t>(device.info.device_id)),
ddk::MakeStrProperty(bind_fuchsia::PCI_CLASS,
static_cast<uint32_t>(device.info.base_class)),
ddk::MakeStrProperty(bind_fuchsia::PCI_SUBCLASS,
static_cast<uint32_t>(device.info.sub_class)),
ddk::MakeStrProperty(bind_fuchsia::PCI_INTERFACE,
static_cast<uint32_t>(device.info.program_interface)),
ddk::MakeStrProperty(bind_fuchsia::PCI_REVISION,
static_cast<uint32_t>(device.info.revision_id)),
ddk::MakeStrProperty(bind_fuchsia::PCI_TOPO, pci_bind_topo),
};
目前,繫結變數和巨集是在 lib/ddk/binding.h 中定義。在不久的將來,所有節點屬性都會由繫結程式庫定義,例如上述匯入的 fuchsia.pci 程式庫。如果您要導入新的裝置類別,可能也需要將新的節點屬性導入繫結標頭和繫結程式庫。
節點屬性為 32 位元值。如果變數值需要大於 32 位元的值,請將其分割為多個 32 位元變數。例如 ACPI HID 值,長度為 8 個字元 (64 位元)。這項功能分為 BIND_ACPI_HID_0_3 和 BIND_ACPI_HID_4_7。完成繫結程式庫的遷移作業後,您就能使用其他資料類型,例如字串、較大的數字和布林值。
您可以在 bind_rules 建構規則中指定 disable_autobind = true,停用自動繫結行為。在這種情況下,可以使用 fuchsia.device.Controller/Bind FIDL 呼叫,將驅動程式庫繫結至裝置。
驅動程式繫結
驅動程式與裝置配對時,系統會呼叫驅動程式的 bind() 函式。一般來說,驅動程式庫會初始化裝置所需的任何資料結構,並在這個函式中初始化硬體。請勿在這個函式中執行任何耗時的工作或封鎖,因為這個函式是從驅動程式代管程序的 RPC 執行緒叫用,且無法同時處理其他要求。而是應該產生新的執行緒來執行冗長的工作。
驅動程式不應對
bind() 中的硬體狀態做出任何假設。可能需要重設硬體,或確保硬體處於已知狀態。由於系統會重新產生驅動程式主機,從驅動程式庫程式當機中復原,因此在叫用 bind() 時,硬體可能處於不明狀態。
bind() 通常會產生四種結果:
驅動程式會判斷裝置是否受支援,不需要執行任何繁重的工作,因此會發布新裝置 (使用 C 中的
device_add()或 DDKTL C++ 包裝函式程式庫中的ddk::Device::DdkAdd()),並傳回 `ZX_OK。驅動程式判斷即使繫結規則相符,裝置仍無法支援 (可能是因為檢查硬體版本位元或其他原因),並傳回錯誤。
驅動程式需要進一步初始化,才能準備好裝置或確定可以支援裝置,因此會發布實作
init()鉤子的隱形裝置,並啟動執行緒繼續工作,同時將ZX_OK傳回bind()。該執行緒最終會呼叫 C 中的device_init_reply(),或 DDKTL C++ 包裝函式庫中的ddk::InitTxn::Reply()。在收到回覆前,裝置保證不會遭到移除。如果狀態能夠成功初始化裝置,且應顯示該裝置,則會顯示ZX_OK,否則會顯示錯誤,表示應移除裝置。驅動程式代表匯流排或控制器,具有 0..n 個可能動態出現或消失的子項。在這種情況下,它應立即發布代表匯流排或控制器的裝置,然後動態發布代表該匯流排上硬體的子項 (下游驅動程式會繫結至該子項)。例如:AHCI/SATA、USB 等。
系統新增並顯示裝置後,用戶端程序和相容驅動程式就能繫結該裝置。
Banjo 通訊協定
驅動程式會為裝置提供一組裝置作業和選用通訊協定作業。 裝置作業會實作裝置生命週期方法和裝置的外部介面,供其他使用者空間應用程式和服務呼叫。通訊協定作業會實作裝置的程序內通訊協定,這些通訊協定是由載入至相同驅動程式代管程序的其他驅動程式呼叫。
您可以在 device_add_args_t 中傳遞裝置的一組通訊協定作業。如果裝置支援多種通訊協定,請實作 get_protocol() 裝置作業。裝置只能有一個通訊協定 ID。如果該類別仍啟用 devfs,通訊協定 ID 會對應至裝置發布時所屬的類別。
駕駛操作
驅動程式通常會透過處理子項驅動程式或其他程序的用戶端要求來運作。它會直接與硬體通訊 (例如透過 MMIO),或與其父項裝置通訊 (例如將 USB 交易排入佇列),以滿足這些要求。
驅動程式代管程序外部程序提出的外部用戶端要求,會由子驅動程式 (通常在同一個程序中) 透過與裝置類別對應的 banjo 通訊協定完成。駕駛人對驅動程式庫的要求應使用 banjo 通訊協定,而非裝置作業。
裝置可以在父項裝置上呼叫 device_get_protocol(),取得父項支援的通訊協定。
裝置中斷
裝置中斷是由中斷物件實作,這是一種核心物件。驅動程式會在裝置通訊協定方法中,向父項裝置要求裝置中斷的控制代碼。傳回的控制代碼會繫結至裝置的適當中斷,如父項驅動程式庫所定義。舉例來說,PCI 通訊協定會為 PCI 子項實作 map_interrupt()。驅動程式應產生執行緒,等待中斷處理常式。
核心會根據中斷是邊緣觸發還是電位觸發,自動適當處理中斷遮罩和取消遮罩。如果是電位觸發的硬體中斷,zx_interrupt_wait() 會在傳回前遮罩中斷,並在下次再次呼叫時取消遮罩中斷。如果是邊緣觸發中斷,中斷會保持未遮罩狀態。
插播執行緒不應執行任何耗時偏長的工作。如果驅動程式會執行冗長的工作,請使用背景工作執行緒。
您可以在 ZX_INTERRUPT_SLOT_USER 插槽上使用 zx_interrupt_trigger() 發出中斷處理常式信號,從 zx_interrupt_wait() 傳回。在驅動程式庫清理期間,必須關閉中斷執行緒。
FIDL 訊息
非驅動程式庫程序
每個裝置類別的訊息都是以 FIDL 語言定義。每部裝置都會實作零或多個 FIDL 通訊協定,並透過每個用戶端的單一管道進行多工處理。驅動程式可透過 message() 鉤子解讀 FIDL 訊息。
其他程序中的驅動程式
如果驅動程式庫需要與另一個程序中的驅動程式庫通訊,則必須代管傳出目錄 (類似於元件),而非定義通訊協定作業,該目錄應代管子項驅動程式庫程式在繫結時會存取的所有 FIDL 通訊協定。
通訊協定作業與 FIDL 訊息
通訊協定作業會定義裝置的程序內 API。FIDL 訊息定義了用於跨程序通訊的 API。如果函式是要由同一程序中的其他驅動程式呼叫,請定義通訊協定作業。驅動程式應在其父項上呼叫通訊協定作業,才能使用這些函式。
隔離裝置
使用 DEVICE_ADD_MUST_ISOLATE 新增的裝置會產生新的驅動程式代管程序。裝置必須有隨附的傳出目錄,用於託管 FIDL 通訊協定。繫結至裝置的驅動程式會載入新的驅動程式代管程序,並可連線至父項驅動程式庫提供的傳出目錄中匯出的 FIDL 通訊協定。
司機權利
雖然驅動程式在使用者空間程序中執行,但權限比一般程序更受限。駕駛人不得存取檔案系統,包括 devfs。也就是說,驅動程式庫無法與任意裝置互動。如果驅動程式需要執行這項操作,請考慮改為編寫服務元件。舉例來說,虛擬控制台是由 virtcon 元件實作。
zx_vmo_create_contiguous() 和 zx_interrupt_create 等特殊權限作業需要根資源控制代碼。系統驅動程式庫 (x86 系統上的 ACPI 和 ARM 系統上的平台) 以外的驅動程式無法使用這個控制代碼。裝置應要求其父項代為執行這類作業。如果父項驅動程式庫的通訊協定無法解決這個用途,請與作者聯絡。
同樣地,驅動程式庫不得要求任意 MMIO 範圍、中斷或 GPIO。PCI 和平台等匯流排驅動程式只會傳回與子裝置相關聯的資源。
進階主題和提示
初始化時間過長
如果裝置初始化時間過長,該怎麼辦?在討論上述 null_bind() 函式時,我們指出成功傳回值會告知驅動程式管理器,驅動程式庫現在已與裝置建立關聯。我們無法在繫結函式中花費太多時間;基本上,我們應該初始化裝置、發布裝置,然後完成。
但裝置可能需要執行長時間的初始化作業,例如:
- 列舉硬體點
- 載入韌體
- 協商通訊協定
等,這可能需要很長的時間。
您可以實作裝置 init() 鉤子,將裝置發布為「隱藏」。裝置透過 device_add() 新增後,系統會執行 init() 掛鉤,可用於安全存取裝置狀態及產生背景工作執行緒。裝置會保持隱藏狀態,且保證在呼叫 device_init_reply() 之前不會移除,這項操作可從任何執行緒完成。這符合繫結函式的規定,但沒有人能使用您的裝置 (因為還沒有人知道,裝置目前處於隱藏狀態)。現在,裝置可以使用背景執行緒執行長時間作業。
當裝置準備好為用戶端要求提供服務時,請呼叫 device_init_reply(),讓裝置顯示在路徑名稱空間中。
節省電力
裝置提供兩個標註 suspend() 和 resume(),可支援省電或其他資源的功能。
兩者都會採用裝置內容指標和旗標引數,但旗標引數僅用於暫停情況。
| 檢舉 | 意義 |
|---|---|
DEVICE_SUSPEND_FLAG_REBOOT |
驅動程式應自行關閉,為重新啟動或關閉機器做好準備 |
DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER |
? |
DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY |
? |
DEVICE_SUSPEND_FLAG_POWEROFF |
驅動程式應會自行關閉,準備關機 |
參考資料:支援函式
本節列出提供給駕駛人使用的支援功能。
存取子函式
做為第一個引數傳遞至驅動程式通訊協定函式的內容區塊是不透明的資料結構。也就是說,如要存取資料元素,您必須呼叫存取子函式:
| 函式 | 目的 |
|---|---|
device_get_name() |
擷取裝置名稱 |
管理功能
下列函式可用於管理裝置:
| 函式 | 目的 |
|---|---|
device_add() |
將裝置新增至父項 |
device_async_remove() |
排定移除裝置及其所有子項的時間 |