返回文章列表

Linux 裝置與驅動程式模型

本文探討 Linux 裝置模型的底層機制,包含裝置驅動程式結構、sysfs 虛擬檔案系統、kobject 基礎架構、Device Tree 以及核心啟動流程。文章解析了核心程式碼中關鍵結構體,例如 device_driver、device 以及 kobject,並闡述了它們之間的關聯。此外,文章也說明瞭 sysfs

作業系統

Linux 核心採用裝置模型來管理系統中的各種硬體,提供統一的表示和管理方式,簡化了驅動程式的開發。每個驅動程式都需例項化 device_driver 結構,其中包含匯流排型別、探測和移除函式等資訊。與之對應,每個裝置由 device 結構表示,包含父裝置、裝置型別、驅動程式等資訊。sysfs 虛擬檔案系統則將這些資訊暴露給使用者空間,方便管理和除錯。kobject 基礎架構是 sysfs 的核心,每個裝置和驅動程式都由一個 kobject 表示,並透過 sysfs 呈現其屬性和資訊。Device Tree 則是一種描述硬體資訊的資料結構,核心利用它來識別平台、描述硬體和支援驅動程式。

Linux 裝置與驅動程式模型

Linux 作業系統的核心元件之一是裝置模型(Device Model),它負責管理系統中的各種硬體裝置。裝置模型提供了一種統一的方式來表示和管理系統中的裝置,使得裝置驅動程式的開發變得更加容易和規範。

裝置驅動程式結構

每個裝置驅動程式都需要例項化和註冊一個 device_driver 結構體的例項。device_driver 結構體的定義如下:

struct device_driver {
    const char *name;
    struct bus_type *bus;
    struct module *owner;
    const char *mod_name;
    bool suppress_bind_attrs;
    const struct of_device_id *of_match_table;
    const struct acpi_device_id *acpi_match_table;
    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;
    const struct dev_pm_ops *pm;
    struct driver_private *p;
};

內容解密:

  • bus 成員指向驅動程式註冊的 bus_type 結構體。
  • probe 成員是一個回呼函式,當檢測到支援的裝置時會被呼叫,驅動程式應在這個函式中初始化裝置。
  • remove 成員是另一個回呼函式,用於解除驅動程式與裝置的繫結。

裝置結構

在 Linux 系統中,每個裝置都由一個 struct device 例項來表示。struct device 結構體包含了裝置模型的核心需要用來建模系統的資訊。

struct device {
    struct kobject kobj;
    struct device *parent;
    struct device_private *p;
    const char *init_name; /* initial name of the device */
    const struct device_type *type;
    struct bus_type *bus; /* type of bus device is on */
    struct device_driver *driver; /* which driver has allocated this device */
    void *platform_data; /* Platform specific data, device core doesn't touch it */
    void *driver_data; /* Driver data, set and get with dev_set_drvdata/dev_get_drvdata */
    [...]
    struct device_node *of_node; /* associated device tree node */
    struct fwnode_handle *fwnode; /* firmware device node */
    [...]
    struct class *class;
    const struct attribute_group **groups; /* optional groups */
    void (*release)(struct device *dev);
    [...]
};

內容解密:

  • kobj 是代表裝置的 kobject,並將其連結到層次結構中。
  • parent 是裝置的父裝置,通常是一個匯流排或主機控制器。
  • type 識別了裝置的型別,並攜帶了型別特定的資訊。
  • bus 表示裝置所連線的匯流排型別。
  • driver 指向分配該裝置的驅動程式。
  • driver_data 是驅動程式用於儲存私有資料的指標。

sysfs 虛擬檔案系統

sysfs 是一個虛擬檔案系統,它將裝置和驅動程式的資訊從核心裝置模型匯出到使用者空間。它提供了一種將核心資料結構、屬性和它們之間的連結匯出到使用者空間的方法。

sysfs 目錄結構

  • /sys/bus/:包含了每個匯流排型別的子目錄。
  • /sys/devices/:包含了裝置的列表。
  • /sys/class/:包含了每個已註冊的裝置類別的子目錄。

Linux 裝置與驅動程式模型

sysfs 與 kobject 基礎架構

sysfs 是 Linux 核心用於將系統硬體裝置和驅動程式資訊暴露給使用者空間的重要機制。它與 kobject 基礎架構緊密相關。kobject 是 Linux 裝置模型的基本結構,負責在 sysfs 中呈現裝置和驅動程式的資訊。

kobject 結構解析

kobject 結構是裝置模型的基礎,其定義如下:

struct kobject {
    const char *name;
    struct list_head entry;
    struct kobject *parent;
    struct kset *kset;
    struct kobj_type *ktype;
    struct sysfs_dirent *sd;
    struct kref kref;
    [...]
};

各欄位說明:

  • name:kobject 的名稱,用於在 sysfs 中建立對應的目錄。
  • parent:指向父 kobject,決定了目錄結構的層級關係。
  • ktype:與 kobject 相關聯的型別,控制 kobject 的建立和刪除行為。
  • kset:一組相同型別的 kobject 集合,用於管理相關的物件。
  • sd:指向代表此 kobject 在 sysfs 中的 sysfs_dirent 結構。
  • kref:提供參考計數功能,用於管理 kobject 的生命週期。

kobject 與裝置模型的關係

kobject 通常被嵌入到更大的結構中,例如 struct cdev

struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

在註冊 kobject 時,核心會在 sysfs 中為其建立對應的目錄,並根據其 ktype 和屬性設定相關檔案。

sysfs 操作與屬性管理

屬性結構與操作

屬性是用於在 sysfs 中呈現裝置或驅動程式資訊的重要機制。struct attribute 定義了基本的屬性結構:

struct attribute {
    char *name;
    struct module *owner;
    umode_t mode;
};

struct sysfs_ops 定義了屬性的讀寫操作:

struct sysfs_ops {
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

自定義屬性

子系統可以定義自己的屬性結構和操作函式。例如,裝置子系統定義了 struct device_attribute

struct device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};

並提供了 DEVICE_ATTR 巨集來簡化屬性定義:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
    struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

新增和移除屬性檔案

可以使用以下函式來新增或移除裝置的 sysfs 屬性檔案:

int device_create_file(struct device *dev, const struct device_attribute *attr);
void device_remove_file(struct device *dev, const struct device_attribute *attr);

內容解密:

  1. device_create_file 函式:此函式用於在 sysfs 中為指定的裝置建立屬性檔案。它呼叫 sysfs_create_file 來新增屬性。
  2. device_remove_file 函式:此函式用於移除先前建立的屬性檔案,確保資源的正確清理。
  3. DEVICE_ATTR 巨集:簡化了裝置屬性的定義過程,讓開發者可以更方便地建立和管理屬性。

Linux 裝置與驅動程式模型:深入理解 sysfs 與 Device Tree

sysfs 屬性檔案的管理

在 Linux 核心中,sysfs 是一種虛擬檔案系統,用於向使用者空間暴露核心物件的資訊。device_create_file 函式用於在 sysfs 中為裝置建立屬性檔案。

程式碼解析

int device_create_file(struct device *dev, const struct device_attribute *attr)
{
    int error = 0;
    if (dev) {
        WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
             "Attribute %s: write permission without 'store'\n",
             attr->attr.name);
        WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
             "Attribute %s: read permission without 'show'\n",
             attr->attr.name);
        error = sysfs_create_file(&dev->kobj, &attr->attr);
    }
    return error;
}

內容解密:

  1. 函式目的:該函式的主要目的是為指定的裝置在 sysfs 中建立屬性檔案。
  2. 引數檢查:首先檢查裝置指標 dev 是否有效。
  3. 警告檢查
    • 使用 WARN 巨集檢查屬性是否具有寫入許可權但沒有對應的 store 方法。
    • 使用 WARN 巨集檢查屬性是否具有讀取許可權但沒有對應的 show 方法。
  4. 建立屬性檔案:呼叫 sysfs_create_file 在 sysfs 中建立屬性檔案。

使用 DEVICE_ATTR 建立屬性

可以使用 DEVICE_ATTR 巨集來定義裝置屬性,例如:

static DEVICE_ATTR(foo1, S_IWUSR | S_IRUGO, show_foo1, store_foo1);
static DEVICE_ATTR(foo2, S_IWUSR | S_IRUGO, show_foo2, store_foo2);

內容解密:

  1. DEVICE_ATTR 巨集:用於定義 struct device_attribute 型別的變數。
  2. 引數說明
    • 第一個引數是屬性的名稱。
    • 第二個引數是屬性的許可權。
    • 第三個引數是 show 方法,用於讀取屬性值。
    • 第四個引數是 store 方法,用於寫入屬性值。

將屬性組織成群組

可以將多個屬性組織成一個群組,以便統一管理。

static struct attribute *dev_attrs[] = {
    &dev_attr_foo1.attr,
    &dev_attr_foo2.attr,
    NULL,
};

static struct attribute_group dev_attr_group = {
    .attrs = dev_attrs,
};

static const struct attribute_group *dev_attr_groups[] = {
    &dev_attr_group,
    NULL,
};

內容解密:

  1. attribute_group 結構:用於表示一個屬性群組。
  2. attrs 成員:指向屬性陣列的指標。
  3. dev_attr_groups:指向屬性群組陣列的指標,用於批次管理多個屬性群組。

新增和移除 sysfs 屬性群組

可以使用以下函式來新增和移除 sysfs 屬性群組:

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);
void sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp);

內容解密:

  1. sysfs_create_group:在指定的 kobject 上建立屬性群組。
  2. sysfs_remove_group:從指定的 kobject 上移除屬性群組。

Device Tree 簡介

Device Tree(DT)是一種資料結構,用於描述系統硬體資訊。它起源於 IEEE 1275 規範,旨在解決嵌入式系統中硬體描述的問題。

Device Tree 的結構與用途

  • Device Tree 由節點組成,每個節點代表一個裝置或硬體元件。
  • 每個節點具有屬性/值對,用於描述裝置的特性。

相容性屬性(compatible property)

相容性屬性是 Device Tree 中非常重要的屬性,用於描述裝置的程式設計模型。它由一個或多個字串組成,從最具體到最一般。

Device Tree 在 Linux 中的應用

Linux 使用 Device Tree 資料進行三個主要目的:

  1. 平台識別:核心使用 Device Tree 資料來識別特定的機器。
  2. 硬體描述:Device Tree 描述了系統的硬體組成,包括處理器、外設等。
  3. 驅動程式支援:Device Tree 為驅動程式提供了必要的硬體資訊。

Linux 裝置和驅動程式模型

裝置樹的匹配與系統設定

在 Linux 核心啟動過程中,裝置樹(Device Tree, DT)扮演著重要的角色。它不僅用於描述硬體裝置,還用於在 u-boot 與核心之間傳遞資料。在 ARM 架構中,裝置樹的匹配機制是透過 setup_machine_fdt() 函式實作的。

setup_machine_fdt() 函式分析

const struct machine_desc * __init setup_machine_fdt(void *dt)
{
    const struct machine_desc *mdesc;
    unsigned long dt_root;

    if (!early_init_dt_scan(dt))
        return NULL;

    mdesc = of_flat_dt_match_machine(NULL, arch_get_next_mach);
    if (!mdesc)
        machine_halt();

    dt_root = of_get_flat_dt_root();
    arc_set_early_base_baud(dt_root);

    return mdesc;
}

內容解密:

  1. early_init_dt_scan(dt):此函式驗證並掃描裝置樹的基本資訊。如果驗證失敗,則傳回 NULL

    • 這個步驟確保裝置樹的資料是有效的,並且可以被核心正確解析。
  2. of_flat_dt_match_machine(NULL, arch_get_next_mach):此函式遍歷 machine_desc 表格,以找到與裝置樹中 compatible 屬性最匹配的 machine_desc

    • machine_desc 結構描述了特定機器的硬體特性,而 compatible 屬性則定義了裝置樹與哪些機器相容。
    • 如果找不到匹配的 machine_desc,則呼叫 machine_halt(),使系統停止運作。
  3. of_get_flat_dt_root():取得裝置樹的根節點。

    • 根節點包含了裝置樹的全域性資訊,例如地址單元和大小單元的組態。
  4. arc_set_early_base_baud(dt_root):根據裝置樹的根節點設定早期基礎波特率。

    • 這通常與串列埠的組態有關,用於初始化系統的輸出裝置。

執行階段組態

在大多數情況下,裝置樹是 u-boot 向核心傳遞資料的唯一方法。因此,它也用於傳遞執行階段的組態資料,例如核心引數和 initrd 映像的位置。這些資料大多包含在 chosen 節點中。

chosen 節點範例

chosen {
    stdout-path = "serial0:115200n8";
};

內容解密:

  1. stdout-path 屬性:指定用於啟動控制檯輸出的裝置。
    • 在這個例子中,輸出將被導向到 serial0,並以 115200 波特率、8 位元資料,無奇偶校驗進行組態。

早期掃描與初始化

在早期啟動過程中,setup_machine_fdt() 呼叫 of_scan_flat_dt() 多次,使用不同的輔助回呼函式來解析裝置樹資料。這發生在分頁設定之前。

early_init_dt_scan_nodes() 函式分析

void __init early_init_dt_scan_nodes(void)
{
    int rc = 0;

    /* 從 /chosen 節點檢索各種資訊 */
    rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    if (!rc)
        pr_warn("No chosen node found, continuing without\n");

    /* 初始化 {size,address}-cells 資訊 */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    /* ... 其他初始化步驟 ... */
}

內容解密:

  1. of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line):掃描 chosen 節點,並解析其中的資訊,如核心引數。

    • 如果找不到 chosen 節點,則發出警告,但系統會繼續執行。
  2. of_scan_flat_dt(early_init_dt_scan_root, NULL):初始化 DT 地址空間模型,包括大小單元和地址單元的組態。

    • 這一步驟對於理解系統的記憶體佈局至關重要。

透過這些步驟,Linux 核心能夠在早期啟動階段有效地利用裝置樹進行硬體初始化和系統組態。這種機制使得核心能夠支援多種不同的硬體平台,而無需為每個平台進行大量的程式碼修改。