文章目录
一、I/O设备模型
RT-Thread中对于I/O设备的抽象是很有特点一个地方,是ucos,freertos都没有的一个地方。RT-Thread提供了一个简单的I/O设备管理框架,希望所有的I/O设备驱动都通过这个框架编写,实现统一的管理。
1.1 I/O设备模型框架
RT-Thread 提供的 I/O 设备模型框架如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层:
- 应用程序:通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互;
- I/O 设备管理层:实现了对设备驱动程序的封装,应用程序通过 I/O设备层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性;
- 设备驱动框架层:是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现;
- 设备驱动层:是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中;对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册。
1.2 I/O设备对象描述
RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,下图是设备对象的继承和派生关系示意图。
设备对象的描述数据结构如下:
// rt-thread-4.0.1\include\rtdef.h
/**
* Device structure
*/
struct rt_device
{
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
#if defined(RT_USING_POSIX)
const struct dfs_file_ops *fops;
struct rt_wqueue wait_queue;
#endif
void *user_data; /**< device private data */
};
设备对象rt_device继承自基对象rt_object,其中rt_device.parent.type值为RT_Object_Class_Device,rt_device.parent.未使用,rt_device.parent.list为设备对象链表节点,所有设备对象被组织成双向链表。
rt_device.type:RT-Thread定义的设备类型如下
// rt-thread-4.0.1\include\rtdef.h
/**
* device (I/O) class type
*/
enum rt_device_class_type
{
RT_Device_Class_Char = 0, /**< character device */
RT_Device_Class_Block, /**< block device */
RT_Device_Class_NetIf, /**< net interface */
RT_Device_Class_MTD, /**< memory device */
RT_Device_Class_CAN, /**< CAN device */
RT_Device_Class_RTC, /**< RTC device */
RT_Device_Class_Sound, /**< Sound device */
RT_Device_Class_Graphic, /**< Graphic device */
RT_Device_Class_I2CBUS, /**< I2C bus device */
RT_Device_Class_USBDevice, /**< USB slave device */
RT_Device_Class_USBHost, /**< USB host bus */
RT_Device_Class_SPIBUS, /**< SPI bus device */
RT_Device_Class_SPIDevice, /**< SPI device */
RT_Device_Class_SDIO, /**< SDIO bus device */
RT_Device_Class_PM, /**< PM pseudo device */
RT_Device_Class_Pipe, /**< Pipe device */
RT_Device_Class_Portal, /**< Portal device */
RT_Device_Class_Timer, /**< Timer device */
RT_Device_Class_Miscellaneous, /**< Miscellaneous device */
RT_Device_Class_Sensor, /**< Sensor device */
RT_Device_Class_Unknown /**< unknown device */
};
其中字符设备、块设备、网络设备是常用的设备类型(也是Linux的三种主要设备类型),它们的分类依据是设备数据与系统之间的传输处理方式。
- 字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备;
- 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址;
- 网络设备:特殊设备的驱动,它负责接收和发送帧数据,可能是物理帧,也可能是ip数据包,这些特性都有网络驱动决定。
rt_device.flag:设备标识如下
// rt-thread-4.0.1\include\rtdef.h
/**
* device flags defitions
*/
#define RT_DEVICE_FLAG_DEACTIVATE 0x000 /**< device is not not initialized */
#define RT_DEVICE_FLAG_RDONLY 0x001 /**< read only */
#define RT_DEVICE_FLAG_WRONLY 0x002 /**< write only */
#define RT_DEVICE_FLAG_RDWR 0x003 /**< read and write */
#define RT_DEVICE_FLAG_REMOVABLE 0x004 /**< removable device */
#define RT_DEVICE_FLAG_STANDALONE 0x008 /**< standalone device */
#define RT_DEVICE_FLAG_ACTIVATED 0x010 /**< device is activated */
#define RT_DEVICE_FLAG_SUSPENDED 0x020 /**< device is suspended */
#define RT_DEVICE_FLAG_STREAM 0x040 /**< stream mode */
#define RT_DEVICE_FLAG_INT_RX 0x100 /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX 0x400 /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /**< DMA mode on Tx */
rt_device.open_flag:设备打开标识如下
// rt-thread-4.0.1\include\rtdef.h
/**
* device open flags defitions
*/
#define RT_DEVICE_OFLAG_CLOSE 0x000 /**< device is closed */
#define RT_DEVICE_OFLAG_RDONLY 0x001 /**< read only access */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /**< write only access */
#define RT_DEVICE_OFLAG_RDWR 0x003 /**< read and write */
#define RT_DEVICE_OFLAG_OPEN 0x008 /**< device is opened */
#define RT_DEVICE_OFLAG_MASK 0xf0f /**< mask of open flag */
rt_device.ref_count:设备引用计数,每当设备的open方法调用后会使设备引用计数增加1。
rt_device.device_id:设备ID,在部分设备类型(比如IIC)中使用。
rt_device.rx_indicate:设备收到数据后的回调函数指针。
rt_device.tx_complete:设备写入数据完成后的回调函数指针。
rt_device.ops:设备操作函数接口,定义的统一接口如下
// rt-thread-4.0.1\include\rtdef.h
/**
* operations set for device object
*/
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);
};
如果想使用Linux的POSIX(Portable Operating System Interface of UNIX)标准接口,可以启用设备文件系统,贯彻Linux一切皆文件的设计思想,把设备抽象为文件,设备句柄也就变成文件描述符了,使用POSIX标准接口还方便将Linux/UNIX系统软件移植到RT-Thread上,POSIX接口fops如下(包括等待队列wait_queue):
// rt-thread-4.0.1\components\dfs\include\dfs_file.h
struct dfs_file_ops
{
int (*open) (struct dfs_fd *fd);
int (*close) (struct dfs_fd *fd);
int (*ioctl) (struct dfs_fd *fd, int cmd, void *args);
int (*read) (struct dfs_fd *fd, void *buf, size_t count);
int (*write) (struct dfs_fd *fd, const void *buf, size_t count);
int (*flush) (struct dfs_fd *fd);
int (*lseek) (struct dfs_fd *fd, off_t offset);
int (*getdents) (struct dfs_fd *fd, struct dirent *dirp, uint32_t count);
int (*poll) (struct dfs_fd *fd, struct rt_pollreq *req);
};
/* file descriptor */
#define DFS_FD_MAGIC 0xfdfd
struct dfs_fd
{
uint16_t magic; /* file descriptor magic number */
uint16_t type; /* Type (regular or socket) */
char *path; /* Name (below mount point) */
int ref_count; /* Descriptor reference count */
struct dfs_filesystem *fs;
const struct dfs_file_ops *fops;
uint32_t flags; /* Descriptor flags */
size_t size; /* Size in bytes */
off_t pos; /* Current file position */
void *data; /* Specific file system data */
};
// rt-thread-4.0.1\include\rtdef.h
/**
* WaitQueue structure
*/
struct rt_wqueue
{
rt_uint32_t flag;
rt_list_t waiting_list;
};
typedef struct rt_wqueue rt_wqueue_t;
rt_device.user_data是设备的私有数据。
1.3 I/O设备对象接口函数
上层的rt_device设备对象可以看作是一个接口类,下层的设备驱动层中的各种设备都应该继承(包含)rt_device设备对象中的所有接口函数,并在自身的驱动中重新实现这些函数。
下层的设备驱动层实现某具体设备的描述和接口函数的实现后,还需要负责创建设备实例,并注册到I/O设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的函数进行动态创建和销毁:
// rt-thread-4.0.1\src\device.c
/**
* This function creates a device object with user data size.
*
* @param type, the kind type of this device object.
* @param attach_size, the size of user data.
*
* @return the allocated device object, or RT_NULL when failed.
*/
rt_device_t rt_device_create(int type, int attach_size)
{
int size;
rt_device_t device;
size = RT_ALIGN(sizeof(struct rt_device), RT_ALIGN_SIZE);
attach_size = RT_ALIGN(attach_size, RT_ALIGN_SIZE);
/* use the totoal size */
size += attach_size;
device = (rt_device_t)rt_malloc(size);
if (device)
{
rt_memset(device, 0x0, sizeof(struct rt_device));
device->type = (enum rt_device_class_type)type;
}
return