將驅動程式移植到新的驅動模型

Patrick Mochel

2003 年 1 月 7 日

概述

請參閱 Documentation/driver-api/driver-model/*.rst 以瞭解各種驅動程式型別和概念的定義。

將裝置驅動程式移植到新模型的大部分工作發生在匯流排驅動程式層。這是有意為之,旨在最大限度地減少對核心驅動程式的負面影響,並允許匯流排驅動程式逐步過渡。

簡而言之,驅動程式模型由一組可以嵌入到更大的、特定於匯流排的物件中的物件組成。這些通用物件中的欄位可以替換特定於匯流排的物件中的欄位。

通用物件必須在驅動程式模型核心中註冊。透過這樣做,它們將透過 sysfs 檔案系統匯出。可以透過執行以下操作來掛載 sysfs

# mount -t sysfs sysfs /sys

過程

步驟 0:閱讀 include/linux/device.h 以瞭解物件和函式定義。

步驟 1:註冊匯流排驅動程式。

  • 為匯流排驅動程式定義一個 struct bus_type

    struct bus_type pci_bus_type = {
          .name           = "pci",
    };
    
  • 註冊匯流排型別。

    這應該在匯流排型別的初始化函式中完成,該函式通常是 module_init() 或等效函式

    static int __init pci_driver_init(void)
    {
            return bus_register(&pci_bus_type);
    }
    
    subsys_initcall(pci_driver_init);
    

    可以透過執行以下操作來取消註冊匯流排型別(如果匯流排驅動程式可以編譯為模組)

    bus_unregister(&pci_bus_type);
    
  • 匯出匯流排型別供其他人使用。

    其他程式碼可能希望引用匯流排型別,因此在共享標頭檔案中宣告它並匯出該符號。

來自 include/linux/pci.h

extern struct bus_type pci_bus_type;

來自上述程式碼所在的檔案

EXPORT_SYMBOL(pci_bus_type);
  • 這將導致匯流排出現在 /sys/bus/pci/ 中,其中包含兩個子目錄:“devices”和“drivers”

    # tree -d /sys/bus/pci/
    /sys/bus/pci/
    |-- devices
    `-- drivers
    

步驟 2:註冊裝置。

struct device 表示單個裝置。它主要包含描述裝置與其他實體關係的元資料。

  • struct device 嵌入到特定於匯流排的裝置型別中

    struct pci_dev {
           ...
           struct  device  dev;            /* Generic device interface */
           ...
    };
    

    建議將通用裝置不作為結構體中的第一項,以防止程式設計師在物件型別之間進行無腦的強制轉換。而是應該建立宏或行內函數,以從通用物件型別進行轉換

    #define to_pci_dev(n) container_of(n, struct pci_dev, dev)
    
    or
    
    static inline struct pci_dev * to_pci_dev(struct kobject * kobj)
    {
        return container_of(n, struct pci_dev, dev);
    }
    

    這允許編譯器驗證執行操作的型別安全性(這是好事)。

  • 在註冊時初始化裝置。

    當裝置被發現或註冊到匯流排型別時,匯流排驅動程式應初始化通用裝置。最重要的是初始化 bus_id、parent 和 bus 欄位。

    bus_id 是一個 ASCII 字串,包含裝置在總線上的地址。此字串的格式是特定於匯流排的。這對於在 sysfs 中表示裝置是必需的。

    parent 是裝置的物理父級。匯流排驅動程式正確設定此欄位非常重要。

    驅動程式模型維護一個有序的裝置列表,用於電源管理。此列表必須按順序排列,以保證裝置在其物理父級之前關閉,反之亦然。此列表的順序由已註冊裝置的父級決定。

    此外,裝置 sysfs 目錄的位置取決於裝置的父級。sysfs 匯出一個映象裝置層次結構的目錄結構。準確設定父級可保證 sysfs 準確表示層次結構。

    裝置的 bus 欄位是指向裝置所屬的匯流排型別的指標。這應設定為之前宣告和初始化的 bus_type。

    可選地,匯流排驅動程式可以設定裝置的 name 和 release 欄位。

    name 欄位是描述裝置的 ASCII 字串,例如

    “ATI Technologies Inc Radeon QD”

    release 欄位是一個回撥函式,當裝置已被移除且對其的所有引用都已釋放時,驅動程式模型核心會呼叫該回調函式。稍後會詳細介紹。

  • 註冊裝置。

    一旦通用裝置被初始化,就可以透過執行以下操作將其註冊到驅動程式模型核心

    device_register(&dev->dev);
    

    稍後可以透過執行以下操作取消註冊

    device_unregister(&dev->dev);
    

    這應該發生在支援熱插拔裝置的總線上。如果匯流排驅動程式取消註冊裝置,則不應立即釋放它。而是應該等待驅動程式模型核心呼叫裝置的 release 方法,然後釋放特定於匯流排的物件。(可能還有其他程式碼當前正在引用裝置結構,並且在發生這種情況時釋放裝置是不禮貌的)。

    註冊裝置後,將在 sysfs 中建立一個目錄。sysfs 中的 PCI 樹如下所示

    /sys/devices/pci0/
    |-- 00:00.0
    |-- 00:01.0
    |   `-- 01:00.0
    |-- 00:02.0
    |   `-- 02:1f.0
    |       `-- 03:00.0
    |-- 00:1e.0
    |   `-- 04:04.0
    |-- 00:1f.0
    |-- 00:1f.1
    |   |-- ide0
    |   |   |-- 0.0
    |   |   `-- 0.1
    |   `-- ide1
    |       `-- 1.0
    |-- 00:1f.2
    |-- 00:1f.3
    `-- 00:1f.5
    

    此外,會在匯流排的“devices”目錄中建立符號連結,指向裝置在物理層次結構中的目錄

    /sys/bus/pci/devices/
    |-- 00:00.0 -> ../../../devices/pci0/00:00.0
    |-- 00:01.0 -> ../../../devices/pci0/00:01.0
    |-- 00:02.0 -> ../../../devices/pci0/00:02.0
    |-- 00:1e.0 -> ../../../devices/pci0/00:1e.0
    |-- 00:1f.0 -> ../../../devices/pci0/00:1f.0
    |-- 00:1f.1 -> ../../../devices/pci0/00:1f.1
    |-- 00:1f.2 -> ../../../devices/pci0/00:1f.2
    |-- 00:1f.3 -> ../../../devices/pci0/00:1f.3
    |-- 00:1f.5 -> ../../../devices/pci0/00:1f.5
    |-- 01:00.0 -> ../../../devices/pci0/00:01.0/01:00.0
    |-- 02:1f.0 -> ../../../devices/pci0/00:02.0/02:1f.0
    |-- 03:00.0 -> ../../../devices/pci0/00:02.0/02:1f.0/03:00.0
    `-- 04:04.0 -> ../../../devices/pci0/00:1e.0/04:04.0
    

步驟 3:註冊驅動程式。

struct device_driver 是一個簡單的驅動程式結構,包含驅動程式模型核心可以呼叫的一組操作。

  • struct device_driver 嵌入到特定於匯流排的驅動程式中。

    就像處理裝置一樣,執行以下操作

    struct pci_driver {
           ...
           struct device_driver    driver;
    };
    
  • 初始化通用驅動程式結構。

    當驅動程式註冊到匯流排時(例如,執行 pci_register_driver()),初始化驅動程式的必要欄位:name 和 bus 欄位。

  • 註冊驅動程式。

    初始化通用驅動程式後,呼叫

    driver_register(&drv->driver);
    

    以將驅動程式註冊到核心。

    從匯流排登出驅動程式時,透過執行以下操作從核心登出它

    driver_unregister(&drv->driver);
    

    請注意,這將阻塞,直到對驅動程式的所有引用都消失為止。通常情況下,不會有任何引用。

  • Sysfs 表示。

    驅動程式透過 sysfs 在其匯流排的“driver”目錄中匯出。例如

    /sys/bus/pci/drivers/
    |-- 3c59x
    |-- Ensoniq AudioPCI
    |-- agpgart-amdk7
    |-- e100
    `-- serial
    

步驟 4:為驅動程式定義通用方法。

struct device_driver 定義了一組驅動程式模型核心呼叫的操作。這些操作中的大多數可能與匯流排已經為驅動程式定義的操作類似,但採用不同的引數。

強制總線上的每個驅動程式同時將其驅動程式轉換為通用格式將非常困難和繁瑣。相反,匯流排驅動程式應定義通用方法的單個例項,這些例項將呼叫轉發到特定於匯流排的驅動程式。例如

static int pci_device_remove(struct device * dev)
{
        struct pci_dev * pci_dev = to_pci_dev(dev);
        struct pci_driver * drv = pci_dev->driver;

        if (drv) {
                if (drv->remove)
                        drv->remove(pci_dev);
                pci_dev->driver = NULL;
        }
        return 0;
}

通用驅動程式應在註冊之前使用這些方法進行初始化

/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.probe = pci_device_probe;
drv->driver.resume = pci_device_resume;
drv->driver.suspend = pci_device_suspend;
drv->driver.remove = pci_device_remove;

/* register with core */
driver_register(&drv->driver);

理想情況下,匯流排只應初始化尚未設定的欄位。這允許驅動程式實現自己的通用方法。

步驟 5:支援通用驅動程式繫結。

該模型假定裝置或驅動程式可以隨時動態註冊到匯流排。發生註冊時,裝置必須繫結到驅動程式,或者驅動程式必須繫結到它支援的所有裝置。

驅動程式通常包含它支援的裝置 ID 列表。匯流排驅動程式將這些 ID 與已註冊到它的裝置的 ID 進行比較。裝置 ID 的格式以及比較它們的語義是特定於匯流排的,因此通用模型不會嘗試概括它們。

相反,匯流排可以在 struct bus_type 中提供一種方法來進行比較

int (*match)(struct device * dev, struct device_driver * drv);

如果驅動程式支援裝置,則 match 應返回正值,否則返回零。如果無法確定給定驅動程式是否支援該裝置,它也可以返回錯誤程式碼(例如 -EPROBE_DEFER)。

註冊裝置時,會遍歷匯流排的驅動程式列表。bus->match() 會為每個驅動程式呼叫,直到找到匹配項。

註冊驅動程式時,會遍歷匯流排的裝置列表。bus->match() 會為尚未被驅動程式宣告的每個裝置呼叫。

當裝置成功繫結到驅動程式時,會設定 device->driver,裝置會被新增到每個驅動程式的裝置列表中,並在驅動程式的 sysfs 目錄中建立一個指向裝置物理目錄的符號連結

/sys/bus/pci/drivers/
|-- 3c59x
|   `-- 00:0b.0 -> ../../../../devices/pci0/00:0b.0
|-- Ensoniq AudioPCI
|-- agpgart-amdk7
|   `-- 00:00.0 -> ../../../../devices/pci0/00:00.0
|-- e100
|   `-- 00:0c.0 -> ../../../../devices/pci0/00:0c.0
`-- serial

此驅動程式繫結應替換匯流排當前使用的現有驅動程式繫結機制。

步驟 6:提供熱插拔回調。

每當設備註冊到驅動程式模型核心時,都會呼叫使用者空間程式 /sbin/hotplug 來通知使用者空間。使用者可以定義插入或移除裝置時要執行的操作。

驅動程式模型核心透過環境變數將多個引數傳遞給使用者空間,包括

  • ACTION:設定為“add”或“remove”

  • DEVPATH:設定為裝置在 sysfs 中的物理路徑。

匯流排驅動程式還可以提供額外的引數供使用者空間使用。為此,匯流排必須在 struct bus_type 中實現“hotplug”方法

int (*hotplug) (struct device *dev, char **envp,
                int num_envp, char *buffer, int buffer_size);

這會在執行 /sbin/hotplug 之前立即呼叫。

步驟 7:清理匯流排驅動程式。

通用匯流排、裝置和驅動程式結構提供了幾個可以替換匯流排驅動程式私下定義的欄位。

  • 裝置列表。

struct bus_type 包含已註冊到匯流排型別的所有裝置的列表。這包括該匯流排型別的所有例項上的所有裝置。匯流排使用的內部列表可以被移除,以支援使用此列表。

核心提供了一個迭代器來訪問這些裝置

int bus_for_each_dev(struct bus_type * bus, struct device * start,
                     void * data, int (*fn)(struct device *, void *));
  • 驅動程式列表。

struct bus_type 還包含已註冊到它的所有驅動程式的列表。匯流排驅動程式維護的內部驅動程式列表可以被移除,以支援使用通用列表。

可以像裝置一樣迭代驅動程式

int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
                     void * data, int (*fn)(struct device_driver *, void *));

請參閱 drivers/base/bus.c 以獲取更多資訊。

  • rwsem

struct bus_type 包含一個 rwsem,用於保護對裝置和驅動程式列表的所有核心訪問。匯流排驅動程式可以在內部使用它,並且在訪問匯流排維護的裝置或驅動程式列表時應該使用它。

  • 裝置和驅動程式欄位。

struct devicestruct device_driver 中的某些欄位複製了這些物件的特定於匯流排的表示形式中的欄位。您可以隨意移除特定於匯流排的欄位,並支援通用欄位。但請注意,這可能意味著修復所有引用特定於匯流排的欄位的驅動程式(儘管這些都應該是單行更改)。