---
# System prepended metadata

title: I/O 設備管理
tags: [RTOS]

---

---
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`
