LINUX系统下GPIB驱动优化设计与实现

2020-04-07 10:15
计算机测量与控制 2020年3期
关键词:寄存器调用队列

(1.西安精密机械研究所,西安 710075; 2.陕西海泰电子有限责任公司,西安 710075)

0 引言

随着嵌入式技术快速发展,功能更加齐全,性能更加强大的台式仪器已经面世,例如LXI仪器,其数据传输吞吐率,基于时间触发,组建远程分布式测控系统都具有明显优势,但GPIB经历了几十年的发展,总线协议已经相当成熟,仍然受到传统用户的青睐,因此众多国内外测试测量研发商开发高端的台式仪器时,都会保留传统的GPIB接口。由于LINUX操作系统内核是开源的,使用者可以根据自身的需求,对其进行裁减配置,形成高效的嵌入式操作系统,使得LINUX成为当前最流行的嵌入式操作系统之一,但不足之处是,它不像WINDOWS,有微软专门的研发团队对其进行不断优化和升级,LINUX并非以商业盈利为目的,没有专门组织对其进行官方维护,从而LINUX内核在集成各个芯片厂商的驱动时,不少驱动在一些应用情况下,传输性能还不够理想,尤其像GPIB这种只是测试测量领域里的专用总线,相比应用广泛的USB、LAN,更具典型性。

为了提升LINUX系统下GPIB传输性能,文章对其驱动进行了优化。将GPIB中断服务程序分割成顶半部和底半部,在底半部里开辟了配置成非原子操作的工作队列,同时采用睡眠机制,高效的实现了驱动运行效率;在数据接收流程里,采用FIFO半满位判断标准,使得从FIFO中收数据到内核缓冲区和向FIFO里传数据可以并发进行,从而提升了数据传输速率;设备文件操作中的读写函数可能会同时操作临界资源,应用信号量避免了竞态的发生,增强了驱动可靠性。

1 LINUX字符设备驱动模型分析

LINUX字符设备驱动程序[5]一般包括3部分:初始化、中断服务、设备文件操作。在驱动程序初始化时,要向系统注册此驱动程序,系统后续才能调用驱动里各设备文件操作接口。在Linux系统里,是通过调用register_chrdev向系统注册设备驱动程序,初始化部分除了注册设备驱动程序,一般还需要给驱动程序申请系统资源,包括内存、时钟、I/O端口等,芯片的初始化也在这里进行,另外还要设置清除,它是让在初始化里注册的资源,全部从内核里注销掉。对于设备经常会提出请求给CPU,来执行设备需要完成的操作,这就需要有中断服务,驱动程序通过调用request_irq函数来申请中断,其原型:int request_irq(unsigned int irq,void(*handler)(int irq,void dev_id,struct pt_regs*regs),unsigned long flags,const char*device,void*dev_id);参数irq表示所要申请的硬件中断号,handler为向系统申请的中断服务程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断响应时信息寄存器地址;flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是影响处理速率和资源开销的中断响应方式;device为设备名,在/proc/interrupts文件里被记录,中断服务具体的内容由设备需要完成的操作来决定。设备文件操作是指file_operations结构中的成员,它们全部是函数指针,每个函数都完成一种设备操作,如读写操作,选择操作,控制操作等。

字符设备驱动程序的开发流程是:

1)查看原理图及芯片资料理解设备的工作原理;

2)定义主设备号、注册资源、注销设备号及资源;

3)设计芯片初始化流程;

4)设计中断服务;

5)定义file_operations结构,设计所要实现的文件操作;

6)编写用户态测试程序,调试设备驱动。

调试时是通过insmod和rmmod命令实现驱动的加载和卸载,加载与卸载的原理如图1所示。

图1 加载与卸载的原理

2 GPIB驱动程序优化设计

2.1 TNT4882原理

TNT4882是美国NI公司的一款高速且听讲功能兼备的GPIB(General purpose interface bus)接口专用芯片。它内部集成了Turbo488(高速传输电路)及NAT4882(IEEE488.2兼容电路),能够兼容ANSI IEEE Standard 488.1和ANSI IEEEStandard 488.2规范,其内置还具有16个增强型IEEE488兼容收发器[6]。

TNT4882 内部寄存器[7]共32个,每个寄存器的控制字都是8位,地址通常是TNT4882的基地址加上各个寄存器所对应的偏移量而确定的。在GPIB驱动设计中,只有对寄存器进行正确设置,才能实现对GPIB的各种操作。工作模式可分为单芯片模式和Turbo + 9914模式,工作模式的选择和转换,由寄存器的设置来决定。Turbo+9914相当于TMS9914A芯片的工作模式,目地是为了兼容此款芯片,但此时功能更加强大,单芯片工作模式是NI公司推荐的TNT4882工作模式,工作时的内部结构如图2所示。由于单芯片模式采用的是最简单又是最快速的结构,并且是推荐的TNT4882工作模式,因此,本驱动是在这种模式下进行设计。

图2 单芯片工作模式

2.2 初使化函数设计

在初使化函数里除了注册设备号以外,还要有以下方面要实现:寄存器硬地址映射到内存的虚拟地址;分别初始化读写互斥的信号量、中断服务程序底半部的工作队列、阻塞的等待队列;初始化芯片。

由于内核无法访问TNT4882寄存器硬地址,所以要通过映射到内存的虚拟地址,来访问TNT4882寄存器,映射的实现是通过调用内核函数ioremap()来实现,此函数第一个参数设置为TNT4882芯片的首地址,TNT4882芯片的首地址是由硬件使用的片选信号线决定,函数的返回值便是首地址在内存的映射地址,由于芯片手册给出了每个寄存器的偏移量,这样TNT4882的首地址在内存的映射地址加上偏移量就是每个寄存器在内存的地址。

信号量的本质是一个整数值,它和一对函数联合使用,这一对函数通常称为P和V,P函数使信号量的值增加,V函数使信号量的值减少。如果信号量的值大于零,则进程可以操作临界区的资源,反之,如果值小于零,则进程不能操作临界区的资源[8]。由于读写函数可能会操作同一资源,这种情况会产生竞态,严重时会导致系统内核的崩溃,所以需要通过信号量互斥,使用信号量之前要对其进行初使化,一般放在驱动初使化函数里,调用的内核函数为init_MUTEX();由于中断处理是独占CPU的,这样如果整个任务需要别的进程在中断后很短时间立刻执行,那么中断处理就不能耗时过长,所以经常把中断处理分为顶半部和底半部,顶半部只用来响应中断而底半部通过开辟个内核进程进行真正的处理,而内核进程是不独占CPU的,这样别的进程也可同时执行,在底半部开辟进程常用的机制就是工作队列,而且工作队列还允许睡眠机制,使用工作队列时同样也需进行初使化,它通过调用INIT_WORK ()完成;阻塞是实现进程睡眠的机制,进程在运行时如果需要等待一个事件来到才能继续运行,这时就需要用阻塞来使进程睡眠,由于应用层的进程需要等待数据来到才能继续处理,所以在内核的读函数里需要添加阻塞,内核给驱动提供的最简便的阻塞,就是等待队列机制,使用前一样要进行初使化,调用的函数是init_waitqueue_head()。

TNT4882芯片在上电运行前,要进行初使化,初使化的流程为:复位TNT4882芯片中的Turbo电路;将 TNT4882设置成Turbo+7210式;将TNT4882设置成单芯片模式;使Local_ PowerOn信号有效;设置TNT4882的GPIB主地址,屏蔽副地址以及设置GPIB握手参数;清除Local_ PowerOn信号后,开始GPIB操作。此过程全部是给寄存器赋值,这可通过内核iowrite8()函数进行。

2.3 中断服务优化设计

中断函数的流程如图3所示,ATN(Attention)代表注意命令,REN(Remote Enable)代表远控使能状态,LACS(Listener Active State)代表听者作用状态,TACS(Talker Active State)代表讲者作用状态。GPIB主设备发来ATN命令,TNT4882产生中断,如果GPIB接口处是远控使能和听者作用状态,那么GPIB接口就准备接收数据,如果GPIB接口处是远控使能和讲者作用状态,那么GPIB接口就可以发送数据。具体实现的方法是:利用linux字符设备驱动中断注册接口request_irq,将中断响应注册到内核里,中断号设置成硬件设计时占用中断向量表中的中断号,从参数*device得到的GPIB设备名,写入/proc/interrupts文件里,同时将中断响应属性flags配置成中断底半部独立运行,用iowrite8()将TNT4882中断使能寄存器中有关的ATN、REN、TAC、SLACS全部使能,在中断服务程序中,用ioread8()读TNT4882中断响应信息寄存器的信息,出现与LACS相同的信息,调用收操作,出现与TACS一致的信息,调用发操作。

对于TNT4882芯片,收发操作首先要对收发数据状态初始化,然后循环收发数据,最后退出收发数据状态。对于具体流程,发送数据流程,如图4所示。由于GPIB是通过接口之间的握手协议来进行通信,所以接收和发送流程的初始化和退出要完全按照协议规定来设计,他们是一致的,这样在接收数据的具体流程中,只详细分析了数据接收过程,如图5所示。TNT4882的FIFO是一个16字节深的缓冲区,TNT4882不仅提供了它的全满标志位而且还提供了半满标志位,此流程中没有使用传统的全满位作为接收数据的判断位,而是采用了FIFO半满位进行判断,这样从FIFO中收数据到内核缓冲区和向FIFO里传数据可以同时并发进行,而如果用全满位进行判断,只有先收数据后,有了空间才能向FIFO里传数据,所以采用半满位可以提高数据接收速率。

图3 中断函数的流程

图4 数据发送流程 图5 数据接收流程

在中断函数里,数据收发操作是通过调用自定义的两个函数:short Receive(void),short Send(void)来实现的。对于收是直接调用收函数,而对于发则是间接调用,通过调用工作队列来调用发函数,如果直接调用数据发送函数,那么根据中断服务程序运行是独占CPU的原理,导致用户态的应用程序只能等中断服务程序结束才能执行,这样数据还没有过来,就先执行数据发送,反复轮询,这显然是低效的,而调用工作队列,等于内核开辟了一个进程,便释放了CPU,并配置成非原子操作模式[9],这样内核态的工作队列和用户态的进程可同时运行,当在工作队列开始处再加上睡眠,数据到达内核后,唤醒工作队列,此时再调用发函数,数据进行发送,此时运行效率明显提升。

数据收发操作具体实现的方法是:在数据发操作方面,用creat_workque创建工作队列,将该创建的工作队列赋给queue_work函数第一个参数,将Send(void)函数指针赋给queue_work函数第二个参数,从而将Send(void)函数插入工作队列,并将workqueue_struct 类型的结构体变量中的属性成员配置成非原子操作,中断服务程序运行到queue_work接口处,内核将自动开辟进程执行Send(void),在开辟进程开始处,调入wait_event_interruptible()使Send(void)睡眠,在文件操作写函数结束处,调入wake_up_interruptible()来唤醒该睡眠;对于Send(void)本身,用memset函数将TNT4882的FIFO清空,并用iowrite8()在传输寄存器里配置传输参数和传输字节数,配置完成后,再用ioread8()读取传输就绪位,等待传输就绪,最后使能传输位,完成初始化;设置传输字符计数变量,读取FIFO状态寄存器,判断是否FIFO为满,不为满时往里面循环写数据,直到计数变量到达此次发送字符数,退出发送操作,注意发操作结束后,要利用的destroy_workque注销工作队列。在数据收操作方面,初始化与发操作一致,不做赘述,用ioread8()读取中断使能寄存器的听状态位,如果是听状态,并且FIFO至少半满,设置接收计数变量,开始从FIFO中用循环取数据,直至取FIFO半满的数据,然后再判断中断使能寄存器的听状态位,如果仍是听状态,重复以上操作,如果听状态结束,FIFO中仍有数据,用ioread8()将数据取回,退出收操作。

2.4 设备文件操作读写优化设计

GPIB驱动里的读写函数即read和write分别实现用户态从内核收数据和用户态向内核发数据,实际上就是在read和write里分别调用内核的copy_to_user和copy_from_user来实现的。根据在初使化里的分析,读写要进行互斥,采用的方式是信号量。 具体实现的思路是:设信号量为驱动全局变量并调用内核函数DECLARE MUTEX()使其初始值为1,在读写函数里的开始处调用down()使信号量减1,结束处调用up()使信号量加1。这样如果写进程操作了临界资源,信号量的值变为零,读进程则无法操作临界资源,当写进程结束后,信号量的值恢复为1,此时读进程可以操作临界资源,信号量的值又变为零,写进程则无法操作临界资源,当读进程结束后,信号量的值又恢复为1,此时写进程又可再次操作临界资源,这样就实现了读写互斥,消除了竞态。

除此之外,由于内核的读函数是给应用程序调用的函数接口,为了实现应用程序在数据没有到来的时候能够被挂起,内核的读函数还要实现阻塞,具体实现的思路是:在读函数里的开始处可以通过调用wait_event_interruptible()来使读函数进入睡眠状态,wait_event_interruptible()要放到down()前面,否则会产生死锁,在GPIB接口处听者作用状态结束时,通过wake_up_interruptible()来唤醒睡眠状态,这样就实现了读阻塞。

3 测试验证

将仪器与计算机通过GPIB电缆相连,将mknod建立设备结点命令和insmod向内核插入驱动命令的参数均设置为优化后的GPIB驱动模块,然后将以上命令编写成脚本,在LINUX终端下运行此脚本,GPIB驱动便加载到LINUX内核,并运行测试程序,因为在GPIB驱动的read函数里用了阻塞机制,所以在数据没有到来之前,程序在阻塞状态。从图6可以看到GPIB设备已打开,驱动被成功加载到内核。

图6 优化后的GPIB驱动被成功加载到内核

接着在计算机上运行GPIB管理器软件,*IDN?[10]命令随软件启动自动发送,枚举当前在线仪器,图7展示了仪器的信息成功返回;最后再对GPIB驱动进行命令快速连续发送测试,如图8所示。

图7 仪器信息成功返回

图8 命令快速连续发送测试

从以上测试步骤可以看到,优化后的GPIB驱动加载到LINUX内核,系统运行正常,说明与系统兼容性良好,利用GPIB管理器软件对在线GPIB仪器进行枚举,设备被正常识别,说明驱动通信正常,以上两方面的测试,反映了优化后的GPIB驱动并没有影响驱动原有的正常运行和功能,最后再针对向仪器发命令的频率比较高时,GPIB驱动传输性能不是很理想的问题,进行命令快速连续发送测试,所有命令返回值均立即并正确返回,GPIB管理器软件并无警告或异常报错提示,这表明优化后的GPIB驱动,使此问题得到了有效的改善。

4 结束语

本文主要从中断服务程序和设备文件操作读写函数对GPIB驱动进行了优化设计。在中断服务程序底半部里,利用非原子操作工作队列,实现了内核态的进程和用户态的进程并行,并结合了睡眠机制,从而提高了驱动运行效率;受并行工作思想的启发,提出了FIFO半满位作为接收数据流程中的判断标准,实现了FIFO中收数据到内核缓冲区和向FIFO里传数据可以并发进行,对加快数据传输速率起到了较好的效果;在读写函数里,应用了信号量,避免了读写可能同时操作临界资源的隐患,对驱动的可靠运行进行了加固。在对驱动运行效率,传输速率,可靠性几方面的优化后,GPIB驱动传输性能得到了一定的提升。

猜你喜欢
寄存器调用队列
基于车车通讯的队列自动跟驰横向耦合模型
队列队形体育教案
队列里的小秘密
飞思卡尔单片机脉宽调制模块用法研究
移位寄存器及算术运算应用
基于Android Broadcast的短信安全监听系统的设计和实现
数字电路环境下汽车控制电路信号设计
青春的头屑
利用RFC技术实现SAP系统接口通信
C++语言中函数参数传递方式剖析