我们在学习I2C、USB、SD驱动时,你们有没有发觉一个共性,就是在驱动开发时,每位驱动都分层三部份,由上到下分别是:
1、XXX设备驱动
2、XXX核心层
3、XXX主机控制器驱动
而须要我们编撰的主要是设备驱动部份,主机控制器驱动部份也有少量编撰,两者进行交互主要时由核心层提供的插口来实现;这样结构清晰,大大地有利于我们的驱动开发,这其中就是借助了Linux设备驱动开发中两个重要思想,明天我们来仔细解析一下。
一、设备驱动的分层思想
在面向对象的程序设计中,可以为某一类相像的事物定义一个泛型,而具体的事物可以承继这个子类中的函数。假若对于承继的这个事物而言,其某函数的实现与泛型一致,那它就可以直接承继子类的函数;相反,它可以重载之。这些面向对象的设计思想极大地提升了代码的可重用能力,是对现实世界事物间关系的一种良好呈现。
Linux内核完全由C语言和汇编语言写成,并且却频繁用到了面向对象的设计思想。在设备驱动方面,常常为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。同样的,假如具体的设备不想使用核心层的函数,它可以重载之。举个反例:
[cpp]viewplaincopy
1.return_typecore_funca(xxx_device*bottom_dev,param1_typeparam1,param1_typeparam2)
2.{
3.if(bottom_dev->funca)
4.returnbottom_dev->funca(param1,param2);
5./*核心层通用的funca代码*/
6....
7.}
上述core_funca的实现中,会检测底层设备是否重载了funca(),假如重载了,就调用底层的代码linux设备驱动开发详解ldd,否则,直接使用通用层的。这样做的用处是如何安装LINUX,核心层的代码可以处理绝大多数该类设备的funca()对应的功能,只有少数特殊设备须要重新实现funca()。
再看一个反例:
[cpp]viewplaincopy
1.return_typecore_funca(xxx_device*bottom_dev,param1_typeparam1,param1_typeparam2)
2.{
3./*通用的步骤代码A*/
4....
5.bottom_dev->funca_ops1();
6./*通用的步骤代码B*/
7....
8.bottom_dev->funca_ops2();
9./*通用的步骤代码C*/
10....
11.bottom_dev->funca_ops3();
12.}
上述代码假设为了实现funca(),对于同类设备而言,操作流程一致,都要经过“通用代码A、底层ops1、通用代码B、底层ops2、通用代码C、底层ops3”这几步,分层设计显著带来的益处是,对于通用代码A、B、Clinux设备驱动开发详解ldd,具体的底层驱动不须要再实现,而仅仅只关心其底层的操作ops1、ops2、ops3。图1明晰反映了设备驱动的核心层与具体设备驱动的关系,实际上,这些分层可能只有2层(图1的a),也可能是多层的(图1的b)信盈达嵌入式要领吧五六体悟四五吧。
这样的分层化设计在Linux的input、RTC、MTD、I2C、SPI、TTY、USB等众多设备驱动类型中屡见不鲜。
二、主机驱动和外设驱动分离思想
主机、外设驱动分离的意义
在Linux设备驱动框架的设计中,不仅有分层设计实现以外,还有分隔的思想。举一个简单的事例,假定我们要通过SPI总线访问某外设linux使用教程,在这个访问过程中,要通过操作CPUXXX上的SPI控制器的寄存器来达到访问SPI外设YYY的目的,最简单的方式是:
[cpp]viewplaincopy
1.return_typexxx_write_spi_yyy(...)
2.{
3.xxx_write_spi_host_ctrl_reg(ctrl);
4.xxx_write_spi_host_data_reg(buf);
5.while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
6....
7.}
若果根据这些方法来设计驱动,结果是对于任何一个SPI外设来讲,它的驱动代码都是CPU相关的。也就是说,其实用在CPUXXX上的时侯,它访问XXX的SPI主机控制寄存器,当用在XXX1的时侯,它访问XXX1的SPI主机控制寄存器:
[cpp]viewplaincopy
1.return_typexxx1_write_spi_yyy(...)
2.{
3.xxx1_write_spi_host_ctrl_reg(ctrl);
4.xxx1_write_spi_host_data_reg(buf);
5.while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
6....
7.}
这其实是不能接受的,由于这意味着外设YYY用在不同的CPUXXX和XXX1上的时侯须要不同的驱动。这么,我们可以用如图的思想对主机控制器驱动和外设驱动进行分离。这样的结构是,外设a、b、c的驱动与主机控制器A、B、C的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用的API进行数据传输,主机和外设之间可以进行任意的组合。
假如我们不进行上图的主机和外设分离,外设a、b、c和主机A、B、C进行组合的时侯,须要9个不同的驱动。构想一共有m个主机控制器,n个外设,分离的结果是须要m+n个驱动,不分离则须要m*n个驱动。
LinuxSPI、I2C、USB、ASoC(ALSASoC)等子系统都典型地借助了这些分离的设计思想。