--- 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... 等等) > ![](https://i.imgur.com/0nyT8d2.png) ### I/O 註冊 * I/O 設備首先要先經過註冊 (註冊到 `I/O Manager`),使用者才能在 I/O Manager 中找到目標設備驅動,註冊到 I/O Manager 有兩種方式 1. **設備驅動直接註冊到 I/O Manager**:這種情況一般是簡易設備 > ![](https://i.imgur.com/vpEQLLe.png) 2. **先註冊設備驅動 Module,Module 在註冊到 I/O Manager**:較複雜設備要先註冊到驅動 Module 層,然後再由驅動設備註冊到 I/O Manager > ![](https://i.imgur.com/BGp5G5S.png) * 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); }; ``` > ![](https://i.imgur.com/uxKi03F.png) ### 串口初始化 - 初始化硬體抽象結構 * 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; } ``` > ![](https://i.imgur.com/BZu5xF5.png) 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 腳色** ::: > ![](https://i.imgur.com/csJ9qwj.png) 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; } ``` > ![](https://i.imgur.com/j0a4YUX.png) * 串口整體初始化流程圖 > ![](https://i.imgur.com/5jFguF1.png) ### 用戶使用初始化 - 流程 * 使用者串口初始化的使用者,只需要呼叫 `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; } ``` > ![](https://i.imgur.com/D6Q8OTN.png) ## Appendix & FAQ :::info ::: ###### tags: `RTOS`