---
title: 'I/O 設備管理'
disqus: kyleAlien
---
I/O 設備管理
===
## OverView of Content
OS 的強大並不是因為本身的功能 (調度、內存管理... 等等),而是 OS 的 **==平台化== 屬性**,讓許多不同硬體都可以在 OS 上運作,大大提升集成的方便性
這裡就來看看 RTOS I/O 的集成 (**使用 RTT OS**)
[TOC]
## I/O 設備管理框架
* **OS 會對底層設備驅動封裝**:在裸機 (沒有系統) 的狀況下 I/O 設備基本上就是想訪問就直接訪問,但是 **我們知道在 OS 中有許多 Thread 在運作,為了防止設備中出現衝突、同步問題**
經過 OS 的封裝,加入互斥量、信號、事件、動態分配、回收內存... 特性,讓我們更容易使用
* 對於應用程式而言,我們不該直接訪問底層的硬體驅動,而應該使用 OS 提供的 API 框架 (open、release、mmap... 等等)
> 
### I/O 註冊
* I/O 設備首先要先經過註冊 (註冊到 `I/O Manager`),使用者才能在 I/O Manager 中找到目標設備驅動,註冊到 I/O Manager 有兩種方式
1. **設備驅動直接註冊到 I/O Manager**:這種情況一般是簡易設備
> 
2. **先註冊設備驅動 Module,Module 在註冊到 I/O Manager**:較複雜設備要先註冊到驅動 Module 層,然後再由驅動設備註冊到 I/O Manager
> 
* RTT 有將設備類型分為多種 (以下列出幾種),最常見的莫非就是 **Char 類型( c )、Block 設備( b )**
| 設備類型 | RTT 數據結構 |
| -------- | -------- |
| Char | RT_Device_Class_Char |
| Block | RT_Device_Class_Block |
| 網路 | RT_Device_Class_Netlf |
| 內存 | RT_Device_Class_MTD |
| CAN | RT_Device_Class_CAN |
| RTC | RT_Device_Class_RTC |
| 聲音 | RT_Device_Class_Sound |
| 圖形 | RT_Device_Class_Graphic |
| I^2^C | RT_Device_Class_I2CBUS |
| USB DEvice | RT_Device_Class_USBDevice |
1. Char 類型:泛指非結構化數據 (也就是沒有一定的格式)
> 串口設備
2. Block 類型:Block 類型則是有一定的數據結構,要按照固定格式才能訪問
> Flash 訪問 (Flash 無法隨機訪問,只有透過固定的格式才能存取,像是 page)
## 串口設備
### 串口設備管理 - 結構關係
* 在單晶片機中最常見的就是接口設備,接下來看看 RTT 如何創建管理設備、框架
1. **設備驅動**:這是最基礎的硬體驅動設備在 RTT 中的表示方式,**`rt_device` 繼承了 Kernel 的 `rt_object` 結構**,該結構定義了一些基礎的操作 (init、open、close ...)
```c=
// rtdef.h
/**
* Device structure
*/
struct rt_device
{
// struct 繼承
struct rt_object parent; /**< inherit from rt_object */
enum rt_device_class_type type; /**< device type */
rt_uint16_t flag; /**< device flag */
rt_uint16_t open_flag; /**< device open flag */
rt_uint8_t ref_count; /**< reference count */
rt_uint8_t device_id; /**< 0 - 255 */
/* device call back */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
#ifdef RT_USING_DEVICE_OPS
const struct rt_device_ops *ops;
#else
/* common device interface */
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
#endif /* RT_USING_DEVICE_OPS */
#ifdef RT_USING_POSIX_DEVIO
const struct dfs_file_ops *fops;
struct rt_wqueue wait_queue;
#endif /* RT_USING_POSIX_DEVIO */
void *user_data; /**< device private data */
};
```
2. **設備驅動 Module**:由於目前是要實現串口設備,所以我們要看 RTT 實現的 `rt_serial_device` 結構
```c=
// serial.h
struct rt_serial_device
{
// 繼承 rt_device
struct rt_device parent;
// 串口操作的結構
const struct rt_uart_ops *ops;
struct serial_configure config;
void *serial_rx;
void *serial_tx;
};
```
:::warning
* C 語言的繼承
注意 **`rt_serial_device` 結構又繼承了 `rt_device`**,也就是之後 `rt_device` 可以強制轉型為 `rt_serial_device`
:::
* 並有 **串口操作的數據結構 `rt_uart_ops`**,也就是 **可以透過框架提供的 API 來操控裝置驅動**
```c=
// serial.h
/**
* uart operators
*/
struct rt_uart_ops
{
// 奇、偶校驗,停止位、彼特率... 等等
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
// 開啟、關閉串口
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
// 發送一個 char 數據
int (*putc)(struct rt_serial_device *serial, char c);
// 接收一個 char 數據
int (*getc)(struct rt_serial_device *serial);
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction);
};
```
> 
### 串口初始化 - 初始化硬體抽象結構
* RTT 初始化串口裝置的函數是 `rt_hw_usart_init`
1. **`rt_hw_usart_init` 函數**:初始化 `rt_serial_device` 結構,並設定 UART 專用的操作函數 config
```c=
// drv_usart.c
int rt_hw_usart_init(void) {
rt_size_t obj_num;
int index;
obj_num = sizeof(usart_config) / sizeof(struct at32_usart);
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
for (index = 0; index < obj_num; index++) {
// 初始化 uart 專用操作函數
usart_config[index].serial.ops = &at32_usart_ops;
usart_config[index].serial.config = config;
// 註冊 uart 裝置
// @ 查看 `rt_hw_serial_register` 函數
result = rt_hw_serial_register(&usart_config[index].serial,
usart_config[index].name,
RT_DEVICE_FLAG_RDWR |
RT_DEVICE_FLAG_INT_RX |
RT_DEVICE_FLAG_INT_TX,
&usart_config[index]);
RT_ASSERT(result == RT_EOK);
}
return result;
}
```
> 
2. **`rt_hw_serial_register` 函數**:初始化傳入的 `rt_serial_device` 的父結構 `rt_device`
```c=
// serial.c
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
rt_err_t ret;
struct rt_device *device;
RT_ASSERT(serial != RT_NULL);
device = &(serial->parent);
device->type = RT_Device_Class_Char;
device->rx_indicate = RT_NULL;
device->tx_complete = RT_NULL;
#ifdef RT_USING_DEVICE_OPS
device->ops = &serial_ops;
#else
// 設定函數指標 ( 操控 device 的方式 )
device->init = rt_serial_init;
device->open = rt_serial_open;
device->close = rt_serial_close;
device->read = rt_serial_read;
device->write = rt_serial_write;
device->control = rt_serial_control;
#endif
device->user_data = data;
// 註冊字元裝置
ret = rt_device_register(device, name, flag);
#ifdef RT_USING_POSIX_STDIO
/* set fops */
device->fops = &_serial_fops;
#endif
return ret;
}
```
:::info
* 在這邊可以看到 `serial.c` 註冊了一連串的 指標函數,這些指標函數就是 IO 管理的固定操作,**`serial.c` 就是一個 Module 腳色**
:::
> 
3. `rt_device_register` 初始化傳入的 `rt_device` 的父結構 `rt_device`
```c=
// device.c
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
{
if (dev == RT_NULL)
return -RT_ERROR;
if (rt_device_find(name) != RT_NULL)
return -RT_ERROR;
rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
dev->flag = flags;
dev->ref_count = 0;
dev->open_flag = 0;
#ifdef RT_USING_POSIX_DEVIO
dev->fops = RT_NULL;
rt_wqueue_init(&(dev->wait_queue));
#endif /* RT_USING_POSIX_DEVIO */
return RT_EOK;
}
```
> 
* 串口整體初始化流程圖
> 
### 用戶使用初始化 - 流程
* 使用者串口初始化的使用者,只需要呼叫 `rt_device_init`,就可以將串口初始化;其他硬體驅動裝置也是透過這個方法來初始化,只是每個硬體驅動的實現不同
1. **`rt_device_init` 函數**:首先看 `rt_device_init`,它會透過 `device_init` 宏進行初始化
```c=
// device.c
// 呼叫 結構中的 init 函數指針
#define device_init (dev->init)
rt_err_t rt_device_init(rt_device_t dev)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev != RT_NULL);
/* get device_init handler */
if (device_init != RT_NULL)
{
if (!(dev->flag & RT_DEVICE_FLAG_ACTIVATED))
{
// 查看 device_init
result = device_init(dev);
if (result != RT_EOK)
{
RT_DEBUG_LOG(RT_DEBUG_DEVICE, ("To initialize device:%s failed. The error code is %d\n",
dev->parent.name, result));
}
else
{
dev->flag |= RT_DEVICE_FLAG_ACTIVATED;
}
}
}
return result;
}
```
2. 宏會呼叫 `rt_device_t` 結構中的 init,而 **對於串口來說 init 的初始化函數是 `rt_serial_init`**
```c=
// serial.c (複習一下初始化 init 函數指標的地方)
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
rt_err_t ret;
struct rt_device *device;
... 省略部分
// 設定函數指標 ( 操控 device 的方式 )
device->init = rt_serial_init;
... 省略部分
return ret;
}
```
3. `rt_serial_init` 函數:該函數會 **將 `rt_device` 結構向下轉型成 `rt_serial_device` 結構**,最終透過 configure 函數指針進行初始化
```c=
// serial.c
static rt_err_t rt_serial_init(struct rt_device *dev)
{
rt_err_t result = RT_EOK;
struct rt_serial_device *serial;
RT_ASSERT(dev != RT_NULL);
// 結構向下轉型 !
serial = (struct rt_serial_device *)dev;
/* initialize rx/tx */
serial->serial_rx = RT_NULL;
serial->serial_tx = RT_NULL;
/* apply configuration */
if (serial->ops->configure)
result = serial->ops->configure(serial, &serial->config);
return result;
}
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `RTOS`