Nuttx相关的历史文章:
介绍
Nuttx支持多种设备驱动,包括:
- 字符设备驱动,比如串口设备、触摸屏设备、ADC/DAC、PWM、CAN、正交编码器、Timer、RTC、Watchdog、Keyboard/Keypad等;
- 块设备驱动;
- 其他特殊设备驱动,比如Ethernet、SPI、I2C、Frame Buffer、LCD、MTD、SDIO、USB等;
总体来说,Nuttx中的驱动机制相对来说比较简单,它并没有提供像Linux系统那样复杂的驱动模型机制,比如Device、Driver、Bus、Class等。Nuttx只是简单的通过驱动注册接口,将驱动注册进文件系统中,并实现file_operations
操作函数集,上层应用便能通过标准的系统调用,进而调用到低层的驱动。
我将以字符设备驱动来阐述整个驱动的机制。
数据结构与接口
数据结构
应用层通过系统调用来访问驱动:系统调用->vfs->驱动,因此首先需要了解一下,驱动注册进文件系统时所涉及到的数据结构。数据结构的相关定义在include/nuttx/fs/fs.h
文件中。
首先,驱动注册后,会创建一个inode
,对应到设备文件上:
/* This structure represents one inode in the Nuttx pseudo-file system */
struct inode
{
FAR struct inode *i_peer; /* Link to same level inode */
FAR struct inode *i_child; /* Link to lower level inode */
int16_t i_crefs; /* References to inode */
uint16_t i_flags; /* Flags for inode */
union inode_ops_u u; /* Inode operations */
#ifdef CONFIG_FILE_MODE
mode_t i_mode; /* Access mode flags */
#endif
FAR void *i_private; /* Per inode driver private data */
char i_name[1]; /* Name of inode (variable) */
};
其中i_flags
字段用于标记该inode
对应的为什么文件,典型的有驱动、消息队列等,专门有宏定义来设置或者判断这个字段是否为驱动文件:
#define INODE_IS_DRIVER(i) INODE_IS_TYPE(i,FSNODEFLAG_TYPE_DRIVER)
#define INODE_SET_DRIVER(i) INODE_SET_TYPE(i,FSNODEFLAG_TYPE_DRIVER)
struct inode
结构体中inode_ops_u
用于描述操作函数集,这个字段是一个联合体,可以是字符设备驱动、块设备驱动、挂载点等的操作函数集。驱动操作函数集如下:
struct file_operations
{
/* The device driver open method differs from the mountpoint open method */
int (*open)(FAR struct file *filep);
/* The following methods must be identical in signature and position because
* the struct file_operations and struct mountp_operations are treated like
* unions.
*/
int (*close)(FAR struct file *filep);
ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
off_t (*seek)(FAR struct file *filep, off_t offset, int whence);
int (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);
/* The two structures need not be common after this point */
#ifndef CONFIG_DISABLE_POLL
int (*poll)(FAR struct file *filep, struct pollfd *fds, bool setup);
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
int (*unlink)(FAR struct inode *inode);
#endif
};
这个函数集,由低层的驱动来实现,并且设置进设备文件对应的inode
中,当系统调用操作设备文件时,便能根据设备文件对应的inode
来找到对应的函数了。
接口
驱动注册的时候,会调用register_driver()
接口:
/****************************************************************************
* Name: register_driver
*
* Description:
* Register a character driver inode the pseudo file system.
*
* Input parameters:
* path - The path to the inode to create
* fops - The file operations structure
* mode - inmode priviledges (not used)
* priv - Private, user data that will be associated with the inode.
*
* Returned Value:
* Zero on success (with the inode point in 'inode'); A negated errno
* value is returned on a failure (all error values returned by
* inode_reserve):
*
* EINVAL - 'path' is invalid for this operation
* EEXIST - An inode already exists at 'path'
* ENOMEM - Failed to allocate in-memory resources for the operation
*
****************************************************************************/
int register_driver(FAR const char *path, FAR const struct file_operations *fops,
mode_t mode, FAR void *priv)
{
FAR struct inode *node;
int ret;
/* Insert a dummy node -- we need to hold the inode semaphore because we
* will have a momentarily bad structure.
*/
inode_semtake();
ret = inode_reserve(path, &node);
if (ret >= 0)
{
/* We have it, now populate it with driver specific information.
* NOTE that the initial reference count on the new inode is zero.
*/
INODE_SET_DRIVER(node);
node->u.i_ops = fops;
#ifdef CONFIG_FILE_MODE
node->i_mode = mode;
#endif
node->i_private = priv;
ret = OK;
}
inode_semgive();
return ret;
}
这个接口完成以下几个操作:
- 根据
path
(一般对应设备文件,比如/dev/xxxx
),来查找是否存在对应的inode
,如果没有的话,那为path
创建一个inode
; - 将实际驱动实现的
struct file_operations fops
更新到path
对应的inode
中,此外还设置权限; - 将
priv
数据设置进inode
中,这个一般存放驱动的私有数据;
ADC驱动
下面将以一个实际的驱动,ADC驱动
,来分析一下流程。
在Nuttx的驱动代码中,你会发现经常会把驱动分成两部分,一个是upper half
,一个是lower half
:
-
upper half
:上半部分提供了应用程序级的通用接口,也就是实现了file_operations
中的函数集,比如针对ADC驱动
,专门有drivers/analog/adc.c
来描述上半部分的操作,这个对于所有的ADC
设备都是相同的; -
lower half
:下半部分基于特定平台的驱动程序,用于实现硬件级的控制,比如寄存器的操作等。arch/arm/src/lpc43xx/lpc43_adc.c
文件实现了特定的硬件驱动;
整体的框架如下图所示:
- 芯片相关,代表了
lower half
,针对硬件的实际操作,并且在中断处理函数中,会去回调upper half
的回调函数。可以在这个回调函数中做一些处理,比如通过消息队列的机制,统治上层应用已经收到了数据; - 通用框架,代表了
upper half
,对接上层的系统调用,并且在实现file_operations
函数集的时候,会去调用lower half
的接口; - 板级部分,这个部分其实是将
upper half
和lower half
进行绑定,建立连接并注册进文件系统中,这个接口最终会在系统boot的阶段调用;
具体的驱动代码就不贴了。
其他的驱动实现,机制都大体类似,分成两部分,上半部分对接应用系统调用,下半部分对应实际的低层硬件操作,这种分层是一种合理的做法,上半部分做成通用的框架,不需要改动,下半部分针对不同硬件实现具体的操作接口即可了。