【linux】驅動-7-平臺裝置驅動
阿新 • • 發佈:2021-04-01
[toc]
---
## 前言
區分**裝置驅動模型**和**平臺裝置驅動模型**。
**裝置驅動模型** 可以理解為 **匯流排、裝置、驅動**。
**平臺裝置驅動模型** 就是那些 Linux 核心管理沒有物理匯流排(*即是不需要特殊時序控制的裝置*)(*也是Linux核心沒有自動建立相應驅動匯流排的裝置型別*)的裝置的一套 Linux **平臺匯流排、平臺模型、平臺驅動**的模型。
## 7. 平臺裝置驅動
為解決驅動程式碼和裝置資訊耦合問題,linux提出了裝置驅動模型。
注意,前面的匯流排、裝置、驅動是一個軟體層面的抽象,與 **SOC** 中物理匯流排概念不一樣。
**物理匯流排**:晶片與各個功能外設之間傳送資訊的公共通訊幹線,包括資料匯流排、地址匯流排和控制匯流排,以此來傳輸各種**通訊時序**。
**驅動匯流排**:負責管理驅動和裝置。制定裝置和驅動的匹配規則。一旦總線上註冊了新裝置/驅動,匯流排便執行匹配程式。
對於常見的 **I2C、SPI、USB**等 **物理匯流排**,**Linux** 核心都會自動建立與之對應的 **驅動匯流排**。所以 I2C裝置、SPI裝置、USB裝置都會掛在在相應的總線上。
相對的,實際專案開發中還有很多結構簡單的裝置是不需要特殊的**時序控制**的。也就沒有相應的物理匯流排,Linux 也不會為它們建立相應的驅動匯流排。如 LED、RTC時鐘、按鍵等等。
但是為了這些簡單的裝置也能遵循裝置驅動模型,Linux 核心引入了一種虛擬匯流排--**平臺匯流排**。
**平臺匯流排** 用於管理、掛在那些沒有物理匯流排的裝置,且,這些裝置被稱為**平臺裝置**,對應的裝置驅動被稱為**平臺驅動**。
平臺裝置使用 **platform_device** 結構體進行表示,繼承了裝置驅動模型中的 **device** 結構體。
平臺驅動使用 **platform_driver** 結構體進行表示,繼承了裝置驅動模型中的 **device_driver** 結構體。
### 7.1 平臺匯流排
Linux核心只有一條**平臺匯流排**,用於圖一管理簡單裝置--**platform_bus_type**。
#### 7.1.1 平臺匯流排註冊和匹配方式
**最先比較**:
* 最先比較 **platform_device.driver_override** 和 **platform_driver.driver.name**。
* 可以設定 **platform_device** 的 **driver_override**,強制選擇某個 **platform_driver**。
**其次比較**:
* 其次比較 **platform_device.name** 和 **platform_driver.id_table[i].name**。
* **platform_driver.id_table** 是 **platform_device_id** 指標,表示該 **drv** 支援若干個 **device**,它裡面列出了各個 **device** 的 **{.name, .driver_data}**,其中的 **name** 表示該 **drv** 支援的裝置的名字,**driver_data**是些提供給該 **device** 的私有資料。
**最後比較**:
* 最後比較 **platform_device.name** 和 **platform_driver.driver.name**。
* 由於 **platform_driver.id_table** 可能為空,所以,接下來就可以使用 **platform_driver.driver.name** 來匹配。
#### 7.1.2 原始碼分析
**平臺匯流排**:
* 核心使用 **bus_type** 來抽象系統中的匯流排。相應地,核心使用 **platform_bus_type** 來描述平臺匯流排。
* 原始碼:
```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);
```
* 位於 **核心原始碼/driver/base/platform.c**。
* **該匯流排在Linux核心啟動的時候自動進行註冊**。
* **平臺匯流排**初始化函式原始碼:
```c
int __init platform_bus_init(void){
int error;
...
error = bus_register(&platform_bus_type);
...
return error;}
```
* 位於 **核心原始碼/driver/base/platform.c**。
* **`error = bus_register(&platform_bus_type);`** 就是向Linux核心註冊 **plateform_bus_type** 平臺匯流排。
**建議**:以上匹配規則及原始碼,可以追蹤函式 **platform_match** 原始碼來分析。
### 7.2 平臺裝置
#### 7.2.1 platform_device
**平臺裝置**:
* 使用 **platform_device** 描述平臺裝置。
* 原始碼:
```c
struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/* 省略部分成員 */
};
```
* 位於 **核心原始碼/include/linux/platform_device.h**
* **name**:裝置名稱,匯流排進行匹配時,會比較裝置和驅動的名稱是否一致;
* **id**:指定裝置的編號,Linux支援同名的裝置,而同名裝置之間則是通過該編號進行區分;
* **dev**:Linux裝置模型中的device結構體,platform_device 通過繼承該結構體可複用它的相關程式碼,方便核心管理平臺裝置;
* **num_resources**:記錄資源的個數,當結構體成員 resource 存放的是陣列時,需要記錄 resource 陣列的個數,核心提供了巨集定義 ARRAY_SIZE 用於計算陣列的個數;
* **resource**:平臺裝置提供給驅動的資源,如irq,dma,記憶體等等;
* **id_entry**:平臺匯流排提供的另一種匹配方式,原理依然是通過比較字串,這裡的 id_entry 用於儲存匹配的結果;
#### 7.2.2 裝置資訊
平臺裝置的工作是為 驅動程式 提供 裝置資訊。包括 硬體資訊 和 軟體資訊。
* **硬體資訊**:驅動程式需要使用到的暫存器、中斷號、記憶體資源、IO口等等;
* **軟體資訊**:乙太網裝置中的 MAC 地址、I2C 裝置中的裝置地址等等。
**硬體資訊**:
* 硬體資訊使用 **struct resource** 來儲存裝置所提供的資源。
* 原始碼:
```c
/**
* Resources are tree-like, allowing nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
/* 省略部分成員 */
};
```
* 位於 **核心原始碼/include/linux/ioport.h**;
* **name**:資源名字,可為 NULL;
* **start、end**:指定資源的起始地址及結束地址;
* 對於 IORESOURCE_IO 和 IORESOURCE_MEM 是有起始和結束地址的,若只有一個引腳或者一個通道,那麼 **start == end**。
* **flags**:用於指定資源的型別,在 Linux 中,資源包括**I/O、Memory、Register、IRQ、DMA、Bus等多種型別**,如:
* **IORESOURCE_IO**:IO 地址空間,對應於 IO 埠對映方式;
* **IORESOURCE_MEM**:外設可直接定址的地址空間;
* **IORESOURCE_IRQ**:指定該裝置使用哪個中斷;
* **IORESOURCE_DMA**:指定 DMA 通道。
裝置驅動程式主要目的還是操作裝置的暫存器。
不同架構的計算機提供不同的操作介面,主要有**IO埠對映**和**IO記憶體對映**兩種方式。
**IO埠對映**方式:
* 只能通過專門的介面函式才能訪問。
**IO記憶體對映**方式:
* 可以像記憶體一樣訪問,去讀寫暫存器。
**軟體資訊**:
* 軟體資訊需要以私有資料儲存;
* **platform_device** 結構體中繼承有 **device** 結構體,成員為 **dev**,該結構體裡面的 **platform_data** 可以用於儲存裝置的私有資料。
* **platform__data** 是 **void \*** 型別的萬能指標,所以,只需要把私有資料的地址付給 **platform_data** 即可。
#### 7.2.3 註冊/登出平臺裝置
**註冊:platform_device_register**:
* 註冊平臺裝置,掛在到平臺總線上。
* 函式原型:**`int platform_device_register(struct platform_device *pdev)`**;位於 **核心原始碼/drivers/base/platform.c**。
* **pded**:**platform_device** 型別結構體指標;
* 返回:
* 成功:0;
* 失敗:負數。
**登出:platform_device_unregister**:
* 登出平臺裝置。
* 函式原型:**`void platform_device_unregister(struct platform_device *pdev)`**;位於 **
核心原始碼/drivers/base/platform.c**。
* **pded**:**platform_device** 型別結構體指標;
### 7.3 平臺驅動
#### 7.3.1 platform_driver
**平臺驅動**:
* 使用 **platform_driver** 描述平臺驅動。
* 原始碼:
```c
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
```
* 位於 **核心原始碼/include/platform_device.h**
* **probe**:匹配成功後執行的回撥函式。
* **remove**:移除某個平臺裝置是的回撥函式。
* **driver**:Linux 裝置模型中用於抽象驅動的 device_driver 結構體,platform_driver 繼承該結構體,也就獲取了裝置模型驅動物件的特性。
* **id_table**:表示該驅動能夠相容的裝置型別。
* **platform_device_id**
* **name**:指定驅動名稱。(*用於和 platform_device 中的 name 比較,匹配。*)
* **driver_data**:用於儲存裝置的配置。
* 原始碼:
```c
struct platform_device_id
{
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
```
#### 7.3.2 註冊/登出平臺驅動
**註冊:platform_driver_register**:
* 註冊平臺驅動,掛在到平臺總線上。
* 函式原型:**`int platform_driver_register(struct platform_driver *drv)`**。
* **drv**:**platform_driver** 型別結構體指標;
* 返回:
* 成功:0;
* 失敗:負數。
**登出:platform_driver_unregister**:
* 登出平臺啟動驅動。
* 函式原型:**`void platform_driver_unregister(struct platform_driver *drv)`**;位於 **
核心原始碼/drivers/base/platform.c**。
* **drv**:**platform_driver** 型別結構體指標;
#### 7.3.3 平臺驅動獲取裝置資訊
首先要知道的是,平臺裝置的**硬體資訊**儲存在 **resource** 結構體中。而**軟體資訊**則儲存在 **platform_data** 中。
**獲取硬體資訊:platform_get_resource**:
* 獲取平臺裝置提供的資源結構體。一般用在 **probe** 函式中。、
* 函式原型:**`struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);`**
* **dev**:指定獲取哪個平臺裝置的資源;
* **type**:指定獲取資源的型別,如IORESOURCE_MEM、IORESOURCE_IO等;
* **num**:指定要獲取的資源編號。每個裝置所需要的資源的個數是不一樣的。且不同資源的編號是不一樣的。
* 返回:
* 成功:struct resouce 結構體型別指標;
* 失敗:NULL。
* 若要獲取的資源型別為 **IORESOURCE_IRQ**,平臺裝置驅動還提供以下函式介面,來獲取中斷引腳。
* 函式原型:**`int platform_get_irq(struct platform_device *pdev, unsigned int num)`**
* **pedv**:指定需要獲取哪個平臺裝置的資源;
* **num**:指定要獲取的資源編號。
* 返回:
* 成功:可用中斷號;
* 失敗:負數。
**獲取軟體資訊:dev_get_platdata**:
* **dev**:struct device 結構體型別指標。
```c
static inline void *dev_get_platdata(const struct device *dev)
{
return dev->platform_data;
}
```
## 參考
* [李柱明部落格](https://www.cnblogs.com/lizhuming/p/14605803.html)