一、Linux驅動的分離
1. 為什麼需要驅動分離?
在嵌入式開發中,無論處理器如何更換,外設子產品的操作都是一緻的,比如有三個不同的平台都要驅動MPU6050傳感器,最簡單的方法是針對每個平台都寫一份驅動:
顯然這種處理方式太low了,MPU6050都是使用I2C接口操作的,對于不同的平台,隻是I2C操作方式不一樣,是以這裡可以将I2C接口抽象出來,給不同的平台用自己的庫函數适配:
這樣多種平台就可以共用同一份MPU6050驅動:
2. Linux核心中的驅動分離
在Linux核心中,一般SOC的主機控制器驅動已經由半導體廠家寫好了,比如這裡imx6ull的i2c控制器驅動已經由NXP寫好了。
而對于具體的裝置,比如MPU6050等裝置,其驅動程式也由裝置廠商寫好了。
我們要做的是提供裝置資訊即可,比如:裝置連接配接到了哪個I2C接口上?支援的速率是多少?裝置在總線上的從機位址是多少?等等。
這樣設計之下,SOC的外設驅動隻負責外設驅動,某個裝置的驅動隻負責裝置驅動,使用時隻需要讓核心将二者聯系起來即可。
當我們向系統注冊一個驅動的時候,核心就會在右側的裝置中查找有沒有比對的裝置,同樣,當我們向系統中注冊一個裝置的時候,核心就會在左側的驅動中查找有沒有比對的驅動。
這個就是Linux核心中的總線(bus)、驅動(driver)、裝置(device)模型,也稱之為Linux核心中的驅動分離。
3. Linux核心中的驅動分層
分層的目的是為了在不同的層處理不同的内容。
以input子系統為例,input子系統負責管理所有跟輸入有關的驅動,包括鍵盤、滑鼠、觸摸闆等。
- 裝置原始驅動層:負責擷取輸入裝置的原始值。
- input核心層:處理各種IO模型,并且提供file_operations操作集合。
這樣分層設計之後,在編寫輸入設别的驅動時,隻需要考慮到如何上報輸入事件即可,至于如何處理這些上報的輸入事件,是上層需要做的事情,無需關注。
二、platform平台驅動模型
1. 為什麼需要platform?
Linux核心中的驅動程式分離為:總線(bus)、驅動(driver)、裝置(device)模型,但是有些SOC中有些外設是沒有總線這個概念的。
為了解決此問題,Linux核心中提出了platform平台模型作為虛拟總線,相應的有platform_driver和platform_device。
2. platform總線
2.1. platform總線的類型——bus_type結構體
Linux核心中使用bus_type結構體表示總線,定義在檔案
include/linux/device.h
,如下:
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
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;
};
總線最重要的工作就是根據注冊的裝置來查找對應的驅動,或者根據注冊的驅動來查找相應的裝置,該任務主要依賴match函數指針,是以每一條總線都必須實作此函數。
int (*match)(struct device *dev, struct device_driver *drv);
可以看到,match函數有兩個參數:dev和drv。
- dev:device類型,表示裝置
- drv:device_driver類型,表示驅動
2.2. 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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
platform_bus_type就是platform平台總線,其中 platform_match 就是比對函數。
2.3. 驅動和裝置如何比對
platform_match 函數定義在
drivers/base/platform.c
中,
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
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);
}
從代碼中可以看到,裝置和驅動的比對有四種方法。
(1)OF類型的比對(裝置樹采用的比對方式):根據裝置節點的 compatible 屬性和驅動的of_match_table表進行比對;
(2)ACPI比對方式
(3)id_table比對
(4)直接比較驅動和裝置的name字段是否相等
3. platform驅動(重點)
3.1. platform_driver結構體
platform_driver結構體表示platform驅動,定義在檔案
include/linux/platform_device.h
中,如下:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
(1)probe函數
當驅動與裝置比對成功以後probe函數就會執行,由驅動的提供者編寫。
(2)remove函數
驅動解除安裝的時候會執行
(3)driver成員
device_driver相當于基類,提供了最基礎的驅動架構,platform_driver繼承了這個基類。
(4)id_table表
(5)of_match_table表:裝置樹比對表
3.2. platform驅動可用API
(1)向核心注冊一個platform驅動
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner);
/*
* use a macro to avoid include chaining to get THIS_MODULE
*/
#define platform_driver_register(drv)\
__platform_driver_register(drv, THIS_MODULE)
(2)從核心解除安裝一個platform驅動
extern void platform_driver_unregister(struct platform_driver *);