微波EDA网,见证研发工程师的成长!
首页 > 应用设计 > 消费类电子 > LonWorks现场总线设备驱动设计与实现

LonWorks现场总线设备驱动设计与实现

时间:08-21 来源:嵌入式在线 点击:

3.2 Linux下设备驱动程序

  系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,可以通过相应的系统调用象操作普通文件一样对硬件设备进行操作。

  (1) Linux设备分类

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

  (2) 设备标识方式

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

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

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

  ●自动配置和初始化子程序。负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软硬件进行初始化。
  ● 服务于I/O请求的子程序。它们主要是对file_operations结构的各个入口点的实现。这部分的实现支持了文件系统的调用(如open,close, read等等)。
  ●中断服务子程序。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统来接收硬件中断,再由系统来调用中断服务子程序。

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

  3.3 LonWorks现场总线网卡驱动程序

  研究了Linux的设备管理以及设备驱动程序实现方法后,我们来设计LonWorks现场总线设备驱动程序,并对实现中的一些关键问题进行探讨。

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

  在驱动程序设计和开发中,我们一定要注意的问题是机制(Mechanism)与策略(Policy)的分离。这里所谓的机制是指我们的驱动程序提供的接口应该很忠实地反映设备的原始功能(bare function),而与应用无关。而策略是指一旦这个设备驱动程序为设备机制提供了相应的软件接口,那么应用程序开发人员就能按照特定的方式使用机制接口。可以说,在内核驱动程序开发过程中,所设计的数据结构,以及确定的接口命令都是为以后的应用策略提供的一种机制。而如前所述,这种机制在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, read,write, ioctl。

  对于open和release入口点由于设备特点,我们只需要控制设备驱动模块在使用时,不被异常释放即可。

  接下来,我们将描述以上设计实现中与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) 模块初始化与模块卸载

  ●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)//检查端口地址范围start到start+n-1是否可用,是则返回0,否则返回1。request_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结构中入口点的实现

  ●open和release入口点。这两个入口点在本模块中被赋予的就是前面在介绍file_operations结构时给出的lmdev_open和lmdev_ close函数指针,它们主要通过调用MOD_INC_USE_COUNT及MOD_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。

  编译好模块后的如何加载模块,在前面已经有所描述,这里就不再叙述了。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top