LonWorks节点驱动程序编写简介

(海思LonWorks技术粉丝供稿)

引言

   在现场总线产品开发及系统建设中,各类新设备及新的接口规范等,使得操作系统的设备驱动程序的开发工作层出不穷。在基于嵌入现场总线控制器的开发中,将遇到LonWorks设备的驱动程序问题。对驱动程序实现机制进行研究,对开发LonWorks现场总线设备的驱动程序十分必要。

一、LonWorks技术简介

    现场总线是一类工业数据总线,是连接智能现场设备和自动化系统的高可靠的数字式、双向传输的通信技术,可方便地构成全数字化的分布式现场控制网络。在各种现场总线中,LonWorks总线技术以其在技术先进性、可靠性、开放性、拓扑结构灵活性等方面独特的优势,为分布式监控系统提供了理想的实现手段。特别适合于建筑的楼宇自动化系统。

    LON网络接口卡是上位机与LonWorks网络的接口适配器,使上位机能够完成与LonWorks节点之间的数据通信。

(一) LonWorks网卡的硬件构成

了解LonWorks网卡的工作原理,对编写驱动程序是必要的。

1LonWorks网卡的硬件原理框图。

    在LonWorks网卡的设计中,使用可编程逻辑阵列(CPLD)来实现与ISA总线的接口逻辑,只用一个芯片就完成了所有功能,大大简化了网卡的电路。

(二)LonWorks网卡的工作原理

    计算机与微控制器之间数据交互的流程图如图23所示,完成计算机与微控制器之间读写数据、置标志位和清除标志位的功能。CPLD为内部实现了存储数据和标志位的寄存器。

 

 

二、LonWorks网卡设备驱动实现

Linux平台上开发和设计LonWorks网卡的软件包含应用程序和设备驱动程序两部分。本文主要讨论的是设备驱动程序部分。

Linux平台上实现对硬件的驱动支持采用了如下工作方式:使用Linux内核中提供的机制来实现。 

(一) Linux的可加载模块机制

    Linux内核提供了两种机制来开发设备驱动程序:一种是直接把驱动程序链接到内核中;另一种则是通过称为Linux可加载模块的机制来开发可动态加载和卸载的驱动模块。而第一种方式可以在后一种方式成功后,采用与内核一起提供的配置工具和接口来完成。

    Linux作为单核结构其效率比较高,但是系统灵活性不足,为了平衡这两者的关系,它提供了可动态加载机制。利用这种机制我们可以开发Linux内核模块,并且可以动态的对它加载和卸载。Linux下的设备驱动程序一般都支持这种方式,且模块被加载到内核后,它就可以任意的利用核心提供的各种资源和服务了。为了让模块利用核心提供的资源,Linux内核维护了一张所有内核资源的符号表(在接下来的部分我们称它为内核资源符号表),用于在模块载入时解决对相应资源的引用问题。并且,Linux允许模块的堆栈操作,由此一个模块可以使用其他模块所提供的资源。也就是说:一个模块对另一个模块的资源的使用与其对内核资源的使用非常相似,不同的只是这些服务的资源从属于另一个模块而已。每当一个模块被加载Linux就会有一个修改内核资源符号表的过程,将该模块所提供的服务和资源加入进去,这样另一个模块载入时,如果需要就可以引用这个模块的资源了。而卸载一个模块时,就要知道当前模块是否正在被使用。如果没有被使用,在卸载时要能够通知该模块它将被卸载,以便由它自己释放已被它占用的系统资源。然后,Linux还要从内核资源符号表中删除所有该模块提供的资源和服务。 

    从上面的原理分析可知,内核模块编写时应该具有两个主要的接口函数:init_module()用于在模块加载时由加载模块的工具调用,以便于注册一些必要的服务和申请一些资源。cleanup_module()用于在模块卸载时由删除模块的工具来调用,清除掉由init_module()所做的工作,从而使内核模块可以安全的卸载。其中对init_module()调用的一种工具是在根用户执行insmod命令来加载模块时执行。而对于cleanup_module()的调用是在根用户使用rmmod命令来卸载模块时执行。

(二) Linux下设备驱动程序

    系统调用是操作系统内核和应用程序之间的接口, .aspx" title="设备" style="text-decoration:underline;color:blue">设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,可以通过相应的系统调用象操作普通文件一样对硬件设备进行操作。

(1) Linux设备分类

    Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(network device)三种。字符设备是指存取时没有缓存的设备,如系统的串口设备/dev/cua0, /dev/cual。块设备的读写则都有缓存来支持,只能以块为单位进行读写,并且块设备必须能够随机存取(random access),即不管块处于设备的什么地方都可以对它进行读写,字符设备则没有这个要求。块设备主要包括硬盘软盘设备,CD-ROM等。网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unixsocket机制。

(2) 设备标识方式

    Linux设备由一个主设备号和一个次设备号标识。主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中相应表项的索引。次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。值得一提的是次设备号还可以被分成几个部分用来区分子设备驱动程序和具体的设备。

(3) Linux设备驱动程序组成部分

    Linux设备驱动程序可以分为三个主要组成部分:

●自动配置和初始化子程序。负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软硬件进行初始化。

●服务于I/O请求的子程序。它们主要是对file_operations结构的各个入口点的实现。这部分的实现支持了文件系统的调用(openclose,

read等等)

●中断服务子程序。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统来接收硬件中断,再由系统来调用中断服务子程序。

但是,这三个部分不是必须在每个驱动程序中必须具有的。

(三)  LonWorks网卡驱动程序 

    根据Linux的设备管理以及设备驱动程序实现方法,LonWorks节点设备驱动程序即可进行编写实现,并对实现中的一些关键问题进行探讨。

(1) LonWorks现场总线网卡驱动程序

    在驱动程序设计和开发中,一定要注意机制(Mechanism)与策略(Policy)的分离。所谓的机制是指驱动程序提供的接口应该忠实地反映设备的原始功能,而与应用无关。而策略是指一旦这个设备驱动程序为设备机制提供了相应的软件接口,应用程序开发人员就能按照特定的方式使用机制接口。可以说,在内核驱动程序开发过程中,所设计的数据结构,以及确定的接口命令都是为以后的应用策略提供的一种机制。而如前所述,这种机制在Unix类系统内部是通过一组固定的入口点来提供的。由于我们要开发的设备驱动程序是一个字符型的设备,所以接下来我们首先分析字符型设备驱动程序中常用的入口点:

● open入口点

    打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。 

release入口点

关闭一个设备。当最后一次使用设备终结后,调用release子程序。独占设备必须改变前由open子程序设置的标志,以便设备可再次被使用。

read入口点

从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。

write入口点

往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。

● ioctl入口点

执行读、写之外的一些硬件控制操作。

poll入口点

    把对许多非阻塞操作的设备描述符集合起来,等待事件的发生,以便于集中检查,看数据是否可从设备读取或设备是否可用于写数据,这样就做到了所谓的多路复用。

    以上入口点构成了设备驱动程序的三大组成部分中I/O请求的部分,在Linux中它们由file_operations结构来封装,并不是所有的字符设备驱动程序都必须提供以上每一个入口点的实现,如果设备驱动程序没有提供上述入口点中的某几个,系统会用缺省的子程序来代替。

    由上面的描述可见,在内核设备驱动程序的设计中,相应的机制的提供主要是对设备入口点的选择和设计。

    针对LonWorks网卡的特点,选择并实现了五个入口点,即open, release,readwrite, ioctl。对于openrelease入口点由于设备特点,只需要控制设备驱动模块在使用时,不被异常释放即可。接下来将描述以上设计实现中与Linux内核相关的一些调用和问题。

(2) file_operations结构的初始化file_operations结构是Linux操作系统中用于实现驱动程序的最重要的数据结构,前面提到过,它对Linux提供I/O请求的子程序的一系列入口点进行了封装。该结构贯穿在整个驱动程序中,故在文件作用域内定义了它的一个变量,并对本程序中用到的入口点做了初始化,其代码如下:

struct file_operations lmdev_fops= {

NULL

lmdev_read,

//把实现的lmdev_read函数指针赋给read入口点。 

lmdev_write

//把实现的lmdev_write函数指针赋给write入口点。

NULL

NULL

lmdev_ioctl

//把实现的lmdev_ioctl函数指针赋给ioctl入口点。

NULL

lmdev_open

//把实现的lmdev_ open函数指针赋给open入口点。

lmdev_release

//把实现的lmdev_release函数指针赋给release入口点。

NULL

NULL

NULL

NULL,

};

对于lmdev-*函数的实现方法,我们将在后面做详细的讨论。

(3) 模块初始化与模块卸载

● 9;color:blue">LonWorks网卡驱动模块初始化,通过对init_module的实现来完成以下几个任务。以字符设备类型向系统注册LonWorks现场总线设备卡,同时动态获得其设备号。通过调用下面这个函数int

register_ chrdev(unsigned int major, const char*name,struct file_operations

*fops)来实现。

    这里我们使major参数为0,这样系统就会动态的分配并返回主设备号。name参数是用于标识设备的字符串。file_operatons传入的是如前所述的lmdev_fops。然后,向系统申请LonWorks网卡的I/O端口地址。根据该卡上的跳线得到的I/O地址,调用系统提供的宏:check_region(start,n)//检查端口地址范围startstart+n-1是否可用,是则返回0,否则返回1request_region(start,n,name)//用于申请通过以上函数检查的地址范围。接下来,做一些必要的系统日志,根据各种条件用printk向系统日志缓冲区写入不同级别的信息。最后,控制对内核资源提供的符号表输出的符号信息(即在可加载模块机制部分提到的模块要注册的服务)。这里使用EX-PORT_NO_SYMBOLS使得该模块不输出任何符号信息。

LonWorks现场总线网卡模块卸载需要完成以下几个任务:

调用release_region(start,n)宏释放模块初始化时申请的I/O端口资源。

调用int unregister_chrdev(unsigned int major, const char*name);

    向系统注销该字符设备,本程序中major参数即前面注册时动态获得的主设备号,name与注册时提供的name字符串相同。调用printk函数,做一些必要的系统日志。 

(4) file operations结构中入口点的实现

openrelease入口点。

    这两个入口点在本模块中被赋予的就是前面在介绍file_operations结构时给出的lmdev_openlmdev_close函数指针,它们主要通过调用MOD_INC_USE_COUNTMOD_DEC_USE_COUNT来进行模块计数。用计数来对LonWorks现场总线设备驱动模块是否正在被使用进行控制,防止模块正在使用时被意外卸载而导致核心对设备操作出现异常。

●对read/write入口点的实现

    这个入口点在本模块中被赋予的就是前面在介绍file_operations结构时给出的lmdev_read函数指针,它是对设备操作的核心部分,根据前面描述的算法,它实现了如下几个功能:

    用inb_p宏,访问硬件的状态和数据端口,以读取相应的状态和数据信息。

    调用long_sleep_on_timeout(wait_queue_head_t *q, long timeout)函数把当前进程加入时钟等待队列q中,使它等待timeout时间。根据LonWorks现场总线卡的工作方式来看,这样做可以减少轮询时间,大大的提高了效率。

    Linux分为核心空间和用户空间,用户空间的代码不能直接访问核心空间,故需调用Linux核心提供的copy_to_user(to,from,n)宏,把数据从内核空间地址from拷贝到用户空间地址to中。这样,系统调用返回后,用户空间的代码就可以通过to指针来访问相应的数据并进行处理了。这样核心驱动模块部分的程序就完成了。

(5) 编译内核模块

    在程序完成后,用gcc编译成目标文件(不链接,生成*.o文件),要做到这一点只需在gcc命令行里加上-c参数。另外,还要加上-D_KERNEL_ -DMODULE参数。上述程序可以这么编译。

root# gcc -c -D-KERNEL_-DMODULE -Wall -02

    lmdev.c。其中参数-Wall的功能是打印附加的警告信息。由于头文件中的函数都是声明为inline的,还必须给编译器指定-O选项。gcc只有打开优化选项后才能扩展内嵌函数,不过它能同时接受-g-O选项,这样就可以调试那些内嵌函数的代码了。优化参数-O有三个级别:Ol,02, 03,它们的优化程度不同,优化效果03大于02大于Ol。编译好模块后的如何加载模块,在前面已经有所描述,这里就不再叙述了。

(四)  应用程序开发

    在对以上模块编译并加载后,Linux根据用户可用mknod命令,利用动态分配的主设备号(该设备号在用户空间可以从/proc/devices文件中用设备名获得)建立相应的设备文件,并对它设置恰当读写权限后,就可以在应用程序中,使用Linux的文件系统调用通过这个设备文件来操作LonWorks现场总线卡了。这样做不仅使得应用程序编程风格更加统一,代码更具鲁棒性,应用系统更加安全更易于维护。而且可在核心级来保证关键部分的实时响应,从而降低了用户程序开发的难度。  

(本文仅供对LonWorks技术开发有兴趣者学习、参考,不代表本网站同意其观点及方法)

返回顶部

    版权所有:南京海思自动化系统有限公司    网站地图:Sitemap        苏ICP备13027346号-1
电话:025-52804590 52804690 地址:南京市白下高新产业园区永丰大道8号B2-503室 网址: www.highsys.com.cn
版权所有:南京海思自动化系统有限公司
总机:025-52804590 / 52804690
市场部:8008分机  售后部:8006分机
手机:18905184663(市场)  邮箱:highsys@163.com
南京市白下高新产业园区永丰大道8号B2-503室