RTThread IO device and driver learning

1. IO device model framework

There are three levels:

Insert picture description here

  • The device management layer
    realizes the encapsulation of the device driver.
    The application program accesses the underlying device through the standard interface provided by the I/O device layer, and the upgrade and replacement of the device driver will not affect the upper layer application.
    In this way, the code related to the hardware operation of the device can exist independently of the application, and both parties only need to pay attention to the realization of their respective functions, thereby reducing the coupling and complexity of the code and improving the reliability of the system.

  • The device driver framework layer is
    an abstraction of similar hardware device drivers, extracting the same parts of similar hardware device drivers from different manufacturers, leaving different parts out of the interface, and implementing them by the driver.

  • Device driver layer
    A group of programs that drive hardware devices to work and realize the function of accessing hardware devices.
    It is responsible for creating and registering I/O devices. For devices with simple operation logic, you can directly register the device to the I/O device manager without going through the device driver framework layer. The sequence diagram is shown in the figure below, and the main two are as follows: point:

  1. The model defines the device driving apparatus, an example of a device includes hardware to create access capabilities, the devices rt_device_register()registered to the I / O device manager interface.
  2. Application through rt_device_find()to find the device interface, and then use the I / O device management interface to access the hardware.

When there is no device driver framework layer:
When there is no device driver framework layer

For other devices, such as watchdogs, the created device instance will be registered to the corresponding device driver framework first, and then the device driver framework will register to the I/O device manager. There are three main points:

  1. The watchdog device driver apparatus according to the watchdog model definition, create an instance of the device includes a hardware watchdog access capabilities, and by the watchdog device rt_hw_watchdog_register()registered to the watchdog device driver framework interfaces.
  2. Watchdog frame by a device driver rt_device_register()interface to the watchdog register the device I / O device manager.
  3. The application program accesses the watchdog device hardware through the I/O device management interface.

Complete IO device driver model:
Complete IO device driver model

2. IO device class model

Device class relationship diagram:
Device class diagram

Device class structure definition:

struct rt_device
{
    
    
    struct rt_object          parent;        /* 内核对象基类 */
    enum rt_device_class_type type;          /* 设备类型 */
    rt_uint16_t               flag;          /* 设备参数 */
    rt_uint16_t               open_flag;     /* 设备打开标志 */
    rt_uint8_t                ref_count;     /* 设备被引用次数 */
    rt_uint8_t                device_id;     /* 设备 ID,0 - 255 */

    /* 数据收发回调函数 */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

    const struct rt_device_ops *ops;    /* 设备操作方法 */

    /* 设备的私有数据 */
    void *user_data;
};
typedef struct rt_device *rt_device_t;

3. Types of I/O devices

RT-Thread supports a variety of I/O device types, the main device types are as follows:

RT_Device_Class_Char             /* 字符设备       */
RT_Device_Class_Block            /* 块设备         */
RT_Device_Class_NetIf            /* 网络接口设备    */
RT_Device_Class_MTD              /* 内存设备       */
RT_Device_Class_RTC              /* RTC 设备        */
RT_Device_Class_Sound            /* 声音设备        */
RT_Device_Class_Graphic          /* 图形设备        */
RT_Device_Class_I2CBUS           /* I2C 总线设备     */
RT_Device_Class_USBDevice        /* USB device 设备  */
RT_Device_Class_USBHost          /* USB host 设备   */
RT_Device_Class_SPIBUS           /* SPI 总线设备     */
RT_Device_Class_SPIDevice        /* SPI 设备        */
RT_Device_Class_SDIO             /* SDIO 设备       */
RT_Device_Class_Miscellaneous    /* 杂类设备        */

4. Create and register I/O devices

The driver layer is responsible for creating device instances and registering them in the I/O device manager,

4.1, create a device

  • Device instance can be created by static declaration
  • You can also use the following interface for dynamic creation:
rt_device_t rt_device_create(int type, int attach_size);
parameter description
type Device type, can take the device type value listed in the previous section
attach_size User data size
return ——
Device handle Created successfully
RT_NULL Creation failed, dynamic memory allocation failed

When invoking the interface, the system will assign a dynamic device control block from the heap memory, the size struct rt_deviceand attach_sizetype and the device type of the parameter set. After the device is created, it needs to implement its method of accessing the hardware.

struct rt_device_ops
{
    
    
    /* 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);
};

The description of each operation method is shown in the following table:

Method name Method description
init Initialize the device. After the device is initialized, the flag of the device control block will be set to the activated state (RT_DEVICE_FLAG_ACTIVATED). If the flag flag in the device control block has been set to an active state, it will return immediately when the initialization interface is run again without re-initialization.
open Turn on the device. Some devices are not turned on and start running as soon as the system is started, or the device needs to send and receive data, but if the upper layer application is not ready, the device should not be enabled by default and start to receive data. Therefore, it is recommended to enable the device only when the open interface is called when writing the low-level driver.
close Turn off the device. When the device is turned on, the device control block maintains an open count, performs a + 1 operation when the device is turned on, and performs a − 1 operation when the device is closed. When the counter becomes 0, the actual close operation is performed.
read Read data from the device. The parameter pos is the offset of the read data, but some devices do not necessarily need to specify the offset, such as serial devices, the device driver should ignore this parameter. For block devices, pos and size are based on the data block size of the block device. For example, the data block size of the block device is 512, and pos = 10, size = 2 in the parameters, then the driver should return the 10th block in the device (starting with the 0th block), a total of 2 blocks of data. The type returned by this interface is rt_size_t, which is the number of bytes or blocks read. Normally, the value of size in the parameter should be returned. If it returns zero, please set the corresponding errno value.
write Write data to the device. The parameter pos is the offset of the written data. Similar to read operations, for block devices, pos and size are based on the data block size of the block device. The type returned by this interface is rt_size_t, which is the number of bytes or blocks of data actually written. Normally, the value of size in the parameter should be returned. If it returns zero, please set the corresponding errno value.
control Control the device according to cmd commands. Commands are often implemented customized by various underlying device drivers. For example, the parameter RT_DEVICE_CTRL_BLK_GETGEOME means to get the size information of the block device.

When a dynamically created device is no longer needed, it can be destroyed by the following function:

void rt_device_destroy(rt_device_t device);
parameter description
device Device handle
return no

4.2. Register the device

After the device is created, it needs to be registered in the I/O Device Manager so that the application can access it. The function of registering the device is as follows:

rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); 
parameter description
dev Device handle
name The device name, the maximum length of the device name is specified by the macro RT_NAME_MAX defined in rtconfig.h, and the extra part will be automatically cut off
flags Device mode flag
return ——
RT_EOK registration success
RT_ERROR Registration failed, dev is empty or name already exists

The flags parameter supports the following parameters (you can use or to support a variety of parameters), in fact, the operation authority of the device is set:

#define RT_DEVICE_FLAG_RDONLY       0x001 /* 只读 */
#define RT_DEVICE_FLAG_WRONLY       0x002 /* 只写  */
#define RT_DEVICE_FLAG_RDWR         0x003 /* 读写  */
#define RT_DEVICE_FLAG_REMOVABLE    0x004 /* 可移除  */
#define RT_DEVICE_FLAG_STANDALONE   0x008 /* 独立   */
#define RT_DEVICE_FLAG_SUSPENDED    0x020 /* 挂起  */
#define RT_DEVICE_FLAG_STREAM       0x040 /* 流模式  */
#define RT_DEVICE_FLAG_INT_RX       0x100 /* 中断接收 */
#define RT_DEVICE_FLAG_DMA_RX       0x200 /* DMA 接收 */
#define RT_DEVICE_FLAG_INT_TX       0x400 /* 中断发送 */
#define RT_DEVICE_FLAG_DMA_TX       0x800 /* DMA 发送 */

4.3, log off the device

rt_err_t rt_device_unregister(rt_device_t dev);
parameter description
device Device handle
return ——
RT_EOK success

The following sample code registered watchdog device, calls rt_hw_watchdog_register()the interface devices rt_device_register()are registered to the I / O device manager interface.

const static struct rt_device_ops wdt_ops =
{
    
    
    rt_watchdog_init,
    rt_watchdog_open,
    rt_watchdog_close,
    RT_NULL,
    RT_NULL,
    rt_watchdog_control,
};

rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd,
                                 const char                *name,
                                 rt_uint32_t                flag,
                                 void                      *data)
{
    
    
    struct rt_device *device;
    RT_ASSERT(wtd != RT_NULL);

    device = &(wtd->parent);

    device->type        = RT_Device_Class_Miscellaneous;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

    device->ops         = &wdt_ops;
    device->user_data   = data;

    /* register a character device */
    return rt_device_register(device, name, flag);
}

问题:系统是在哪里调用的hw_watchdog_register接口?

5、访问 I/O 设备

I/O 设备管理接口与 I/O 设备的操作方法的映射关系下图所示:

映射关系:
Mapping relations

I/O设备管理接口放在内核的device.c文件中。

5.1、查找设备

应用程序根据设备名称获取设备句柄,进而可以操作设备。查找设备函数如下所示:

rt_device_t rt_device_find(const char* name);
参数 描述
name 设备名称
返回 ——
设备句柄 查找到对应设备将返回相应的设备句柄
RT_NULL 没有找到相应的设备对象

5.2、初始化设备

获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作:

rt_err_t rt_device_init(rt_device_t dev);
参数 描述
dev 设备句柄
返回 ——
RT_EOK 设备初始化成功
错误码 设备初始化失败

注意事项:

当一个设备已经初始化成功后,调用这个接口将不再重复做初始化 0。

5.3、打开和关闭设备

通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 描述
dev 设备句柄
oflags 设备打开模式标志
返回 ——
RT_EOK 设备打开成功
-RT_EBUSY 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开
其他错误码 设备打开失败

oflags 支持以下的参数:

#define RT_DEVICE_OFLAG_CLOSE 0x000   /* 设备已经关闭(内部使用)*/
#define RT_DEVICE_OFLAG_RDONLY 0x001  /* 以只读方式打开设备 */
#define RT_DEVICE_OFLAG_WRONLY 0x002  /* 以只写方式打开设备 */
#define RT_DEVICE_OFLAG_RDWR 0x003    /* 以读写方式打开设备 */
#define RT_DEVICE_OFLAG_OPEN 0x008    /* 设备已经打开(内部使用)*/
#define RT_DEVICE_FLAG_STREAM 0x040   /* 设备以流模式打开 */
#define RT_DEVICE_FLAG_INT_RX 0x100   /* 设备以中断接收模式打开 */
#define RT_DEVICE_FLAG_DMA_RX 0x200   /* 设备以 DMA 接收模式打开 */
#define RT_DEVICE_FLAG_INT_TX 0x400   /* 设备以中断发送模式打开 */
#define RT_DEVICE_FLAG_DMA_TX 0x800   /* 设备以 DMA 发送模式打开 */

注意事项:

如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。

应用程序打开设备完成读写等操作后,如果不需要再对设备进行操作则可以关闭设备,通过如下函数完成:

rt_err_t rt_device_close(rt_device_t dev);
参数 描述
dev 设备句柄
返回 ——
RT_EOK 关闭设备成功
-RT_ERROR 设备已经完全关闭,不能重复关闭设备
其他错误码 关闭设备失败

注意事项:

关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。

5.4、控制设备

通过命令控制字,应用程序也可以对设备进行控制,通过如下函数完成:

rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数 描述
dev 设备句柄
cmd 命令控制字,这个参数通常与设备驱动程序相关
arg 控制的参数
返回 ——
RT_EOK 函数执行成功
-RT_ENOSYS 执行失败,dev 为空
其他错误码 执行失败

参数 cmd 的通用设备命令可取如下宏定义:

#define RT_DEVICE_CTRL_RESUME           0x01   /* 恢复设备 */
#define RT_DEVICE_CTRL_SUSPEND          0x02   /* 挂起设备 */
#define RT_DEVICE_CTRL_CONFIG           0x03   /* 配置设备 */
#define RT_DEVICE_CTRL_SET_INT          0x10   /* 设置中断 */
#define RT_DEVICE_CTRL_CLR_INT          0x11   /* 清中断 */
#define RT_DEVICE_CTRL_GET_INT          0x12   /* 获取中断状态 */

5.5、读写设备

  • 应用程序从设备中读取数据可以通过如下函数完成:
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
参数 描述
dev 设备句柄
pos 读取数据偏移量
buffer 内存缓冲区指针,读取的数据将会被保存在缓冲区中
size 读取数据的大小
返回 ——
读到数据的实际大小 如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位
0 需要读取当前线程的 errno 来判断错误状态

调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。

  • 向设备中写入数据,可以通过如下函数完成:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
参数 描述
dev 设备句柄
pos 写入数据偏移量
buffer 内存缓冲区指针,放置要写入的数据
size 写入数据的大小
返回 ——
写入数据的实际大小 如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位
0 需要读取当前线程的 errno 来判断错误状态

调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。

5.6、数据收发回调

当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达:

rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
参数 描述
dev 设备句柄
rx_ind 回调函数指针
返回 ——
RT_EOK 设置成功

该函数的回调函数由调用者提供。当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在 size 参数中传递给上层应用。上层应用线程应在收到指示后,立刻从设备中读取数据。

在应用程序调用 rt_device_write() 入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示,函数参数及返回值见:

rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
参数 描述
dev 设备句柄
tx_done 回调函数指针
返回 ——
RT_EOK 设置成功

When this function is called, the callback function is provided by the caller. When the hardware device finishes sending data, the driver calls back this function and passes the data block address buffer that has been sent to the upper application as a parameter. The upper application (thread) will release the buffer memory block or use it as a buffer for the next write data according to the situation of sending the buffer when receiving the instruction.

5.7. Device access example

The following sample code for the program accessing the device by first rt_device_find()look to the watchdog port device, to obtain a handle to the device, and then through rt_device_init()port initializing apparatus, through rt_device_control()the overflow opening time of the watchdog device.

#include <rtthread.h>
#include <rtdevice.h>

#define IWDG_DEVICE_NAME    "iwg"

static rt_device_t wdg_dev;

static void idle_hook(void)
{
    
    
    /* 在空闲线程的回调函数里喂狗 */
    rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
    rt_kprintf("feed the dog!\n ");
}

int main(void)
{
    
    
    rt_err_t res = RT_EOK;
    rt_uint32_t timeout = 1000;    /* 溢出时间 */

    /* 根据设备名称查找看门狗设备,获取设备句柄 */
    wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
    if (!wdg_dev)
    {
    
    
        rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME);
        return RT_ERROR;
    }
    /* 初始化设备 */
    res = rt_device_init(wdg_dev);
    if (res != RT_EOK)
    {
    
    
        rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME);
        return res;
    }
    /* 设置看门狗溢出时间 */
    res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
    if (res != RT_EOK)
    {
    
    
        rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME);
        return res;
    }
    /* 设置空闲线程回调函数 */
    rt_thread_idle_sethook(idle_hook);

    return res;
}

5.8. Interaction process between melis user space and kernel driver — ioctl

Insert picture description here

Guess you like

Origin blog.csdn.net/houxiaoni01/article/details/108343772