驱动分层的一种实现方式
前言
操作系统最底层的驱动涉及到硬件部分,由于硬件的差异性,比如一个timer ip,厂商A的芯片需要控制3个寄存器完成一次计时,厂商B的芯片只需要操作两个寄存器就能达到相同的效果,因此,在软件层面,我们需要把最底层的操作剥离出来,也就是进行分层,将底层硬件的差异性隔离出来。那么具体怎么实现的呢?
实现方式
底层硬件的控制是C语言的天下,这里要实现驱动分层,我们需要用到C语言的一个重要语法----指针,一个指向函数的指针,同时使用一种我理解为注册的机制,完成分层与隔离。这里我以nuttx实时操作系统为例。
NUTTX的驱动实现
对于操作系统而言,需要定义一个结构体,这个结构体的成员是函数指针,操作系统只需要使用这些函数指针,就能达到想要的操作硬件的效果。而对于硬件的具体操作,需要由硬件驱动的工程师,在相应的函数中完成对硬件的具体操作后,把函数填充到操作系统提供的函数指针中即可。
以timer为例,nuttx提供以下结构体声明,需要由相应的驱动工程师实现以下结构体中的函数功能。
struct timer_lowerhalf_s;
struct timer_ops_s
{
CODE int (*start)(FAR struct timer_lowerhalf_s *lower);
CODE int (*stop)(FAR struct timer_lowerhalf_s *lower);
CODE int (*getstatus)(FAR struct timer_lowerhalf_s *lower,
FAR struct timer_status_s *status);
CODE int (*settimeout)(FAR struct timer_lowerhalf_s *lower,
uint32_t timeout);
CODE void (*setcallback)(FAR struct timer_lowerhalf_s *lower,
CODE tccb_t callback, FAR void *arg);
CODE int (*ioctl)(FAR struct timer_lowerhalf_s *lower, int cmd,
unsigned long arg);
CODE int (*maxtimeout)(FAR struct timer_lowerhalf_s *lower,
FAR uint32_t *maxtimeout);
};
struct timer_lowerhalf_s
{
FAR const struct timer_ops_s *ops;
};
nuttx中称上面的代码为up层,底层驱动一般称为lower层。在lower层,一般也会定义一个static的全局结构体,在这个结构体中保存lower层需要的一些信息,比如tiemr的频率,位数,状态等等,由驱动工程师自己定义。
lower层可以如下声明:
struct timer_lowerhalf_s {
FAR const struct timer_ops_s *ops;
tccb_t callback;
FAR void *arg;
bool started;
uint32_t freq;
};
上面声明中,结构体第一个成员是一个指向结构体的指针变量,驱动工程师需要实现相应的工程函数,然后赋值给一个timer_ops_s的结构体,再让ops指针指向这个结构体。这是完成分层的第一步!
static struct dw_lowerhalf_s g_timer_lowerhalf;
static const struct timer_ops_s g_timer_ops = {
.start = timer_start,
.stop = timer_stop,
.getstatus = timer_getstatus,
.settimeout = timer_settimeout,
.setcallback = timer_setcallback,
.maxtimeout = timer_maxtimeout,
.ioctl = NULL,
};
g_timer_lowerhalf.ops = &g_timer_ops;
上面的步骤是将所有的信息记录再一个g_timer_lowerhalf的结构体中,完成之后,我们需要将这个结构体返回给up层,也就是我理解的注册。使用一个结构体指针,指向g_timer_lowerhalf,做强制转换,滤除上层不需要的信息后,将该地址返回至上层,这是第二步,也就是完成”注册“。
struct dw_lowerhalf_s *lower = &g_timer_lowerhalf;
//抹除lower层的驱动信息,返回up层需要的struct timer_ops_s *ops即可
return (struct timer_lowerhalf_s *)lower;
当然,可以用这种直接return的方式,也可以用上层提供的register函数,将这个g_timer_lowerhalf的地址告诉up层,这取决于操作系统提供的方式。
总结
上述对驱动分层的一种实现做了简单的描述,在linux等大型操作系统上,对于驱动的分层以及编写更加复杂,并且对于硬件比较复杂,如USB等硬件驱动的编写也较为复杂,不在本文探讨范围内。本文旨在资源受限以及对效率要求很高的场景下探讨简单的驱动分层。
利用C语言的函数指针与结构体实现分层,这也是在工作中根据实际代码总结出来的一种实现方式,若是有其他的实现方式,或者描述不准确的地方欢迎在评论区指出,一起探讨交流。同时随着学习研究的深入,相信后续会对分层会有更多的理解,希望日后回头来看这篇文章,能发现自己理解的分层方向是正确的!