一、總線
Linux 總線驅動模型主要可以分為三個部分:總線、設備、驅動。Linux 中的總線(bus)、驅動(driver)和設備(device)模型,也就是常說的驅動分離。Linux內核在啟動時會向系統注冊總線,比如 IIC總線、SPI總線、SDIO總線、Platform總線等。總線是與硬件平臺無關的,由Linux系統提供并進行注冊,無需用戶關心。總線的職責就是將驅動與設備進行匹配,Linux系統內核使用 bus_type 結構體表示總線,此結構體定義在文件include/linux/device.h,bus_type 結構體內容如下:
struct bus_type {
const char *name; /* 總線名字 */
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups; /* 總線屬性 */
const struct attribute_group **dev_groups; /* 設備屬性 */
const struct attribute_group **drv_groups; /* 驅動屬性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
第 10 行:match 函數,此函數很重要,此函數就是完成設備和驅動之間匹配的,總線就是使用 match 函數來根據注冊的設備來查找對應的驅動,或者根據注冊的驅動來查找相應的設備,因此每一條總線都必須實現此函數。match 函數有兩個參數:dev 和 drv,這兩個參數分別為 device 和 device_driver 類型,也就是設備和驅動。
Linux 還提供了兩個 API 用于總線的注冊與注銷(由Linux完成,用戶無需關心),定義在 Drive/base/bus.c中,如下:
bus_register(struct bus_type *bus);
bus_unregister(struct bus_type *bus);
參數:對應總線結構體變量
各種總線如IIC、SPI、SDIO總線等都是 bus_type 的一個實例。
內核初始化會先調用 platform_bus_init() 初始化 platform_bus 總線,之后由 _initcall 的優先級順序可知先初始化各種實際總線例如 spi、i2c。之后注冊 platform_device 到 platform_bus 上,然后注冊 platform_driver 到platform_bus。
在 linux 引進設備樹之前設備信息都存放在內核 arch 目錄下,引進設備樹以后設備信息由設備樹描述(此舉為了刪除linux內核大量和平臺設備相關的代碼)內核初始化會掃描設備樹文件將設備信息保存到設備鏈表,提供鏈表的頭指針,在注冊設備時調用of_xxx(操作設備樹鏈表的api)來獲得設備信息。
注冊 platform_device 會將設備綁定到 platform_bus,之后會遍歷 platform_bus 上是否有與之匹配的驅動程序(調用 platform_bus_type 的 match 函數進行匹配),如果存在,匹配成功調用驅動的probe對設備進行初始化的配置。
注冊 platform_driver 到 platform_bus 會遍歷 platform_bus 上是否有與之匹配的設備(調用 platform_bus_type 的 match 函數進行匹配),如果存在,匹配成功調用驅動的 probe 對設備進行初始化的配置。如果平臺設備本身是實際的總線例如spi、i2c等,此時在驅動程序中會注冊總線設備到到相應的總線上,之后會遍歷xxx_bus上是否與之匹配的驅動程序,如果有會執行驅動的 probe 進行初始化相關配置。
下面以 SPI 總線驅動為例子做一個詳細講解:
①、SPI 驅動分為 SPI 控制器驅動、SPI 設備驅動。SPI 控制器掛在 Linux 的虛擬總線 Platform Bus 上,包括 SPI 控制器設備和 SPI 控制器設備的驅動,在內核代碼里分別由結構體 platform_device 和 platform_driver 表示,內核初始化時先注冊 platform_device(SPI 控制器設備,定義在設備樹 DTS 中的 SPI 控制器設備節點)到 platform bus,此時會遍歷總線上是否存在匹配的 SPI 控制器驅動程序(根據兼容性、設備id、設備名),匹配成功會調用控制器驅動的 probe 對控制器進行相關的配置。
②、之后會注冊 SPI 控制器的驅動程序(定義在內核源碼中,由 SOC 產商編寫)到 platform bus 上,此時會遍歷總線上是否存在匹配的 SPI 控制器設備(根據兼容性、設備id、設備名),匹配成功會調用控制器驅動的 probe 對控制器進行相關的配置。在 SPI 控制器驅動的 probe 函數中會注冊 spi device(設備樹 DTS 中定義在 SPI 控制器設備節點下的 SPI 設備)到 spi bus(spi bus此時已經初始化),此時會遍歷 spi 總線上是否有與之匹配的設備驅動程序,如果有,調用 spi 設備驅動的 probe 函數對 spi device 進行配置。spi 設備驅動(由用戶編寫,如IIC OLED、Sensor IIC等驅動)注冊到 spi bus 時也會同樣去匹配是否有匹配的設備。
擴展:
Linux 內核的 sysfs 子系統用來管理 Linux 設備驅動模型,參考下圖:
1、I2C 總線
I2C 總線結構體變量為 i2c_bus_type,該結構體變量是 bus_type 結構體的一個實例,IIC 設備和驅動的匹配以及注冊都是由 I2C 總線完成的,IIC總線結構體和IIC驅動設備注冊 API 都定義在 drivers/i2c/i2c-core.c 文件中,i2c_bus_type 內容如下:
struct bus_type i2c_bus_type = {
.name = 'i2c',
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
.match 就是 I2C 總線的設備和驅動匹配函數,也就是 i2c_device_match 這個函數,此函數內容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
第 11 行:of_driver_match_device 函數用于完成設備樹設備和驅動匹配。比較 I2C 設備節點的 compatible 屬性和 of_device_id 中的 compatible 屬性是否相等,如果相當的話就表示 I2C 設備和驅動匹配。
第 15 行:acpi_driver_match_device 函數用于 ACPI 形式的匹配。
第 21 行:i2c_match_id 函數用于傳統的、無設備樹的 I2C 設備和驅動匹配過程。比較 I2C 設備名字和 i2c_device_id 的 name 字段是否相等,相等的話就說明 I2C 設備和驅動匹配。
IIC設備驅動的注冊與注銷 API 也定義在 drivers/i2c/i2c-core.c 文件中,API 函數如下:
1、i2c_adapter 注冊/注銷函數(主機驅動API)
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver 注冊/注銷函數(設備驅動API)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
結論:
i2c-core.c 定義了 IIC 總線結構體變量,并調用 bus.c 提供的總線注冊與注銷API,將 IIC 總線注冊到系統中。同時,向下提供了 IIC 主機驅動和設備驅動的注冊、注銷相關 API。
2、SPI 總線
再如 SPI 設備和驅動的匹配過程是由 SPI 總線來完成的,這點和 I2C 等驅動一樣,SPI 總線為 spi_bus_type,定義在 drivers/spi/spi.c 中,內容如下:
struct bus_type spi_bus_type = {
.name = 'spi',
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
可以看出,SPI 設備和驅動的匹配函數為 spi_match_device,函數內容如下:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
spi_match_device 函數和 i2c_match_device 函數的對于設備和驅動的匹配過程基本一樣。
第 7 行:of_driver_match_device 函數用于完成設備樹設備和驅動匹配。比較 SPI 設備節點的 compatible 屬性和 of_device_id 中的 compatible 屬性是否相等,如果相當的話就表示 SPI 設備和驅動匹配。
第 11 行:acpi_driver_match_device 函數用于 ACPI 形式的匹配。
第 15 行:spi_match_id 函數用于傳統的、無設備樹的 SPI 設備和驅動匹配過程。比較 SPI 設備名字和 spi_device_id 的 name 字段是否相等,相等的話就說明 SPI 設備和驅動匹配。
第 17 行:比較 spi_device 中 modalias 成員變量和 device_driver 中的 name 成員變量是否相等。
SPI總線提供SPI驅動的注冊與注銷相關API,也定義在 drivers/spi/spi.c 中。
1、主機驅動API
SPI 主機驅動的核心是申請 spi_master,然后初始化 spi_master,最后向 Linux 內核注冊 spi_master,主機驅動注冊與注銷相關 API 如下:
①、spi_master 申請與釋放
spi_alloc_master 函數用于申請 spi_master,函數原型如下:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
函數參數和返回值含義如下:
dev:設備,一般是 platform_device 中的 dev 成員變量。
size:私有數據大小,可通過 spi_master_get_devdata 函數獲取這些私有數據。
返回值:申請到的 spi_master。
spi_master 的釋放通過 spi_master_put 函數來完成,當刪除一個 SPI 主機驅動的時候就需要釋放掉前面申請的 spi_master,spi_master_put 函數如下:
void spi_master_put(struct spi_master *master)
函數參數和返回值含義如下:
master:要釋放的 spi_master。
返回值:無。
②、spi_master 的注冊與注銷
當 spi_master 初始化完成以后就需要將其注冊到 Linux 內核,spi_master 注冊函數為 spi_register_master,函數原型如下:
int spi_register_master(struct spi_master *master)
函數參數和返回值含義如下:
master:要注冊的 spi_master。
返回值:0,成功;負值,失敗。
I.MX6U 的 SPI 主機驅動會采用 spi_bitbang_start 這個 API 函數來完成 spi_master 的注冊,spi_bitbang_start 函數內部其實也是通過調用 spi_register_master 函數來完成 spi_master 的注冊。
如果要注銷 spi_master 的話可以使用 spi_unregister_master 函數,此函數原型為:
void spi_unregister_master(struct spi_master *master)
函數參數和返回值含義如下:
master:要注銷的 spi_master。
返回值:無。
如果使用 spi_bitbang_start 注冊 spi_master 的話就要使用 spi_bitbang_stop 來注銷掉 spi_master。
2、設備驅動API
spi_driver 初始化完成以后需要向 Linux 內核注冊,spi_driver 注冊函數為 spi_register_driver,函數原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函數參數和返回值含義如下:
sdrv:要注冊的 spi_driver。
返回值:0,注冊成功;賦值,注冊失敗。
注銷掉前面注冊的 spi_driver,使用 spi_unregister_driver 函
數完成 spi_driver 的注銷,函數原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函數參數和返回值含義如下:
sdrv:要注銷的 spi_driver。
返回值:無。
結論:
spi.c 定義了 spi 總線結構體變量,并調用 bus.c 提供的總線注冊與注銷API,將 spi 總線注冊到系統中。同時,向下提供了 spi 主機驅動和設備驅動的注冊、注銷相關 API。
3、platform 總線
在 SOC 中有些外設是沒有總線這個概念的,但是又要使用總線、驅動和設備模型,因此 Linux 提出了 platform 這個虛擬總線。platform 總線也是 bus_type 的一個具體實例,定義在文件 drivers/base/platform.c 文件中,如下:
struct bus_type platform_bus_type = {
.name = 'platform',
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_bus_type 就是 platform 平臺總線,其中 platform_match 就是匹配函數。=platform_match 函數定義在文件 drivers/base/platform.c 中,函
數內容如下所示:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/*When driver_override is set,only bind to the matching driver*/
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
驅動和設備的匹配有四種方法,我們依次來看一下:
第 11~12 行:第一種匹配方式,OF 類型的匹配,也就是設備樹采用的匹配方式,
of_driver_match_device 函數定義在文件 include/linux/of_device.h 中。device_driver 結構體(表示設備驅動)中有個名為of_match_table的成員變量,此成員變量保存著驅動的compatible匹配表,設備樹中的每個設備節點的 compatible 屬性會和 of_match_table 表中的所有成員比較,查看是否有相同的條目,如果有的話就表示設備和此驅動匹配,設備和驅動匹配成功以后 probe 函數
就會執行。
第 15~16 行:第二種匹配方式,ACPI 匹配方式。
第 19~20 行:第三種匹配方式,id_table 匹配,每個 platform_driver 結構體有一個 id_table 成員變量,顧名思義,保存了很多 id 信息。這些 id 信息存放著這個 platformd 驅動所支持的驅動類型。
上一篇:【IMX6ULL學習筆記】十八、Platform 驅動框架
下一篇:【IMX6ULL學習筆記】十六、設備樹下LED驅動
推薦閱讀最新更新時間:2025-06-10 17:54



設計資源 培訓 開發板 精華推薦
- LTC3624HMSE-2 可調輸出電壓、2A 同步降壓型穩壓器的典型應用,具有 1MHz、突發模式操作
- LTC3630AHMSE 5V 至 76V 輸入至 5V 輸出、150mA 穩壓器和 20kHz 最小突發頻率的典型應用電路
- Si2434-EVB,帶有 UART 接口的 SI2434 ISOmodem 芯片的評估板
- LF33CV 3.3V 極低壓降穩壓器的典型應用
- thumb-arduino
- 使用 Microchip Technology 的 MIC5310-1.8/1.5YML 的參考設計
- CH9102F 串口
- USB-HUB-V8
- LTC3614 的典型應用 - 4A、4MHz 單片式同步降壓型 DC/DC 轉換器
- HDMI_connector_public