使用者空間 I/O HOWTO

作者:

Hans-Jürgen Koch Linux 開發者, Linutronix

日期:

2006-12-11

關於本文件

翻譯

如果您知道本文件的任何翻譯版本,或者您有興趣翻譯它,請傳送電子郵件給我 hjk@hansjkoch.de

前言

對於許多型別的裝置,建立 Linux 核心驅動程式有點過頭了。真正需要的只是某種處理中斷並提供對裝置記憶體空間訪問的方法。控制裝置的邏輯不一定必須在核心中,因為裝置不需要利用核心提供的任何其他資源。一種常見的此類裝置是工業 I/O 卡。

為了解決這種情況,設計了使用者空間 I/O 系統 (UIO)。對於典型的工業 I/O 卡,只需要一個非常小的核心模組。驅動程式的主要部分將在使用者空間中執行。這簡化了開發並降低了核心模組中出現嚴重錯誤的風險。

請注意,UIO 不是通用的驅動程式介面。那些已經被其他核心子系統(如網路、序列埠或 USB)很好處理的裝置不是 UIO 驅動程式的候選者。最適合 UIO 驅動程式的硬體應滿足以下所有條件

  • 裝置具有可以對映的記憶體。透過寫入此記憶體可以完全控制裝置。

  • 裝置通常會生成中斷。

  • 裝置不適合任何標準核心子系統。

致謝

我要感謝 Linutronix 的 Thomas Gleixner 和 Benedikt Spranger,他們不僅編寫了大部分 UIO 程式碼,還透過向我提供各種背景資訊,極大地幫助了我編寫這個 HOWTO。

反饋

發現本文件有任何錯誤嗎?(或者可能有什麼正確的地方?)我很樂意收到您的來信。請傳送電子郵件至 hjk@hansjkoch.de

關於 UIO

如果您使用 UIO 作為卡的驅動程式,您將獲得以下好處

  • 只需編寫和維護一個小核心模組。

  • 使用您習慣的所有工具和庫,在使用者空間中開發驅動程式的主要部分。

  • 驅動程式中的錯誤不會導致核心崩潰。

  • 無需重新編譯核心即可更新驅動程式。

UIO 的工作原理

每個 UIO 裝置都透過一個裝置檔案和幾個 sysfs 屬性檔案進行訪問。第一個裝置的裝置檔案將被稱為 /dev/uio0,後續裝置的裝置檔案將被稱為 /dev/uio1/dev/uio2,以此類推。

/dev/uioX 用於訪問卡的地址空間。只需使用 mmap() 即可訪問卡的暫存器或 RAM 位置。

中斷透過從 /dev/uioX 讀取來處理。一旦發生中斷,從 /dev/uioX 進行阻塞的 read() 將立即返回。您也可以在 /dev/uioX 上使用 select() 來等待中斷。從 /dev/uioX 讀取的整數值表示中斷總數。您可以使用此數字來確定是否錯過了某些中斷。

對於某些硬體,其內部有多箇中斷源,但沒有單獨的 IRQ 掩碼和狀態暫存器,在某些情況下,如果核心處理程式透過寫入晶片的 IRQ 暫存器來停用它們,則使用者空間可能無法確定中斷源是什麼。在這種情況下,核心必須完全停用 IRQ,以保持晶片的暫存器不動。現在,使用者空間部分可以確定中斷的原因,但無法重新啟用中斷。另一個極端情況是重新啟用中斷是對組合的 IRQ 狀態/確認暫存器進行讀-修改-寫操作的晶片。如果同時發生新的中斷,這將是競爭的。

為了解決這些問題,UIO 還實現了 write() 函式。它通常不使用,對於只有單箇中斷源或具有單獨的 IRQ 掩碼和狀態暫存器的硬體可以忽略它。但是,如果您需要它,則寫入 /dev/uioX 將呼叫驅動程式實現的 irqcontrol() 函式。您必須寫入一個 32 位值,該值通常為 0 或 1,以停用或啟用中斷。如果驅動程式未實現 irqcontrol(),則 write() 將返回 -ENOSYS

為了正確處理中斷,您的自定義核心模組可以提供自己的中斷處理程式。它將自動被內建處理程式呼叫。

對於不產生中斷但需要輪詢的卡,可以設定一個計時器,該計時器以可配置的時間間隔觸發中斷處理程式。此中斷模擬是透過從計時器的事件處理程式呼叫 uio_event_notify() 來完成的。

每個驅動程式都提供用於讀取或寫入變數的屬性。這些屬性可以透過 sysfs 檔案訪問。自定義核心驅動程式模組可以將其自己的屬性新增到 UIO 驅動程式擁有的裝置,但此時未新增到 UIO 裝置本身。如果發現有用,將來可能會更改。

UIO 框架提供了以下標準屬性

  • name: 您的裝置的名稱。建議為此使用核心模組的名稱。

  • version: 由您的驅動程式定義的版本字串。這允許驅動程式的 user space 部分處理不同版本的核心模組。

  • event: 自上次讀取裝置節點以來,驅動程式處理的中斷總數。

這些屬性出現在 /sys/class/uio/uioX 目錄下。請注意,此目錄可能是一個符號連結,而不是一個真正的目錄。任何訪問它的使用者空間程式碼都必須能夠處理此問題。

每個 UIO 裝置都可以使一個或多個記憶體區域可用於記憶體對映。這是必要的,因為某些工業 I/O 卡需要在驅動程式中訪問多個 PCI 記憶體區域。

每個對映在 sysfs 中都有自己的目錄,第一個對映顯示為 /sys/class/uio/uioX/maps/map0/。後續對映建立目錄 map1/map2/ 等。只有當對映的大小不為 0 時,這些目錄才會出現。

每個 mapX/ 目錄都包含四個只讀檔案,顯示記憶體的屬性

  • name: 此對映的字串識別符號。這是可選的,字串可以為空。驅動程式可以設定它,以便使用者空間更容易找到正確的對映。

  • addr: 可以對映的記憶體地址。

  • size: addr 指向的記憶體的大小,以位元組為單位。

  • offset: 必須新增到 mmap() 返回的指標的偏移量,以位元組為單位,才能到達實際的裝置記憶體。如果裝置的記憶體未頁對齊,這一點很重要。請記住,mmap() 返回的指標始終是頁對齊的,因此始終新增此偏移量是一種好的做法。

從使用者空間,不同的對映透過調整 mmap() 呼叫的 offset 引數來區分。要對映對映 N 的記憶體,您必須使用 N 乘以頁面大小作為偏移量

offset = N * getpagesize();

有時會有一些硬體具有類似記憶體的區域,無法使用此處描述的技術進行對映,但仍然可以透過使用者空間訪問它們。最常見的示例是 x86 ioport。在 x86 系統上,使用者空間可以使用 ioperm()iopl()inb()outb() 和類似的函式訪問這些 ioport。

由於這些 ioport 區域無法對映,因此它們不會像上面描述的普通記憶體一樣出現在 /sys/class/uio/uioX/maps/ 下。如果沒有關於硬體必須提供的埠區域的資訊,驅動程式的 user space 部分很難找出哪些埠屬於哪個 UIO 裝置。

為了解決這種情況,添加了新的目錄 /sys/class/uio/uioX/portio/。只有當驅動程式想要將關於一個或多個埠區域的資訊傳遞給使用者空間時,它才存在。如果是這種情況,則名為 port0port1 等的子目錄將出現在 /sys/class/uio/uioX/portio/ 下。

每個 portX/ 目錄都包含四個只讀檔案,顯示埠區域的名稱、起始位置、大小和型別

  • name: 此埠區域的字串識別符號。該字串是可選的,可以為空。驅動程式可以設定它,以便使用者空間更容易找到某個埠區域。

  • start: 此區域的第一個埠。

  • size: 此區域中的埠數。

  • porttype: 描述埠型別的字串。

編寫您自己的核心模組

請檢視 uio_cif.c 作為示例。以下段落解釋了此檔案的不同部分。

struct uio_info

此結構告訴框架有關您的驅動程式的詳細資訊。某些成員是必需的,而另一些成員是可選的。

  • const char *name: 必需。您的驅動程式的名稱,因為它將出現在 sysfs 中。我建議為此使用您的模組的名稱。

  • const char *version: 必需。此字串出現在 /sys/class/uio/uioX/version 中。

  • struct uio_mem mem[ MAX_UIO_MAPS ]: 如果您有可以使用 mmap() 對映的記憶體,則為必需。對於每個對映,您需要填寫一個 uio_mem 結構。有關詳細資訊,請參見下面的描述。

  • struct uio_port port[ MAX_UIO_PORTS_REGIONS ]: 如果您想將關於 ioport 的資訊傳遞給使用者空間,則為必需。對於每個埠區域,您需要填寫一個 uio_port 結構。有關詳細資訊,請參見下面的描述。

  • long irq: 必需。如果您的硬體產生中斷,則您的模組的任務是在初始化期間確定 irq 編號。如果您沒有硬體產生的中斷,但想以某種其他方式觸發中斷處理程式,請將 irq 設定為 UIO_IRQ_CUSTOM。如果您根本沒有中斷,則可以將 irq 設定為 UIO_IRQ_NONE,但這很少有意義。

  • unsigned long irq_flags: 如果您已將 irq 設定為硬體中斷編號,則為必需。此處給出的標誌將用於呼叫 request_irq()

  • int (*mmap)(struct uio_info *info, struct vm_area_struct *vma): 可選。如果您需要特殊的 mmap() 函式,可以在此處設定它。如果此指標不為 NULL,則將呼叫您的 mmap() 來代替內建的 mmap()

  • int (*open)(struct uio_info *info, struct inode *inode): 可選。您可能想要擁有自己的 open(),例如,僅當實際使用您的裝置時才啟用中斷。

  • int (*release)(struct uio_info *info, struct inode *inode): 可選。如果您定義了自己的 open(),您可能還想要一個自定義的 release() 函式。

  • int (*irqcontrol)(struct uio_info *info, s32 irq_on): 可選。如果您需要能夠透過寫入 /dev/uioX 來啟用或停用來自使用者空間的中斷,您可以實現此函式。引數 irq_on 將為 0 以停用中斷,為 1 以啟用中斷。

通常,您的裝置將具有一個或多個可以對映到使用者空間的記憶體區域。對於每個區域,您必須在 mem[] 陣列中設定一個 struct uio_mem。以下是 struct uio_mem 欄位的描述

  • const char *name: 可選。設定此值以幫助識別記憶體區域,它將顯示在相應的 sysfs 節點中。

  • int memtype: 如果使用對映,則為必需。如果您有要對映的卡上的物理記憶體,則將其設定為 UIO_MEM_PHYS。對於邏輯記憶體(例如,使用 __get_free_pages() 分配但不是 kmalloc()),請使用 UIO_MEM_LOGICAL。還有 UIO_MEM_VIRTUAL 用於虛擬記憶體。

  • phys_addr_t addr: 如果使用對映,則為必需。填寫您的記憶體塊的地址。此地址是出現在 sysfs 中的地址。

  • resource_size_t size: 填寫 addr 指向的記憶體塊的大小。如果 size 為零,則該對映被認為是未使用的。請注意,您*必須*使用零初始化所有未使用的對映的 size

  • void *internal_addr: 如果您必須從您的核心模組中訪問此記憶體區域,您將需要使用類似 ioremap() 的方法在內部對其進行對映。此函式返回的地址無法對映到使用者空間,因此您不得將其儲存在 addr 中。請使用 internal_addr 來記住這樣的地址。

請不要觸控 struct uio_memmap 元素!它由 UIO 框架用於為此對映設定 sysfs 檔案。只需保持原樣即可。

有時,您的裝置可能有一個或多個無法對映到使用者空間的埠區域。但是,如果使用者空間還有其他訪問這些埠的可能性,那麼在 sysfs 中提供關於埠的資訊是有意義的。對於每個區域,您必須在 port[] 陣列中設定一個 struct uio_port。以下是 struct uio_port 欄位的描述

  • char *porttype: 必需。將其設定為預定義的常量之一。對於 x86 架構中發現的 ioport,請使用 UIO_PORT_X86

  • unsigned long start: 如果使用埠區域,則為必需。填寫此區域的第一個埠的編號。

  • unsigned long size: 填寫此區域中的埠數。如果 size 為零,則該區域被認為是未使用的。請注意,您*必須*使用零初始化所有未使用的區域的 size

請不要觸控 struct uio_portportio 元素!它由 UIO 框架在內部用於為此區域設定 sysfs 檔案。只需保持原樣即可。

新增中斷處理程式

在您的中斷處理程式中需要執行的操作取決於您的硬體以及您希望如何處理它。您應該儘量減少核心中斷處理程式中的程式碼量。如果您的硬體在每次中斷後不需要您執行任何操作,則您的處理程式可以為空。

另一方面,如果您的硬體*需要*在每次中斷後執行某些操作,那麼您*必須*在您的核心模組中執行它。請注意,您不能依賴驅動程式的 user space 部分。您的使用者空間程式可以隨時終止,可能會使您的硬體處於仍然需要正確處理中斷的狀態。

可能還會有一些應用程式,您希望在每次中斷時從硬體讀取資料並將其緩衝在您為此目的分配的一塊核心記憶體中。使用此技術,如果您的使用者空間程式錯過中斷,您可以避免資料丟失。

關於共享中斷的說明:您的驅動程式應該儘可能支援中斷共享。當且僅當您的驅動程式可以檢測到您的硬體是否觸發了中斷時,才有可能進行中斷共享。這通常透過檢視中斷狀態暫存器來完成。如果您的驅動程式看到 IRQ 位實際上已設定,它將執行其操作,並且處理程式返回 IRQ_HANDLED。如果驅動程式檢測到不是您的硬體導致了中斷,它將不執行任何操作並返回 IRQ_NONE,允許核心呼叫下一個可能的中斷處理程式。

如果您決定不支援共享中斷,您的卡將無法在沒有可用中斷的計算機中工作。由於這經常發生在 PC 平臺上,因此您可以透過支援中斷共享來避免很多麻煩。

使用 uio_pdrv 作為平臺裝置

在許多情況下,平臺裝置的 UIO 驅動程式可以以通用方式處理。在您定義 struct platform_device 的相同位置,您只需也實現您的中斷處理程式並填寫您的 struct uio_info。然後,指向此 struct uio_info 的指標用作您的平臺裝置的 platform_data

您還需要設定一個 struct resource 陣列,其中包含您的記憶體對映的地址和大小。此資訊使用 struct platform_device.resource.num_resources 元素傳遞給驅動程式。

您現在必須將 struct platform_device.name 元素設定為 "uio_pdrv" 以使用通用的 UIO 平臺裝置驅動程式。此驅動程式將根據給定的資源填寫 mem[] 陣列,並註冊裝置。

此方法的優點是您只需要編輯一個您無論如何都需要編輯的檔案。您無需建立額外的驅動程式。

使用 uio_pdrv_genirq 作為平臺裝置

特別是在嵌入式裝置中,您經常會發現晶片的 IRQ 引腳連線到其自己的專用中斷線上。在這種情況下,如果您能真正確定中斷沒有被共享,我們可以將 uio_pdrv 的概念更進一步,並使用通用的中斷處理程式。這就是 uio_pdrv_genirq 所做的。

此驅動程式的設定與上述 uio_pdrv 的描述相同,只是您不實現中斷處理程式。struct uio_info.handler 元素必須保持 NULL.irq_flags 元素不得包含 IRQF_SHARED

您需要將 struct platform_device.name 元素設定為 "uio_pdrv_genirq" 才能使用此驅動程式。

uio_pdrv_genirq 的通用中斷處理程式將簡單地使用 disable_irq_nosync() 停用中斷線。完成其工作後,使用者空間可以透過向 UIO 裝置檔案寫入 0x00000001 重新啟用中斷。該驅動程式已經實現了一個 irq_control() 使其成為可能,您不得實現自己的。

使用 uio_pdrv_genirq 不僅可以節省幾行中斷處理程式程式碼。您也不需要了解任何關於晶片內部暫存器的知識來建立驅動程式的核心部分。您只需要知道晶片連線的引腳的 IRQ 編號即可。

當在啟用裝置樹的系統中使用時,驅動程式需要使用設定為該節點 "compatible" 字串的 "of_id" 模組引數進行探測,該驅動程式應該處理該節點。預設情況下,節點的名稱(沒有單元地址)作為使用者空間中 UIO 裝置的名稱公開。要設定自定義名稱,可以在 DT 節點中指定名為 "linux,uio-name" 的屬性。

為平臺裝置使用 uio_dmem_genirq

除了靜態分配的記憶體範圍之外,還可能希望在使用者空間驅動程式中使用動態分配的區域。特別是,能夠訪問透過 dma-mapping API 提供的記憶體可能特別有用。uio_dmem_genirq 驅動程式提供了一種實現此目的的方法。

此驅動程式的使用方式與 "uio_pdrv_genirq" 驅動程式在中斷配置和處理方面類似。

struct platform_device.name 元素設定為 "uio_dmem_genirq" 才能使用此驅動程式。

使用此驅動程式時,請填寫 struct platform_device.platform_data 元素,該元素型別為 struct uio_dmem_genirq_pdata,其中包含以下元素

  • struct uio_info uioinfo:與 uio_pdrv_genirq 平臺數據使用的結構相同

  • unsigned int *dynamic_region_sizes:指向要對映到使用者空間的動態記憶體區域大小列表的指標。

  • unsigned int num_dynamic_regionsdynamic_region_sizes 陣列中的元素數量。

平臺數據中定義的動態區域將附加到平臺裝置資源之後的 `` mem[] `` 陣列中,這意味著靜態和動態記憶體區域的總數不能超過 MAX_UIO_MAPS

動態記憶體區域將在 UIO 裝置檔案 /dev/uioX 開啟時分配。與靜態記憶體資源類似,動態區域的記憶體區域資訊隨後透過 sysfs 在 /sys/class/uio/uioX/maps/mapY/* 中可見。動態記憶體區域將在 UIO 裝置檔案關閉時釋放。當沒有程序保持裝置檔案開啟時,返回給使用者空間的地址為 ~0。

用使用者空間編寫驅動程式

一旦您擁有適用於您的硬體的工作核心模組,您就可以編寫驅動程式的使用者空間部分。您不需要任何特殊的庫,您的驅動程式可以用任何合理的語言編寫,您可以使用浮點數等等。簡而言之,您可以使用您通常用於編寫使用者空間應用程式的所有工具和庫。

獲取關於您的 UIO 裝置的資訊

關於所有 UIO 裝置的資訊都可以在 sysfs 中找到。您的驅動程式中應該做的第一件事是檢查 nameversion 以確保您與正確的裝置通訊,並且其核心驅動程式具有您期望的版本。

您還應該確保您需要的記憶體對映存在並且具有您期望的大小。

有一個名為 lsuio 的工具,它列出 UIO 裝置及其屬性。它可以在這裡找到

http://www.osadl.org/projects/downloads/UIO/user/

使用 lsuio,您可以快速檢查您的核心模組是否已載入以及它匯出了哪些屬性。請檢視手冊頁以獲取詳細資訊。

lsuio 的原始碼可以用作獲取關於 UIO 裝置資訊的示例。檔案 uio_helper.c 包含許多您可以在使用者空間驅動程式程式碼中使用的函式。

mmap() 裝置記憶體

在您確保您擁有正確的裝置以及您需要的記憶體對映後,您所要做的就是呼叫 mmap() 以將裝置的記憶體對映到使用者空間。

mmap() 呼叫的引數 offset 對於 UIO 裝置具有特殊的含義:它用於選擇您要對映的裝置的哪個對映。要對映對映 N 的記憶體,您必須使用 N 乘以頁面大小作為您的偏移量

offset = N * getpagesize();

N 從零開始,因此如果您只有一個記憶體範圍要對映,請設定 offset = 0。此技術的缺點是記憶體始終從其起始地址開始對映。

等待中斷

在您成功映射了您的裝置記憶體後,您可以像訪問普通陣列一樣訪問它。通常,您將執行一些初始化。之後,您的硬體開始工作,並在完成後立即生成一箇中斷,有可用資料,或者因為發生了錯誤需要您的注意。

/dev/uioX 是一個只讀檔案。read() 將始終阻塞直到發生中斷。對於 read()count 引數只有一個合法值,即帶符號的 32 位整數的大小 (4)。count 的任何其他值都會導致 read() 失敗。讀取的帶符號的 32 位整數是您裝置的中斷計數。如果該值比您上次讀取的值大 1,則一切正常。如果差值大於 1,則您錯過了中斷。

您也可以在 /dev/uioX 上使用 select()

通用 PCI UIO 驅動程式

通用驅動程式是一個名為 uio_pci_generic 的核心模組。它可以與任何符合 PCI 2.3 (大約 2002 年) 和任何符合 PCI Express 裝置的裝置一起工作。使用它,您只需要編寫使用者空間驅動程式,從而無需編寫特定於硬體的核心模組。

使驅動程式識別裝置

由於該驅動程式未宣告任何裝置 ID,因此它不會自動載入,也不會自動繫結到任何裝置,您必須自己載入它並將 ID 分配給驅動程式。例如

modprobe uio_pci_generic
echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id

如果您的裝置已經有一個特定於硬體的核心驅動程式,則通用驅動程式仍然不會繫結到它,在這種情況下,如果您想使用通用驅動程式(您為什麼要這樣做?),您必須手動解除繫結特定於硬體的驅動程式並繫結通用驅動程式,如下所示

echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind

您可以透過在 sysfs 中查詢裝置來驗證該裝置是否已繫結到驅動程式,例如以下所示

ls -l /sys/bus/pci/devices/0000:00:19.0/driver

如果成功,應該列印

.../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic

請注意,通用驅動程式不會繫結到舊的 PCI 2.2 裝置。如果繫結裝置失敗,請執行以下命令

dmesg

並在輸出中查詢失敗原因。

關於 uio_pci_generic 的須知

中斷使用 PCI 命令暫存器中的中斷停用位和 PCI 狀態暫存器中的中斷狀態位來處理。所有符合 PCI 2.3 (大約 2002 年) 的裝置和所有符合 PCI Express 裝置的裝置都應該支援這些位。uio_pci_generic 檢測到這種支援,並且不會繫結到不支援命令暫存器中的中斷停用位的裝置。

在每次中斷時,uio_pci_generic 設定中斷停用位。這會阻止裝置生成進一步的中斷,直到清除該位。使用者空間驅動程式應該在阻塞和等待更多中斷之前清除該位。

使用 uio_pci_generic 編寫使用者空間驅動程式

使用者空間驅動程式可以使用 pci sysfs 介面,或者封裝它的 libpci 庫,來與裝置通訊並透過寫入命令暫存器來重新啟用中斷。

使用 uio_pci_generic 的示例程式碼

以下是一些使用 uio_pci_generic 的示例使用者空間驅動程式程式碼

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
    int uiofd;
    int configfd;
    int err;
    int i;
    unsigned icount;
    unsigned char command_high;

    uiofd = open("/dev/uio0", O_RDONLY);
    if (uiofd < 0) {
        perror("uio open:");
        return errno;
    }
    configfd = open("/sys/class/uio/uio0/device/config", O_RDWR);
    if (configfd < 0) {
        perror("config open:");
        return errno;
    }

    /* Read and cache command value */
    err = pread(configfd, &command_high, 1, 5);
    if (err != 1) {
        perror("command config read:");
        return errno;
    }
    command_high &= ~0x4;

    for(i = 0;; ++i) {
        /* Print out a message, for debugging. */
        if (i == 0)
            fprintf(stderr, "Started uio test driver.\n");
        else
            fprintf(stderr, "Interrupts: %d\n", icount);

        /****************************************/
        /* Here we got an interrupt from the
           device. Do something to it. */
        /****************************************/

        /* Re-enable interrupts. */
        err = pwrite(configfd, &command_high, 1, 5);
        if (err != 1) {
            perror("config write:");
            break;
        }

        /* Wait for next interrupt. */
        err = read(uiofd, &icount, 4);
        if (err != 4) {
            perror("uio read:");
            break;
        }

    }
    return errno;
}

通用 Hyper-V UIO 驅動程式

通用驅動程式是一個名為 uio_hv_generic 的核心模組。它支援 Hyper-V VMBus 上的裝置,類似於 PCI 總線上的 uio_pci_generic。

使驅動程式識別裝置

由於該驅動程式未宣告任何裝置 GUID,因此它不會自動載入,也不會自動繫結到任何裝置,您必須自己載入它並將 ID 分配給驅動程式。例如,要使用網路裝置類 GUID

modprobe uio_hv_generic
echo "f8615163-df3e-46c5-913f-f2d2f965ed0e" > /sys/bus/vmbus/drivers/uio_hv_generic/new_id

如果裝置已經有一個特定於硬體的核心驅動程式,則通用驅動程式仍然不會繫結到它,在這種情況下,如果您想將通用驅動程式用於使用者空間庫,您必須手動解除繫結特定於硬體的驅動程式並繫結通用驅動程式,使用裝置特定的 GUID,如下所示

echo -n ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/hv_netvsc/unbind
echo -n ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/uio_hv_generic/bind

您可以透過在 sysfs 中查詢裝置來驗證該裝置是否已繫結到驅動程式,例如以下所示

ls -l /sys/bus/vmbus/devices/ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver

如果成功,應該列印

.../ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver -> ../../../bus/vmbus/drivers/uio_hv_generic

關於 uio_hv_generic 的須知

在每次中斷時,uio_hv_generic 設定中斷停用位。這會阻止裝置生成進一步的中斷,直到清除該位。使用者空間驅動程式應該在阻塞和等待更多中斷之前清除該位。

當主機撤銷裝置時,中斷檔案描述符將被標記為關閉,並且對中斷檔案描述符的任何讀取都將返回 -EIO。類似於關閉的套接字或斷開連線的序列裝置。

vmbus 裝置區域被對映到 uio 裝置資源中
  1. 通道環形緩衝區:訪客到主機和主機到訪客

  2. 訪客到主機中斷訊號頁面

  3. 訪客到主機監視頁面

  4. 網路接收緩衝區區域

  5. 網路傳送緩衝區區域

如果透過向主機請求建立了一個子通道,則 uio_hv_generic 裝置驅動程式將為每個通道的環形緩衝區建立一個 sysfs 二進位制檔案。例如

/sys/bus/vmbus/devices/3811fe4d-0fa0-4b62-981a-74fc1084c757/channels/21/ring

更多資訊