rt-thread设备驱动框架
1. 设备操作接口
rt-thread提供了一套通用的设备驱动框架,是为了应用开发使用统一的接口来操作设备,从而降低应用程序和驱动程序的耦合性。这样的驱动框架带来的好处是,当底层设备驱动更换后,上层应用可以不进行修改。
/* 根据设备名查找设备得到rt_device */
rt_device_t rt_device_find(const char* name)
/* 初始化设备 */
rt_err_t rt_device_init(rt_device_t dev)
/* 打开设备 */
rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflags)
/* 关闭设备 */
rt_err_t rt_device_close(rt_device_t dev)
/* 从设备读数据 */
rt_size_t rt_device_read (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)
/* 向设备写数据 */
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
/* 控制设备 */
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg)
上述接口为应用层操作设备的统一接口,其操作设备的流程为:
- 根据设备名,调用
rt_device_find
查找设备的rt_device
对象。 - 以
rt_device
为实参调用设备操作接口rt_device_opne
、rt_device_read
等。
2. 内核对象
rt-thread的设备驱动框架与内核对象是深度绑定的,在介绍设备驱动框架的时候有必要说明一下rt-thread的内核对象组织形式。rt-thread的内核中抽象了很多对象,比如线程对象、信号量对象、互斥体对象、消息队列对象、设备对象等。下面为内核中所有对象的枚举。
enum rt_object_class_type
{
RT_Object_Class_Null = 0x00, /**< The object is not used. */
RT_Object_Class_Thread = 0x01, /**< The object is a thread. */
RT_Object_Class_Semaphore = 0x02, /**< The object is a semaphore. */
RT_Object_Class_Mutex = 0x03, /**< The object is a mutex. */
RT_Object_Class_Event = 0x04, /**< The object is a event. */
RT_Object_Class_MailBox = 0x05, /**< The object is a mail box. */
RT_Object_Class_MessageQueue = 0x06, /**< The object is a message queue. */
RT_Object_Class_MemHeap = 0x07, /**< The object is a memory heap. */
RT_Object_Class_MemPool = 0x08, /**< The object is a memory pool. */
RT_Object_Class_Device = 0x09, /**< The object is a device. */
RT_Object_Class_Timer = 0x0a, /**< The object is a timer. */
RT_Object_Class_Module = 0x0b, /**< The object is a module. */
RT_Object_Class_Memory = 0x0c, /**< The object is a memory. */
RT_Object_Class_Unknown = 0x0e, /**< The object is unknown. */
RT_Object_Class_Static = 0x80 /**< The object is a static object. */
};
_object_container
是一个成员类型为struct rt_object_information
的数组,定义如下:
static struct rt_object_information _object_container[RT_Object_Info_Unknown] =
{
/* initialize object container - thread */
{RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)},
#ifdef RT_USING_SEMAPHORE
/* initialize object container - semaphore */
{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)},
#endif
//.......
#ifdef RT_USING_DEVICE
/* initialize object container - device */
{RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), sizeof(struct rt_device)},
#endif
};
这个数组是在代码中静态定义的,其数据成员跟用户配置了那些功能有关。比如,用户使用menuconfig
使能了RT_USING_DEVICE
,那么rt_device
的这个数组成员就会编译进代码,否则不会。这个数组里面定义了内核中所有的对象信息,比如线程rt_thread
对象信息、信号量rt_semaphore
对象信息、设备rt_device
对象信息等。
对象信息使用结构rt_object_information
来描述:
struct rt_object_information
{
enum rt_object_class_type type; /**< object class type */
rt_list_t object_list; /**< object list */
rt_size_t object_size; /**< object size */
};
该结构体中的type
成员用来说明是哪一类对象,如RT_Object_Class_Thread
、RT_Object_Class_Semaphore
、RT_Object_Class_Mutex
、RT_Object_Class_Device
等。object_size
描述的是对象的大小,如sizeof(struct rt_thread)
、sizeof(struct rt_device)
。成员object_list
是一个双向链表,同一类型的对象就会通过链表挂载到对应对象信息的链表上。
rt_object_information
用来描述某一类内核对象的信息,具体的对象则使用struct rt_object
来描述:
struct rt_object
{
char name[RT_NAME_MAX]; /**< name of kernel object */
rt_uint8_t type; /**< type of kernel object */
rt_uint8_t flag; /**< flag of kernel object */
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module */
#endif /* RT_USING_MODULE */
rt_list_t list; /**< list node of kernel object */
};
其中,成员type
与rt_object_information
的type
相同,都表示是内核中的哪一类对象。name
为对象的名字,在设备对象中,rt_device_find
就是通过name
进行比对查找的。list
就是链表节点,用于挂载到对象信息的链表中。rt_object
一般会嵌入到更大的结构体中,按照面向对象的思想作为一个基类。综上,rt-thread中内核对象的组织形式如下:
类型为RT_Object_Class_Device
的对象都被挂载到相同类型的对象信息链表上,如果要查找某一个设备只需要遍历这个链表,逐个比较设备名就行了,函数rt_object_find
就是这样实现的。
3. 设备驱动模型
设备驱动模型整体上可分为三层:
- 应用层:应用程序调用设备操作接口
rt_device_find
、rt_device_read
等来操作设备 - 核心层:提供驱动对象的注册接口、把驱动对象挂载到对应设备信息的链表上
- 驱动相关层:定义驱动对象、实现设备操作函数、调用驱动对象的注册函数进行注册
在应用层,就是调用驱动框架提供的设备标准操作函数。核心层为驱动框架的主要部分,这一部分与前文介绍的内核对象组织形式深度绑定。驱动相关层就是具体设备的的驱动了,开发也较为简单只要实现相关的操作函数,然后调用注册接口进行注册就完成了驱动开发。
驱动框架的核心层的逻辑为:提供一个设备抽象数据结构,这个设备抽象数据结构包含一系列设备操作函数,这些函数需要驱动开发者实现,最后调用设备对象的注册函数进行注册。Linux驱动框架就是采用这种实现逻辑,例如熟悉的字符设备cdev
和设备操作函数file_operations
。
rt-thread的驱动框架也采用相同的逻辑,首先对设备进行了抽象,使用一个rt_device
结构体进行描述:
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 */
/* 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);
/* 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);
};
- 上述代码进行了部分删减,可以看到这个结构体中有
rt_object
内核对象,其作用就是将这个设备结构体挂载到设备对象信息的链表上,函数rt_object_find
就可以通过遍历设备信息链表来找到这个设备的rt_device
,如前文1小结的图所示。 - init、open、close、read等就是设备的操作函数,这些函数需要驱动开发者实现。
- 驱动框架最终会提供一个设备注册函数,调用流程如下:
rt_device_register(rt_device_t dev, const char *name, rt_uint16_t flags)
rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
1. struct rt_object_information *information;
2. rt_list_insert_after(&(information->object_list), &(object->list));
这个函数的作用就是将rt_device
中的rt_object
挂载到类型为RT_Object_Class_Device
设备信息的链表上,从而完成这个设备的注册。
- 对于应用调用流程,首先通过函数
rt_object_find
在类型为RT_Object_Class_Device
设备信息的链表上遍历查找,通过设备名进行比对,比对成功后返回rt_device
。 - 假如此时应用程序调用read函数:
rt_device_read
rt_device->read
xxx_read
经过层层调用,最终执行驱动开发者实现的设备操作函数,其他应用api也是相同的逻辑,相关调用流程如下: