驱动程序10篇

驱动程序篇1

由于工作关系,我经常涉及PC机与设备接口的工作,从PC机这方面要做的工作看来,主要是通过接口处理设备的中断,通过I/O端口或内存地址与外设互相传递数据。从计算机原理的角度看,所要达到的目的很简单,那么如何编写程序完成上述功能呢?

目前国内流行的PC操作系统有三种:DOS,Win95/98系列,WindowsNT。DOS是单用户、单任务操作系统,由于PC机硬件处理速度不断提高,基于单用户、单任务的操作系统越来越不能充分发挥硬件的功能,现在只应用于一些老式PC及其它个别场合,有逐渐被淘汰的趋势;Win95/98系列和WindowsNT属于多任务操作系统,不论从其原理还是界面上看,这两种操作系统都比DOS有着无可比拟的优越性,这两种操作系统虽然在界面和操作上及其相似,但其内部实现的诸多方面有许多区别,有些区别是本质上的。Win95/98设计目标是针对一般家庭用户,安全性及可靠性存在许多薄弱环节,就可靠性而言,Win95/98系列不能很好的防止多任务环境中某个进程的非法操作导致系统中其它程序甚至整个系统的崩溃,而WindowsNT在这方面及其它诸多方面设计的相当严谨。这两种操作系统是Microsoft公司同一时期的产品,但针对不同的使用群,所以在一些重要场合及生产实践中应该选择WindowsNT作为计算机的操作系统,此外,从发展趋势来看,WindowsNT已经成为定型产品,具有相对稳定性。

在不同操作系统下编写驱动程序是有很大区别的,在DOS平台上,应用程序和设备驱动程序之间没有标准的接口,它们在外部表现为一个扩展名为EXE的文件,驱动程序的作用被柔和在应用程序中,这样,应用程序为了使用不同厂商的同一类设备,必须了解这些设备在接口上具体的硬件实现,同时,对于一个特定型号的硬件产品,所有支持它的应用软件中对于控制整个设备动作的这部分代码,可能被多次重写。这种情况不适应硬件及应用软件的飞速发展。Windows系统在这方面,进行了根本性改进,把控制设备动作的这部分代码***出来,提出了设备驱动程序的概念,驱动程序是应用程序和硬件设备之间的一个桥梁,应用程序与驱动程序之间有明确的接口,应用程序通过与驱动程序交换信息,达到控制外设的目的。接口定义的操作是面向设备的,这就是说,在应用程序的设计中,并不用关心对外设操作的具体硬件实现,只是对驱动程序发出一系列指令既可;驱动程序接受来自上层应用程序的指示,具体操纵实际硬件,完成用户功能。具体实现上,Win95/98系列与WindowsNT又有所区别,WindowsNT是严格按照上述思路设计的;而Win95/98系列不那么严格,其支持上述思路,但同时应用程序也可以绕过驱动程序直接访问实际物理I/O,这样做,增加程序设计的灵活性,但同时,对系统可靠性造成一定隐患。这也正是Win95/98系列可靠性低于WinNT的原因之一。

WindowsNT设备驱动程序的组成原理

WindowsNT操作系统结构分为用户模式和内核模式,用户模式下的编程为应用程序的设计,而开发设备驱动程序,则属于内核模式下的编程,内核模式组件包括NTExecutive(ExXxx),内核(KeXxx),硬件抽象层(HalXxx)。其层次如***2-1所示,其中NTExecutive包括几个***的软件组件,它们是系统服务接口(ZwXxx),对象管理器(ObXxx),配置管理器,进程管理器(PsXxx),安全监视器(SeXxx),虚拟空间管理器(MemXxx),本地进程调用,I/O管理器(IoXxx)。内核模式的系统服务并不是全部公开的,而是提供了一系列开发设备驱动程序需要的函数(上文括号内为函数形式,函数手册参见[2]Kernel-ModeDrivers-Reference章节),换言之,这些函数功能是所有内核模式的系统服务功能的子集。

驱动程序由一系列相对***的函数组成,由I/O管理器根据需要调用这些函数,对于一个需要处理中断的最简单的驱动程序也需要由以下几个函数构成:

1.DriverEntry()运行于PASSIVE_LEVEL

驱动程序入口点,当驱动程序被手动或自动装入系统后,驱动程序从这点开始执行,主要用于定位硬件资源,建立指向其它驱动程序函数的指针等其它初始化工作。

2.XxUnload()运行于PASSIVE_LEVEL

用于驱动程序从系统卸出之前,释放由驱动程序占用的所有系统资源。

3.XxIsr()运行于DIRQL

中断服务程序。

4.XxDpcForIsr()运行于DISPATCH_LEVEL

中断服务程序后处理程序,以排队方执行不太关键代码的执行,由于排队机制及优先级,不会造成代码拥塞从而提高中断服务程序的响应并且提高系统总体I/O吞吐率。

5.XxOpen()运行于PASSIVE_LEVEL

处理应用程序Win32函数CreateFile()请求。

6.XxClose()运行于PASSIVE_LEVEL

处理应用程序Win32函数CloseHandle()请求。

7.XxDispatch()运行于PASSIVE_LEVEL

处理应用程序Win32函数DeviceIoControl()请求,通过一系列自定义命令,驱动程序与应用程序交换特定的信息。

WindowsNT使用一个抽象化的CPU优先级方案,IRQL代表中断请求级,任一时刻CPU总处在某一级上,这个数越大,表示当前的任务重要性越大,如表2-1所示,从上至下IRQL越来越小。所有上述驱动程序的函数及内核模式函数都必须运行于各自的IRQL级上,如果违反这一调用规定,会造成系统崩溃。例如,中断服务程序(XxIsr)运行于DIRQL及上,那幺在编写中断服务程序时,只能调用允许在这一级运行的内核模式函数(并不是所有内核模式函数都能运行于DIRQL级)。至于每个内核模式函数运行级别的说明,详见[2]Kernel-ModeDrivers-Reference章节。

WindowsNT是一多任务系统,许多设备的驱动程序同时存在系统中,这样各个设备所占用的资源(中断,I/O及RAM地址空间)很有可能冲突,如果设备驱动程序在运行之前不进行‘探测’而使用自己硬件设备的资源,有可能和系统内其它设备占用的资源冲突,后果不堪设想。WindowsNT通过注册表管理硬件资源的占用信息,作为内核模式信任的组件,驱动程序使用硬件资源之前必须遵循‘查询-申请-使用-释放’的原则

WindowsNT设备驱动程序的编写步骤与实例

现以一实际例子简要说明设备驱动程序的开发步骤,本例以CINRAD天气雷达测试卡实际应用为原型,加以简化、抽象。

第一步,了解被控设备的接口情况。

本例为一ISA卡,占用PC机9号中断,I/O地址360H及RAM地址D0228H分别一个字空间。

第二步,确定驱动程序的功能。

驱动程序每当9号中断达到时,检查运行标志变量RunFlag(为一BOOL变量),如果等于TRUE,中断累积计数器counter(为一unsignedshort变量)增一,把这个值写入RAM地址D0228H,再从这个地址读出,如果读出值等于写入值,把这个值写入I/O地址360H,这个地址的内容会驱动板卡上的LED显示,把写入值显示出来;如果读出值不等于写入值,设置运行标志变量FALSE。如果运行标志变量等于FALSE,什幺也不做,返回。

第三步,定义驱动程序与应用程序的软件接口。

本例定义两个接口命令:

IOCTL_IOCardA_START:应用程序设置驱动程序内部的运行标志变量等于TRUE。

IOCTL_IOCardA_READ:应用程序查询驱动程序内部的中断累积计数器的值。

第四步,画流程***。这里列举本例实现的几个主要流程***,(***略)。

系统传给驱动程序入口函数系统定义的‘设备驱动对象’DrObj,通过初始化这个对象的一些成员变量,把驱动程序其它函数与这个对象联系起来。

ISA卡为非即插即用设备,事先把资源占用信息手工添加注册表如下:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IOCardA\parameters]

"IRQ"=dword:00000009

"IOSPAN"=dword:00000004

"IOAdd"=dword:00000360

"RAMAdd"=dword:000d0228

"RAMSPAN"=dword:00000002

其中IOCardA以下各子键及其值为自定义,设备驱动程序利用相应函数检索出这些值。

(3)每个设备驱动程序可以创建若干系统定义的‘设备对象’,本例根据需要只创建了一个‘设备对象’Dev。‘设备对象’其中一个成员变量为指向一非分页的物理内存块DeviceExtension,这块内存大小及内容为用户自定义,由于Dev或DeviceExtension对象会被系统传给驱动程序的其它函数,这样驱动程序各函数通过访问这块内存区,实际上达到互相传递信息的功能。本例在这里存储设备硬件资源信息及RunFlag和中断计数器counter,这些数值在DriverEntry()初始化后,供驱动程序的其它函数使用。

***3-2为中断服务程序IOCardAIsr()流程***。操作系统接受中断,连同DeviceExtension等参数传给中断服务程序,中断服务程序利用这些参数,实现要求功能。

***3-3为IOCardADispatch()流程***,这个函数用于处理来自上层应用程序的命令。上层应用程序通过以下程序段设置驱动程序中RunFlag值为TRUE,从而启动中断服务程序开始计数。

BOOLcmd=TRUE;

hTest=CreateFile(...);//打开设备

DeviceIoControl(hTest,//设备句柄

IOCTL_IOCardA_START,//命令

&cmd,sizeof(BOOL),//输入缓冲区地址及大小

NULL,0,&c,NULL);

CloseHandle(hTest);//关闭设备

上层应用程序通过以下程序段查询当前的中断计数器的值并存于变量w中。

unsignedshortw;

hTest=CreateFile(...);

DeviceIoControl(hTest,

IOCTL_IOCardA_READ,//命令

NULL,0,

&w,sizeof(unsignedshort),//输出缓冲区地址及大小

&c,NULL);

CloseHandle(hTest);

其中DeviceIoControl()执行后,操作系统调用IOCardADispatch()函数,如流程***所示,这个函数内部通过一个开关语句,根据命令执行相应的分支。驱动程序与应用程序通过此函数接换数据时,操作系统提供4种可选数据缓冲方式,本例由于数据I/O量比较小,故选用‘缓冲I/O’(METHOD_BUFFERED)。过程是,I/O管理器首先分配一个非分页池,它的大小为调用者输入缓冲区和输出缓冲区的较大者,第一段程序为sizeof(BOOL),第二段程序为sizeof(unsignedshort),它的地址存到IRP(I/O请求包)的AssociatedIrp.SystemBuffer域中,然后把输入数据拷贝到这个池中,在第一段程序中cmd的值TRUE被拷贝到池中,这样驱动程序通过RtlCopyBytes()函数再把池中的值拷贝到驱动程序的RunFlag中。IOCardADispatch()函数执行完,I/O管理器把池中的内容拷贝到调用者的输出缓冲区,在第二段程序中,驱动程序通过RtlCopyBytes()函数把counter的值拷贝到池中,从而最终传递到应用程序变量w中。

第五步,编程。在编写设备驱动程序的同时,要编写一个简单的应用程序用于测试设备驱动程序的一些功能。

第六步,驱动程序的载入。

驱动程序C语言源程序经过编译、连接生成扩展名为SYS的文件,本例为IOCardA.sys,把这个文件拷贝到\WINNT\system32\drivers\系统目录下,同时手工添加如下信息到注册表:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IOCardA]

"ErrorControl"=dword:00000001

"Start"=dword:00000003

"Type"=dword:00000001

要保证IOCardA子键名与驱动程序文件名一致,其中Type=1表示此驱动程序为内核模式驱动程序,Start=3表示此驱动程序手动载入,ErrorControl=1表示当驱动程序发生错误时,日志记录错误并显示一个消息框。这样当Windows重新启动后,通过使用控制面板中的Device小应用程序,从列表中找到IOCardA设备名,按Start按钮,于是,设备驱动程序就驻留内存并在底层开始工作了。

第七,调试。设备驱动程序没有界面,完全在系统底层运行,为了观察驱动程序的运行状态,WindowsNTDDK提供windbj.exe程序用于设备驱动程序的调试,调试设备驱动程序需要两台CPU体系结构完全相同的计算机,一台为‘宿主机’,运行windbj.exe程序,另一台为‘目标机’,运行设备驱动程序,两台计算机用串口线连好,进行一系列软件设置(参见[1]第17章),就可以开始调试了,从‘宿主机’可以控制及观察‘目标机’上驱动程序的运行情况。最常用的调试手段是在驱动程序中必要的位置加入DbgPrint()函数,这个函数可以把指定信息输出到‘宿主机’windbg.exe窗口中,通过分析这些信息,可以了解驱动程序当前的运行情况。

结束语

WindowsNT是一个复杂而严密的系统,驱动程序的开发不可避免的涉及现代操作系统理论及其它许多计算机理论,内涵相当广泛,本文围绕着开发实践从一定深度探讨了WindowsNT设备驱动程序开发最基本的知识及一般方法,希望对读者有所帮助,对于复杂,特殊应用的实现及编程技巧,有待于读者在各自实际应用中不断探索。

参考文献

驱动程序篇2

论文摘要:在目前流行的Windows操作系统中,设备驱动程序是操纵硬件的最底层软件接口。为了共享在设备驱动程序设计过程中的经验,给出设备驱动程序通知应用程序的5种方法,详细说明每种方法的原理和实现过程,希望能够给设备驱动程序的设计者提供一些帮助。

为了保证操作系统的安全性和稳定性以及应用程序的可移植性,Windows操作系统不允许应用程序直接访问系统的硬件资源,而是必须借助于相应的设备驱动程序。设备驱动程序可以直接操作硬件,如果应用程序和设备驱动程序之间实现了双向通信,也就达到了应用程序控制底层硬件设备的目的。它们之间的通信包括两个方面:一方面是应用程序传送给设备驱动程序的数据;另一方面是设备驱动程序发送给应用程序的消息。前者的实现较容易,通过CreateFile()函数获取设备驱动程序的句柄后,就可以使用Win32函数,如DeviceIoControl()、ReadFile()或WriteFile()等实现应用程序与设备驱动程序之间的通信。后者的实现远比前者复杂,同时介绍这方面情况的文章较少。这不等于说它不重要,相反,它在有些应用场合发挥着重要的作用。设备驱动程序完成数据的采集工作后,需要马上通知应用程序,以便应用程序能够及时将数据取走并进行处理。诸如此类情况,不一而足。

鉴于设备驱动程序通知应用程序的重要性,本人结合一些经验,对它进行了总结,归纳出5种方法:异步过程调用(APC)、事件方式(VxD)、消息方式、异步I/O方式和事件方式(WDM)。下面分别说明这几种方式的原理,并给出实现的部分源代码。

1 异步过程调用(APC)

Win32应用程序使用CreateFile()函数动态加载设备驱动程序,然后定义一个回调函数backFunc(),并且将回调函数的地址&backFunc()作为参数,通过DeviceIoControl()传送给设备驱动程序。设备驱动程序获得回调函数的地址后,将它保存在一个全局变量(如callback)中,同时调用Get_Cur_Thread_Handle()函数获取它的应用程序线程的句柄,并且将该句柄保存在一个全局变量(如appthread)中。当条件成熟时,设备驱动程序调用_VWIN32_QueueUserApc()函数,向Win32应用程序发送消息。这个函数带有三个参数:第一个参数为回调函数的地址(已经注册);第二个参数为传递给回调函数的消息;第三个参数为调用者的线程句柄(已经注册)。Win32应用程序收到消息后,自动调用回调函数(实际是由设备驱动程序调用)。回调函数的输入参数是由设备驱动程序填入的,回调函数在这里主要是对消息进行处理。

2 事件方式(VxD)

首先,Win32应用程序创建一个事件的句柄,称其为Ring3句柄。由于虚拟设备驱动程序使用事件的Ring0句柄,因此,需要创建Ring0句柄。用LoadLibrary()函数加载未公开的动态链接库Kernel32.dll,获得动态链接库的句柄。然后,调用GetProcAddress(), 找到函数OpenVxDHandle()在动态链接库中的位置。接着,用OpenVxDHandle()函数将Ring3事件句柄转化为Ring0事件句柄。Win32应用程序用CreateFile()函数加载设备驱动程序。如果加载成功,则调用DeviceIoControl()函数将Ring0事件句柄传给VxD;同时,创建一个辅助线程等待信号变成有信号状态,本身则可去干其它的事情。当条件成熟时,VxD置Ring0事件为有信号状态(调用_VWIN32_SetWin32Event()函数),这马上触发对应的Ring3事件为有信号状态。一旦Ring3事件句柄为有信号状态,Win32应用程序的辅助线程就对这个消息进行相应的处理。

3 消息方式

Win32应用程序调用CreateFile()函数动态加载虚拟设备驱动程序。加载成功后,通过调用DeviceIoControl()函数将窗体句柄传送给VxD,VxD利用这个句柄向窗体发消息。当条件满足时,VxD调用SHELL_PostMessage()函数向Win32应用程序发送消息。要让该函数使用成功,必须用#define来自定义一个消息,并且也要照样在应用程序中定义它;还要在消息循环中使用ON_MESSAGE()来定义消息对应的消息处理函数,以便消息产生时,能够调用消息处理函数。SHELL_PostMessage()函数的第一个参数为Win32窗体句柄,第二个参数为消息ID号,第三、四个参数为发送给消息处理函数的参数,第五、六个参数为回调函数和传给它的参数。Win32应用程序收到消息后,对消息进行处理。

4 异步I/O方式

Win32应用程序首先调用CreateFile()函数加载设备驱动程序。在调用该函数时,将倒数第2个参数设置为FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,表示以后可以对文件进行重叠I/O操作。当设备驱动程序文件创建成功后,创建一个初始态为无信号、需要手动复位的事件,并且将这个事件传给类型为OVERLAPPED的数据结构(如Overlapped)。然后,将Overlapped作为一个参数,传给DeviceIoControl()函数。设备驱动程序把这个I/O请求包(IRP)设置为挂起状态,并且设置一个取消例程。如果当前IRP队列为空,则将这个IRP传送给StartIo()例程;否则,将它放到IRP队列中。设备驱动程序做完这些工作后,结束这个DeviceIoControl()的处理,于是Win32应用程序可能不等待IRP处理完,就从DeviceIoControl()的调用中返回。通过判断返回值,得到IRP的处理情况。如果当前IRP处于挂起状态,则主程序先做一些其它的工作,然后调用WaitForSingleObject()或WaitForMultipleObject()函数等待Overlapped中的事件成为有信号状态。设备驱动程序在适当的时候处理排队的IRP,处理完成后,调用IoCompleteRequest()函数。该函数将Overlapped中的事件设置为有信号状态。Win32应用程序对这个事件马上进行响应,退出等待状态,并且将事件复位为无信号状态,然后调用GetOverlappedResult()

函数获取IRP的处理结果。

5 事件方式(WDM)

Win32应用程序首先创建一个事件,然后将该事件句柄传给设备驱动程序,接着创建一个辅助线程,等待事件的有信号状态,自己则接着干其它事情。设备驱动程序获得该事件的句柄后,将它转换成能够使用的事件指针,并且把它寄存起来,以便后面使用。当条件具备后,设备驱动程序将事件设置为有信号状态,这样应用程序的辅助线程马上知道这个消息,于是进行相应的处理。当设备驱动程序不再使用这个事件时,应该解除该事件的指针。

6 结语

在目前流行的Windows操作系统中,设备驱动程序是操纵硬件的最底层软件接口。它向上提供与硬件无关的用户接口,向下直接进行I/O、硬件中断、DMA和内存访问等操作。它将应用程序与硬件细节屏蔽开来,使软件不依赖于硬件并且可在多个不同的平台之间移植。本文介绍了5种设备驱动程序通知应用程序的方法,其中前3种方法主要用于VxD中,后2种方法主要用于WDM。这5种方法都经过实际测试。测试结果表明,它们都能够达到设备驱动程序通知应用程序的目的。

参考文献

[1]欧青立,徐建波,李方敏,等. 虚拟设备驱动程序VxD的研究与开发. 计算机工程,2003

驱动程序篇3

关键词:程序设计教学 分组 任务驱动程序设计课程要求学生具有较强的逻辑思维和细心制作的能力,计算机真实地检验学生的程序是否做得出来而且有无错漏,这些要求使许多初学程序设计的学生有畏难情绪;传统的大班式教学从讲理论到上机编程验证程序的教学方法,往往使学生所做的程序达不到学习内容要求的结果,学生编程练习错漏多,学习效果参差不齐,制作软件的创造能力得不到提升,为此我们上程序设计课采用任务驱动教学法。

“任务驱动”是一种建立在建构主义学习理论基础上的教学方法,它将再现式教学转变为探究式学习,学生的学习活动必须与大的任务或问题相结合,以探索问题来引动和维持学习者学习的兴趣和动机,教师创建真实的教学环境,让学生带着真实的任务学习;在任务驱动教学法中学生必须拥有学习的主动权,教师则不断地挑战和激励学生学习取得进步。

在程序设计课程的教学中,教师采用任务驱动教学法大多是把任务布置给学生并作简单讲解后,由学生自己看书学习完成任务,学生的创新能力和***分析问题、解决问题的能力明显提高,但这种教学法也有缺陷,主要是学生自学太多,总体学习效率较低,学生各自为证统一性差,而且没有改变不同层次学生学习的问题,鉴于这些原因,我们可把“任务驱动”和“分组”两种教学法结合起来应用,教学效果会更好。

一、分组

1、分组前的工作

分组前要对全班各个学生的学习能力和知识基础有全面深入的了解,要发现和培养学生中的学习带头人,这个过程往往要占去全部课程学习时间的1/3左右,在分组前的教学可用不分组的任务驱动教学法或讲解——上机——点评归纳的教学法,边教学边了解学生,同时教学的进行使学生打下一定的理论基础和制作技能。

2、怎样分组

一个班级有几十个学生,可分为若干个学习小组,但分组数不宜超过5组,组数太多使每组人数太少不便于对学生作统一要求,教师兼顾太多组亦会顾此失彼;每个小组要确立一至二名学习带头人作为学习小组长,同时兼顾学生间的友情和合作,方便互相学习,好中差三类学生适当搭配完成分组。

二、任务驱动下的分组学习

1、设置任务

教师精心设置任务是教学成功的第一步,如果要学习的新知识点多,我们可把教材中的每一章设计成一个大任务,再将大任务分为若干小任务,每一节又由一个或几个小任务组成;在教材和各种参考资料中会有许多练习题,任务驱动学习要求精选习题配合新知识点学习,而不是做大量习题的“题海战术”;如果已经把大多数的新知识点学完,我们就要设置综合程序设计任务,培养学生综合运用知识技能进行程序开发设计的能力。

对于同一章节的学习或同一部分内容所设置的任务,任务可准备多个,便于把任务分配到各个学习小组使用。

2、任务的分配

在分配任务到各个小组之前,教师应招集各个小组长进行研讨学习,小组长在教师的提点下提出自己学习并完成任务的思路,教师甚至可以让小组长先行去完成任务,使小组长能更好地带领全组同学完成任务。当这些准备工作做好后,就可以把任务分发到各个小组的学生中去,要让每个学生都明确完成任务要用到的新知识点和任务要求所要达到的目的,使他们有的放矢去学习并完成任务。各个小组间的任务可以相同,也可以不同。如果任务相同,在学生学习完成同一任务的过程中,教师把各个小组完成任务的优劣进行对比讲评,在各个小组之间形成竞争,提高学习效率,同时使对全班学生的统一要求达到更好的效果。如果各个小组的任务不同,可以使全班分小组学习应用不同的知识点,各个小组完成任务后进行小组之间的交叉对比学习,最终使各个小组都能完成多个任务,全班学生接触到更广阔的知识面,取得更高的学习效率。

3、任务的完成

完成任务的过程就是学生学习运用知识点进行程序设计的过程,在这个过程中学生是学习的主角,因为教师在之前已经培训了各个小组长,他们在各个小组学生学习完成任务的过程中要起到以点带面的作用,其实由已经学会了的学生去教那些正在学习的学生会有不错的教学效果。教师在这个过程中要做的就是督促和鼓励学生去完成任务,当然,对于学生的典型问题教师可以分小组或在全班进行讲解,保证不会因为“卡壳”造成学习效率的降低。

当各个小组的任务完成到一定程度时,教师要及时调用学生的程序,在全班进行演示点评,造成班级中各个小组之间一种竞争为了更好地完成任务的态势,同时也是小组之间相互借鉴学习的机会。通过这样的学习并完成任务的过程,各个学生按任务要求所做的作品既有个人的创造性,又能达到任务要求的统一标准,学生作品的水平普遍比其它教学方法要高得多。

4、任务的总结和测试

驱动程序篇4

windows nt是一个功能全面的操作系统,具有完全集成式的连网能力,它的网络模型开始于mac子层,网络接口卡(network interface card以后简称网卡或nic)驱动程序驻留在其中。通过相关的网卡把windows nt与网络连接起来,但一直到80年代后期,许多传输协议的实现受限于mac层接口的独特实现,因为mac层定义了协议与网卡之间的转换机制。

1989年,microsoft和3com两公司提出了一个定义mac层与osi模型高层协议驱动程序之间的网络设备接口规范(network device interface specification : ndis),ndis给数据交换提出了一个灵活的环境,它规范了软件接口──称为ndis接口,传输协议可用它与网卡驱动程序进行通信。因此在windows nt环境下开发核心态网卡驱动程序应遵循ndis规范。

对于高速网络fddi(fiber distributed data interface)网卡驱动程序还需要smt(station management)站管理功能的实现,否则将不能作为一个fddi站连入环结构中,只能实现点到点间的数据通信。故有必要将smt软件移植到网卡驱动程序中,这将又导致对miniport nic驱动程序编程框架的破坏,于是有必要形成fddi网卡驱动程序(包含smt)与windows nt操作系统的良好接口──由逻辑网卡的注册和mac层驱动程序的初始化来完成。

所以,本课题旨在深入研究应用microsoft公司的ddk(device driver kit)将smt移植于windows nt的fddi网卡驱动程序过程中如何注册miniport nic驱动程序。即怎样正确注册逻辑网卡和mac驱动程序的初始化。着重讨论与初始化相关的上边缘函数的使用和调用关系以及初始化过程中遇到的各种问题的具体解决。

第一章windows nt环境下fddi网卡驱动程序

总体结构介绍

第一节windows nt网络结构

§1.1.1 windows nt网络体系结构

windows nt的网络体系结构是基于国际标准化(iso)制定的标准模型──开放式系统互连(open system interconnection:osi)参考模型分层建立的,这种方式有利于随时扩展其它功能和服务。

windows nt网络模型开始于mac子层,网卡驱动程序就驻留在其中。它通过相关的网卡把windows nt与网络连接起来,***中的多个网卡表明在一台运行windows nt的计算机上能使用多种网卡。

这一网络体系结构包括两个重要接口──ndis接口与传输驱动

程序接口(tdi)。这两个接口把两个层隔离开来,办法是相邻的部件只允许按单一的标准来写,不允许多重标准。例如一个网卡驱动程序(在ndis接口的下面)就不需要特地按每个传输协议来写它的代码块,恰恰相反,该驱动程序是写给ndis接口的,它通过符合ndis的相应传输协议来请求服务。这些接口包含在windows nt的网络体系结构中,以容纳可移植、可互换的模块。

在两个接口之间,是传输协议。它在网络中起着组织者的作用。一个传输协议规定了数据以何种方式呈递给下一个接收层,以及如何对数据相应地进行打包。它通过ndis把数据传给网卡驱动程序,并通过tdi把数据传给转发程序(redirector)

tdi之上是转发程序,它把本地的网络资源申请转送给网络。

为了能和其他厂商的网络互连,windows nt允许有多个转发程序。对于每一个转发程序windows nt计算机必须也有一个相应的供应者(provider)(由网络厂商提供)。多供应者路由选择程序决定适当的供应者,然后借助于供应者,对应用请求到相应的转发程序做出选择。

§1.1.2 windows nt网络驱动程序

windows nt支持两种类型的网络驱动程序

传输驱动程序

实现数据链路层中的逻辑链路控制子层协议和传输层协议。向 下与ndis接口,向上与tdi接口。

网卡驱动程序

实现对物理层的管理和数据链路层中介质访问控制子层协议,通过ndis向下管理物理网卡,向上与传输驱动程序通信。

§1.1.3 windows nt网卡驱动程序

windows nt环境下的网卡驱动程序也分为两种:

miniport网卡驱动程序:miniport驱动程序只须实现与网络硬件相关的操作(包括发送和接收)。而所有底层网卡驱动程序的通用操作(如同步),一般由ndis接口程序来实现。

full网卡驱动程序:full网卡驱动程序必须实现所有硬件相关和同步、排队等操作。例如full网卡驱动程序为了响应数据接收,需要保持本身的捆绑信息,而miniport就可以由ndis接口库来实现。

在windows nt的早期版本中,full网卡驱动程序要求开发者实现许多底层操作,来处理多处理器的核心问题以及处理器、线程的同步,这样不同的开发者在大量重复着许多相同的工作。

而miniport网卡驱动程序允许开发者仅仅写一些与网络硬件相关的代码即可,而那些通用的函数由ndis接口库来实现,这样开发出来的驱动程序减少了不必要的工作。

第二节miniport驱动程序的结构

ndis接口规范了网卡驱动程序的实现,同时也对tdi驱动程序的实现提出了一定的要求,在nt中,ndis约束下的网卡驱动程序、tdi驱动程序和系统的关系如下***所示:

***2.0 ndis约束下的网卡驱动程序、tdi驱动程序和系统的关系

miniport驱动程序包括驱动程序对象、驱动程序源代码和ndis接口库代码。windows nt ddk提供ndis.h作为miniport驱动程序的主要头文件,定义了miniport驱动程序的入口点、ndis接口库函数和通用数据结构。

上边缘函数的作用是网卡驱动与ndis接口库进行通信,而下边缘函数是tdi协议驱动程序与ndis通信的手段。

§1.2.1 miniport网卡对象

ndis用一个叫做逻辑网卡的软件对象来描述系统中的每块网卡,而逻辑网卡与windows nt设备对象的通信由i/o子系统来管理,描述网卡的设备对象包括相关的网络信息如名字、网络地址和网卡内存基地址等,它还包含与硬件相关的驱动程序状态数据(捆绑数目,捆绑句柄,包过滤数据库等)。ndis分配一个句柄到miniportinitialize这个上边缘函数的一个结构中,然后miniport网卡驱动程序将在以后提供这个句柄来给ndis调用,这个结构一直被ndis保持,并且对miniport驱动程序不透明。

当miniport网卡驱动程序初始化一块网卡时,它创立自己的内部数据结构来描述网卡,记录需要它管理的与设备相关的状态信息。当miniport网卡驱动程序调用ndismsetatttibutes或ndismsetattributesex两ndis库函数时,它传递一个句柄给这数据结构。这样,当调用miniport驱动程序入口点时,它就传递这个句柄来验证驱动程序所对应的网卡的正确性。这个数据结构为miniport网卡驱动程序所拥有并维护。

§1.2.2网络对象标识符

miniport nic驱动程序还需要维护一组对象,这些对象是系统定义的对象标识符(object idetifier:oid)来标识,以描述驱动程序的性能和当前状态信息。为查询这些信息,上层驱动程序调用ndisrequest向ndis接口库指示oid。oid表示了调用所需的信息类型,如miniport驱动程序所支持的lookahead缓冲区大小等。ndis接到上层驱动程序的查询请求,将oid传递给上边缘函数miniportqueryinformation实现对oid的查询,如果上层驱动程序请求改变状态信息则调用miniportsetinformation实现对oid的设置。

§1.2.3 miniport网卡驱动程序代码

典型的miniport nic驱动程序必须有一些函数来通过ndis接口实现上层驱动程序与硬件的通信。这些函数称为上边缘服务函数。

这些上边缘服务函数由驱动程序的开发者根据驱动程序面向的特定低层网络类型和硬件以及相应环境,可以有选择地实现,但必须保证驱动程序最基本的功能,这些基本功能包括初始化、发送、中断处理、重置、参数查询与设置和报文接收。

miniportinitialize:操作系统根据系统配置信息,检测出网卡已安装时,由ndis接口在初始化时调用,主要完成低层网络类型确定,对应于物理网卡的逻辑网卡初始化,中断信息注册,网卡与主机通讯方式的确认。i/o端口的申请与注册,内存映像,mib的初始化,物理网卡的验证与初始化等。

miniportreconfigure:支持网卡参数动态变化,和miniportinitilize一样由ndis接口以初始化级别调度执行(不能屏蔽中断,必须由驱动程序承认并清除在此期间产生的中断),支持即插即用和软配置的网卡在动态改变参数时,必须提供此函数。

miniportqueryinformation:查询网卡的状态以及网卡驱动程序的操作或统计参数,如是否支持组通讯、网卡的物理速率是否支持回环、是否支持直接拷贝等,这些参数以oid方式统一管理。

miniportsetinformation:ndis接口或协议驱动程序通过调用此接口改变驱动程序维护的oid库,一些操作参数的改变也将同时改变驱动程序状态,例如组地址的设置。

miniportreset:包括网卡硬件重置和驱动程序软件重置,软件重置包括驱动程序状态重置,以及一些相关的参数重置,还需考虑有些参数的恢复,重置时不必完成所有正在活跃的外部请求,但必须释放已占用的外部资源。

miniporthalt:挂起网卡并释放该网卡驱动程序占用的所有资源,在此期间不屏蔽中断。

miniportisr:高优先级的中断处理程序,进行的工作包括初始中断处理类型,决定是否进行中断转交,对卡上中断进行处理 等,该服务类型只在以下情况被调用:

ndis接口调用miniportinitialize和miniporthalt两函数时。

.中断处理类型设为每此中断处理过程都调用时。

为使系统能及时响应所有硬件中断,高优先级的硬件中断处理程序应尽可能的减少运行时间,防止长时间的屏蔽低优先级中断,避免造程中断丢失。

miniporthandleinterrupt:由中断延时处理程序在中断延时处理时进行调用。ndis排队所有的延时处理,该服务主要处理发送完成、报文接收、描述符用尽、溢出、网卡异常等中断。

miniportsend:ndis收到上层发送请求时经过若干协议处理再向下调用此服务过程,发送的packet已含有llc和mac头,该服务过程进行边界对齐、packet约束重整、描述符映射和报文发送、以及发送资源和packet缓冲队列管理。

miniporttransferdata:多个已和网卡捆绑的协议驱动程序在接收到报文到达指示后,向网卡驱动程序发出传送请求以拷贝各自所需的报文数据部分,网卡驱动程序根据各协议驱动程序对单个packet是否进行多次拷贝,以决定是否暂存只允许单次拷贝的packet等。

miniportcheckhandle:ndis每秒调用此服务函数一次,驱动程序发现网卡异常时报告给ndis由ndis调用miniportreset进行硬件重恢复。

miniportenableintrrupt:中断使能。

miniportdisableinterrupt:中断屏蔽。

另外,每个网卡驱动程序必须有一个初始化入口点,由driver entry函数实现,它和系统相关,由操作系统在装入驱动程序时调用,主要完成初始化ndis wrapper,再由wrapper初始生成驱动程序管理块并完成相应各种初始化工作,登录网卡驱动程序所有上边缘服务入口点,同时写入ndis版本信息。

§1.2.4 ndis接口库

ndis接口库包括在ndis.sys中,它是一个核态函数库,有一套抽象的函数,无论协议驱动程序还是nic驱动程序都连接到这个库中,以实现上下层之间的操作。

第二章fddi网卡驱动程序的加载和运行

第一节 网卡驱动程序的安装

windows nt网卡驱动程序安装的目的是实现网卡相应硬件信息和驱动程序在windows nt注册库中的注册,使windows nt能够正确识别网卡,了解所必需的软硬件信息并能在windows nt启动时加载相应驱动程序。

网卡驱动程序安装时,首先在主群组的控制面板中选择“网络”,然后添加网卡,指定相应信息文件──oemsetup.inf的路径,以完成以下两个必要的操作:

复制驱动程序到相应的系统目录(windows nt根目录\system32\drivers\)中;

在windows nt注册库中存入相应软硬件信息。

下面主要以fddi网卡为例介绍安装驱动程序所必需的工作:

§2.1.1网卡一般硬件参数

对于fddi网卡,必须在编写其oemsetup.inf文件时确定以下硬件参数:

总线类型:pci(5)……括号中的数字5表示pci总线在ndis中的总线类型代码;

厂商代号:0x5588……系统加载时确定网卡的标记,也是编程时确定pci槽号的标识;

cfid: 0x01;

介质类型:光纤(3) ……括号中的数字表示光纤在ndis中的介质类型代码;

是否支持全双工:支持。

对于其它的硬件信息在此inf配置信息文件中可有可无,如若配置,则可在驱动程序的编写时利用这些信息,方便编程,同时有利于其它应用对其参数的确定和使用。

§2.1.2 fddi网卡加载时需在注册库登录表里做的网络配置

网卡驱动程序的安装通常将创建登录表中的四个不同子键:

software registrion键,对应于驱动程序,存在于hkey_local_machine\software\company\ productname\version中。我们的fddi网卡驱动程序所对应的是hkey_local_machine\software\net612\yhfddi\yhfddi1.0;

网卡的软件登录键,存在于hkey_local_machine\software\microsoft\ windows nt\nt3.51\networkcards\yhfddi1;

驱动程序的服务登录键,存在于hkey_local_machine\system\currentcontrolset\services

网卡的服务登录键,存在于hkey_local_machine\system\currentcontrolset\services

对于每一个网络部件,一个名为netrules的特殊子键在邻近的驱动程序或网卡登录子键里创建,netrules标识网络部件为网络整体的一部分。

fddi网卡驱动程序对应的标准软件登录表项将出现在以下路径:

hkey_local_machine\software\net612\yhfddi\yhfddi1.0;

驱动程序对应的标准项的值为:

description =yhfddi/pci adapter controller

install date =……

……

refcount =0x01

servicename =yhfddi

softwaretype =driver

title =yhfddi/pci adapter controller

而且在yhfddi驱动程序相关的netrules子键下,这些值项为:

bindable =yhfddi driver yhfddi adapter non exclusiver

bindform =“yhfddisys”yes no container

class = reg_multi_sz “yhfddi driver basic”

infname =oemnad1.inf

type =yhfddisys ndisdriver yhfddidriver

use =driver

yhfddi网卡在如下路径的networkcards子键里介绍:

hkey_local_machine\software\microsoft\

windows nt\nt3.51\networkcards\yhfddi1;

网卡的标准项包括以下这些值:

description =yhfddi/pci adapter controller

install date =……

manufacturer =net612

productname =yhfddi

servicename =yhfddi01

title =[01]yhfddi/pci adapter controller

§2.1.3编写inf信息配置文件

gui inf描述语言被windows nt用以书写系统所有部件的配置文件,当然也可以用以书写网络系统各部件的配置文件,该配置文件描述了网络部件安装、配置、删除的执行过程。当网络部件进行初始安装或二次安装(通常通过ncpa进行)时,安装程序读取部件对应的配置文件,进行解释执行。gui inf描述语言由节、命令、逻辑操作、变量规范、流程控制以及一套调用dll或外部程序的机制组成,其中,节是配置文件的主体,节可分为install节(类似于函数),shell节(也类似于函数,但可调用insall和shell节),detect节(不包含命令),一个配置文件一般由若干不同类型的节组成。驱动程序的开发者根据需要可以在配置文件中编写相应代码,使得用户和系统之间能进行交互,并且由用户决定一些配置参数。

nt网卡配置文件有其一套规范,驱动程序开发者必须按规范编写配置文件,一般来说,一个配置文件至少应该提供下面三个节:

安装入口点:[identify]shell节。该节主要功能是给出安装部件的类型名,系统通过它识别该部件属于哪一大类(display,mouse,scsi,network等)中的哪一类(网络adapter,driver,transport,service,network和netprovidor),同时,还需要给出映像文件和配置文件所在的源介质及标识。

[returnoption]shell节。系统执行安装identify节后,执行该节。它主要功能是检查所需安装的部件是否支持的硬件平台和语言,并给出网卡名(有些配置文件支持多类网卡,此时必须让用户进行选择,并获得选择结果)。

[installoption]shell节。该节是配置文件得主体,也是上次安装完后再次进行配置、删除、更新的入口点。主要功能是拷贝映像文件和配置文件,生成配置的各种选项,创建该部件在注册库中对应的各种登录子树并更新重写。

第二节 驱动程序的加载过程

§2.2.1 windows nt的启动过程

第一阶段:调入装入程序。和硬件平台相关,x86机器首先由rom装入根扇区,再由根扇区装入ntldr;

第二阶段:硬件检测。x86机器调ntdetect程序最大限度地获取各种硬件设备信息,引导hal及基本卷设备驱动程序,以便引导nt内核;

第三阶段:获取注册库中各种控制信息,如用户定义的非页内存大小;第四阶段:初始化注册库 \registry\machine下system和hardware并创建currentcontrolset,为装入相关硬件设备驱动程序作准备;

第五阶段:装入基本核心驱动程序;

第六阶段:释放一些已经完成使命的装入初始数据块;

第七阶段:进一步初始化注册库,以便有些依赖于基本核心驱动程序的上层驱动程序能顺利装入;

第八阶段:服务控制器装入应该由该服务控制器装入的各种驱动程序。

§2.2.2 fddi网卡驱动程序的加载过程

在windows nt启动的第五个阶段,将加载核心驱动程序。而对于ndis网卡驱动程序是在ndis接口(ndis.sys)加载后调入运行,向ndis wrapper注册、初始化、查询设置参数等。

windows nt启动时,相应的实体如nt的服务控制器根据注册库中yhfddi驱动程序的配置注册信息,初始化ndis wrapper,并装入相应的驱动程序,生成驱动程序管理块结构,申请内存以保存各种信息,向ndis wrapper注册驱动程序。初始化和注册完毕后,再由服务控制器读取注册库中相应的链接信息。

在ndis wrapper和yhfddi驱动程序初始化和注册成功后,ndis wrapper根据系统相应的注册信息,加入和yhfddi驱动程序所对应的fddi网卡,同时读入网卡的注册信息,并进行网卡注册和网卡初始化。

在以上过程成功后,wrapper将查询和设置驱动程序的各种参数,了解驱动程序对哪些操作支持,决定对上层驱动程序的支持范围。

第三节fddi网卡驱动程序的注册

driverentry函数是windows nt ddk规定的核心驱动程序的入口点,wrapper识别到入口点后,调入驱动程序,在driverentry函数内完成两个基本注册任务:

调用ndisminitializewapper函数向ndis接口报告驱动程序将以miniport类网卡驱动程序注册。ndis建立它需要记录的驱动程序状态信息,同时返回ndiswrapperhandle,驱动程序保存这个句柄,以利后来调用ndisxxxconfiguration和initialization等函数。

填写ndisxx_miniport_characteristics属性结构,主要记录ndis版本号和驱动程序支持的miniportxxx函数的入口点,然后调用ndismregisterminiport函数实现驱动程序的整体注册。

以yhfddi为例所要注册的属性结构的内容大致如下:

ndis_miniport_characteristics yhfddichar;

(ndis_miniport_characteristics这个结构将在第三章介绍)

yhfddichar.majorndisversion=yhfddi_ndis_major_version;

yhfddichar.minorndisversion=yhfddi_ndis_minor_version;

这两个属性决定驱动程序是ndis的哪个版本所支持,我们所用的是ndis3.0

yhfddichar.disableinterrupthandler=yhfddidisableinterrupt;

yhfddichar.enableinterrupthandler=yhfddienableinterrupt;

yhfddichar.isrhandle=yhfddiinterruptservice;

yhfddichar.handleinterrupthandler=yhfddihandleinterrupt;

以上四项属性是中断处理所需的上边缘服务函数的入口点(句柄)。fddi网卡驱动程序需要有smt站管理功能,而smt是以中断处理方式进行的,故这四项属性在fddi网卡驱动程序中是很重要的。

yhfddichar.initializehandler=yhfddiinitialize;

此项注册的是驱动程序的初始化函数句柄。

yhfddichar.queryinformationhandler= yhfddiqueryinformation;

yhfddichar.setinformationhandler=yhfddisetinformation;

这两项注册的是参数查询和设置函数的句柄。

yhfddichar.sendhanler= yhfddisend;

yhfddichar.transferdatahandler= yhfdditransferdata;

主要提供数据发送和接收函数句柄。

yhfddichar.resethandler=yhfddireset;

此项注册网卡软硬件重置函数句柄。

yhfddichar.halthandler= yhfddihalt;

此项注册网卡驱动程序挂起函数句柄。

yhfddichar.checkforhandler=null;

yhfddichar.reconfigurehandler=null;

这两个上边缘服务函数是fddi网卡驱动程序所不提供的,故置为null。

填好这些结构以后,调用以下函数实现驱动程序的注册:

ndismregisterminiport(

yhfddiwrapperhandle,

&yhfddichar,

sizeof(yhfddichar));

其中yhfddiwrapperhandle是在此之前初始化wrapper调用ndisminitializewrapper所得的句柄。

如果调用ndismregisterminiport不能返回ndis_status_success,必须在退出driverentry之前释放已经分配的资源(如yhfddiwrapperhandle等),故调用

ndisterminatewrapper(yhfddiwrapperhandle,null)。

这样驱动程序没能正确注册,亦不能正常运行。

第四节 网卡驱动程序对象查询与设置

如果ndis的管理实体要查询或设置一个特定的网络对象,它必须提供一个32位的oid。oid的结构如下: ***2.3.0 oid结构***

由上可以看到,oid可分为三大类:

所有ndis驱动程序都有的一般对象;

特定介质的对象;

特殊的与具体实现相关的对象(如多目地址表的长度)。

一般的和特定介质的oid被记录在windows nt ddk中,对于这些oid ddk文本指明了相关的对象能否通过miniportqueryinformation查询参数和通过miniportsetinformation设置参数。

oid也可被分为操作特性(如多目地址表长度参数)和统计参数(如广播包接收)。最后oid可分为必须的和可选的两种。

oid的前三个字节表明oid的不同类别,而最后一个字节确定这一类别内特定的信息管理对象。

针对于fddi网卡,被查询的oid的第一个字节为0x03。而ndis所查询的介质相关参数为:

0x03010104 oid_fddi_long_max_list_size

0x03010108 oid_fddi_short_max_list_size

0x03010102 oid_fddi_long_current_addr

0x03010106 oid_fddi_short_current_addr

tcp/ip传输驱动程序所要查询的fddi oid为:

0x03010102 oid_fddi_long_current_addr

0x03010103 oid_fddi_long_multicast_list

0x03010107 oid_fddi_short_multicast_list

通过以上两阶段的查询,ndis和tcp/ip驱动程序就分别了解了网卡驱动程序对其的支持,从而进行相应的捆绑,以便数据传输时正确选择网卡驱动程序。

第五节 开发环境与调试方法

开发环境:

fddi网卡驱动程序的开发环境为nt server 3.51,sdk,ddk for workstation 3.51, vc++4.1,硬件平台为586。

调试平台:

主机为nt server 3.51,windbg32

目标机为nt workstation3.51 (check 944)

调试方法:

利用dbgprint把目标机上关键信息通过串口传到主机进行分析,以得出ndis驱动程序的调度机制和运转状况;

利用assert产生异常断点,由主机对异常进行控制

自定义宏,进行分级控制,以根据不同情况产生不同调试信息

第四章 与smt移植相关的问题讨论

在本yhfddi网卡驱动程序中,smt的移植是极其关键的一部分,主要承担了驱动程序中硬件初始化和中断延迟处理。但由于smt是相对***的软件,这样就有一个ndis wrapper与smt间参数传递的问题。所以本章主要讨论miniport驱动程序与smt的关系和移植smt过程中初始化的要求、中断处理的要求,ndis wrapper与smt如何传递参数。

(一)miniport fddi网卡驱动程序与smt的关系。

在第一章已经谈及网卡驱动程序主要实现osi参考模型中的物理层和mac层。而对于fddi网络的物理层又可分为介质相关子层和介质无关子层。

对于我们的fddi/pci是基于x.3.19、x3.148、x3.166和x3.229而实现的。

smt在整个iso七层模型中属低两层范畴。下***是iso模型与fddi层次的对应关系,从而可知fddi miniport驱动程序在nt网络结构中的位置。

即在windows nt fddi网卡驱动程序应包含smt,实现fddi拓扑环上的站管理。

而在驱动程序内部smt主要是在miniport驱动程序中的中断延迟处理上边缘服务中实现的,也可以说是将smt嵌入中断延迟处理程序中,实现ndis接口对smt的正确调度。

yh-fddi驱动程序的实现可分为硬件无关部分和硬件相关部分。

移植smt过程中初始化的要求.

这里的初始化主要是指硬件初始化,包括寄存器的初始化和数据结构的初始化,由smt共用的硬件相关例程库中硬件初始化部分来完成. 我们在开发过程序是调用fddi_main(bdd_t*bdd)这个函数来调用smt共用的硬件相关例程库的.可见使用fddi_main(bdd_t*bdd)时,需要传递bdd这个参量,而bdd_t这个数据结构的定义如下:

它包含了各类硬件寄存器的基址,所以要对其进行正确赋值就必须首先在nt的内存中映射一块虚存与网卡内存相对应,也就实现了bdd_t结构的赋值,对fddi_main(bdd_t *bdd)的正确调用.

因此,我们在调用fddi_main前首先将网卡上寄存器内存空间映射到nt的虚存空间上,并将bdd结构正确赋值.以映射bsi_phy_base为例,具体过程如下:

pchar destination;

bdd_t *bdd;

ndis_physical_address physicaladdress;

ulong baseaddress;

ndis_status status;

baseadress =0x0d0000+bsi_phy_base;

ndissetphysicaladdresshigh(physicaladdress,0);

ndissetphysicaladdresslow(physicaladdress,baseaddress);

status=ndismmapiospace(

(pvoid *)&destination,

miniportadapterhandle,

physicaladdress,

bsi_phy_len

);

bdd->bsi_vir_base=(pchar) destination;

adapter-> bdd->bsi_vir_base= bsi_vir_base;

/*对adapter结构中的bdd结构赋值,以便在其它上边缘函数中使用这些虚存基地址*/

中断处理要求.

对于中断处理,在smt中主要调用cspintrhandandler()来实现.我们的fddi网卡驱动程序是miniport方式的,若在isr中做此处理将占用大量系统资源,使系统崩溃,所以我们采用只在isr中进行中断的排队,而在dpc中调用cspintrhandler()来完成中断处理.

在中断处理方面还有一个中断屏蔽和中断使能的问题,这两方面smt并不提供,故我们要正确处理.

具体处理方法见第三章.

ndis wrapper与smt间参数如何传递.

miniport方式的网卡驱动程序中,网卡上有中断时,系统反映给ndiswrapper,再由wrapper调度中断处理上边缘服务实现中断处理,在我们的yhfddi网卡驱动程序的中断具体处理是smt完成的所以在调用cspintrhandler时应将adapter结构传进smt以便在以后应用.

如在处理接收中断时,处理的最后应调用ndisindicatefddireceive,向ndiswrapper指示以接收到一个数据包,而ndisindicaterfddireceive的调用需要adapterminiporthandle作为参数,这就必须一级级从中断延迟处理函数(yhfddi handleinterrupt)中将adapter结构传递下来. 当然,其它方面如发送,也会有类似的问题需要考虑.

总之,对于smt的移植,需要详尽的在程序中做好接口,才能实现与

smt的数据交换.

结束语

ndis规范在网络两层间提供了一个统一界面,ndis对网络本身而言,是一个带有协议功能的标准接口,对实现者而言,它应该是一个环境,这种环境不仅带有协议功能,更重要的是带有和软、硬平台无关的核心功能支持,它不会受软、硬平台的变化严重影响,无疑,它是软件的移植和兼容的可靠保证,ndis把网络的一部分共性抽象出来,并根据具体的操作系统实现系统和平台相关的基础库以保证ndis的标准性和对开发者提供最大的功能支持,这也将加速和规范开发过程,但是,在操作系统之上提供ndis基础库获得标准同时也失去直接作用于操作系统带来的灵活性以及更强的功能支持,同时,ndis处于网络中层和低层之间,低层网络的快速发展和ndis对网络部分共性的抽象必然导致ndis对实现者的滞后,例如ddk3.51提供的ndis开发环境只支持10m以太网、fddi、令牌网(802.5)、localtalk、arcnet等,而对新出现的快速以太网及atm不提供支持,这对我们如何在ndis环境下实现诸如atm的lan emulation,ip over atm、快速以太网带来很大问题。

smt是实现fddi网卡驱动程序的关键,然而由于应用ddk开发miniport驱动程序时要遵循其结构框架,所以要想完整地按其结构移植smt,就必须分解smt适应之,即要求对smt有一个很好的理解。但smt是庞大的给开发带来了一定的困难。

参考文献

【1】《device driver kit用户手册》

【2】《device driver kit核心驱动程序设计》

【3】《device driver kit网络驱动程序设计》

驱动程序篇5

    由于工作关系,我经常涉及PC机与设备接口的工作,从PC机这方面要做的工作看来,主要是通过接口处理设备的中断,通过I/O端口或内存地址与外设互相传递数据。从计算机原理的角度看,所要达到的目的很简单,那么如何编写程序完成上述功能呢?

    目前国内流行的PC操作系统有三种:DOS,Win95/98系列,WindowsNT。DOS是单用户、单任务操作系统,由于PC机硬件处理速度不断提高,基于单用户、单任务的操作系统越来越不能充分发挥硬件的功能,现在只应用于一些老式PC及其它个别场合,有逐渐被淘汰的趋势;Win95/98系列和WindowsNT属于多任务操作系统,不论从其原理还是界面上看,这两种操作系统都比DOS有着无可比拟的优越性,这两种操作系统虽然在界面和操作上及其相似,但其内部实现的诸多方面有许多区别,有些区别是本质上的。Win95/98设计目标是针对一般家庭用户,安全性及可靠性存在许多薄弱环节,就可靠性而言,Win95/98系列不能很好的防止多任务环境中某个进程的非法操作导致系统中其它程序甚至整个系统的崩溃,而WindowsNT在这方面及其它诸多方面设计的相当严谨。这两种操作系统是Microsoft公司同一时期的产品,但针对不同的使用群,所以在一些重要场合及生产实践中应该选择WindowsNT作为计算机的操作系统,此外,从发展趋势来看,WindowsNT已经成为定型产品,具有相对稳定性。

    在不同操作系统下编写驱动程序是有很大区别的,在DOS平台上,应用程序和设备驱动程序之间没有标准的接口,它们在外部表现为一个扩展名为EXE的文件,驱动程序的作用被柔和在应用程序中,这样,应用程序为了使用不同厂商的同一类设备,必须了解这些设备在接口上具体的硬件实现,同时,对于一个特定型号的硬件产品,所有支持它的应用软件中对于控制整个设备动作的这部分代码,可能被多次重写。这种情况不适应硬件及应用软件的飞速发展。Windows系统在这方面,进行了根本性改进,把控制设备动作的这部分代码***出来,提出了设备驱动程序的概念,驱动程序是应用程序和硬件设备之间的一个桥梁,应用程序与驱动程序之间有明确的接口,应用程序通过与驱动程序交换信息,达到控制外设的目的。接口定义的操作是面向设备的,这就是说,在应用程序的设计中,并不用关心对外设操作的具体硬件实现,只是对驱动程序发出一系列指令既可;驱动程序接受来自上层应用程序的指示,具体操纵实际硬件,完成用户功能。具体实现上,Win95/98系列与WindowsNT又有所区别,WindowsNT是严格按照上述思路设计的;而Win95/98系列不那么严格,其支持上述思路,但同时应用程序也可以绕过驱动程序直接访问实际物理I/O,这样做,增加程序设计的灵活性,但同时,对系统可靠性造成一定隐患。这也正是Win95/98系列可靠性低于WinNT的原因之一。

    表1-1 三种操作系统下访问接口比较

    操作系统 应用程序访问接口方式 访问权限

    DOS 直接访问 所有[注]

    Windows95/98 通过设备驱动程序*.VXD 所有[注]

    直接访问 仅I/O端口

    WindowsNT 通过设备驱动程序*.SYS 所有[注]

    [注]‘所有’指I/O端口,RAM总线,中断,DMA。

    WindowsNT设备驱动程序的组成原理

    WindowsNT操作系统结构分为用户模式和内核模式,用户模式下的编程为应用程序的设计,而开发设备驱动程序,则属于内核模式下的编程,内核模式组件包括NT Executive(ExXxx),内核(KeXxx),硬件抽象层(HalXxx)。其层次如***2-1所示,其中NT Executive 包括几个***的软件组件,它们是系统服务接口(ZwXxx),对象管理器(ObXxx),配置管理器,进程管理器(PsXxx),安全监视器(SeXxx),虚拟空间管理器(MemXxx),本地进程调用,I/O管理器(IoXxx)。内核模式的系统服务并不是全部公开的,而是提供了一系列开发设备驱动程序需要的函数(上文括号内为函数形式,函数手册参见[2]Kernel-Mode Drivers-Reference章节),换言之,这些函数功能是所有内核模式的系统服务功能的子集。

    驱动程序由一系列相对***的函数组成,由I/O管理器根据需要调用这些函数,对于一个需要处理中断的最简单的驱动程序也需要由以下几个函数构成:

    1.DriverEntry() 运行于PASSIVE_LEVEL

    驱动程序入口点,当驱动程序被手动或自动装入系统后,驱动程序从这点开始执行,主要用于定位硬件资源,建立指向其它驱动程序函数的指针等其它初始化工作。

    2.XxUnload() 运行于PASSIVE_LEVEL

    用于驱动程序从系统卸出之前,释放由驱动程序占用的所有系统资源。

    3.XxIsr() 运行于DIRQL

    中断服务程序。

    4.XxDpcForIsr() 运行于DISPATCH_LEVEL

    中断服务程序后处理程序,以排队方执行不太关键代码的执行,由于排队机制及优先级,不会造成代码拥塞从而提高中断服务程序的响应并且提高系统总体I/O吞吐率。

    5.XxOpen() 运行于PASSIVE_LEVEL

    处理应用程序Win32函数CreateFile()请求。

    6.XxClose() 运行于PASSIVE_LEVEL

    处理应用程序Win32函数CloseHandle()请求。

    7.XxDispatch() 运行于PASSIVE_LEVEL

    处理应用程序Win32函数DeviceIoControl()请求,通过一系列自定义命令,驱动程序与应用程序交换特定的信息。

    WindowsNT使用一个抽象化的CPU优先级方案, IRQL代表中断请求级,任一时刻CPU总处在某一级上,这个数越大,表示当前的任务重要性越大,如表2-1所示,从上至下IRQL越来越小。所有上述驱动程序的函数及内核模式函数都必须运行于各自的IRQL级上,如果违反这一调用规定,会造成系统崩溃。例如,中断服务程序(XxIsr)运行于DIRQL及上,那幺在编写中断服务程序时,只能调用允许在这一级运行的内核模式函数(并不是所有内核模式函数都能运行于DIRQL级)。至于每个内核模式函数运行级别的说明,详见[2]Kernel-Mode Drivers-Reference章节。

    WindowsNT是一多任务系统,许多设备的驱动程序同时存在系统中,这样各个设备所占用的资源(中断,I/O及RAM地址空间)很有可能冲突,如果设备驱动程序在运行之前不进行‘探测’而使用自己硬件设备的资源,有可能和系统内其它设备占用的资源冲突,后果不堪设想。WindowsNT通过注册表管理硬件资源的占用信息,作为内核模式信任的组件,驱动程序使用硬件资源之前必须遵循‘查询-申请-使用-释放’的原则(如***2-2所示)。

    表2-1

    来源 IRQL

    硬件 HIGHEST_LEVEL

    POWER_LEVEL

    IPI_LEVEL

    CLOCK2_LEVEL

    CLOCK1_LEVEL

    PROFILE_LEVEL

    DIRQLs(I/O设备中断平台相关的级数)

    软件 DISPATCH_LEVEL

    APC_LEVEL

    PASSIVE_LEVEL

    WindowsNT设备驱动程序的编写步骤与实例

    现以一实际例子简要说明设备驱动程序的开发步骤,本例以CINRAD天气雷达测试卡实际应用为原型,加以简化、抽象。

    第一步,了解被控设备的接口情况。

    本例为一ISA卡,占用PC机9号中断,I/O地址360H及RAM地址D0228H分别一个字空间。

    第二步,确定驱动程序的功能。

    驱动程序每当9号中断达到时,检查运行标志变量RunFlag(为一BOOL变量),如果等于TRUE,中断累积计数器counter(为一unsigned short变量)增一,把这个值写入RAM地址D0228H,再从这个地址读出,如果读出值等于写入值,把这个值写入I/O地址360H,这个地址的内容会驱动板卡上的LED显示,把写入值显示出来;如果读出值不等于写入值,设置运行标志变量FALSE。如果运行标志变量等于FALSE,什幺也不做,返回。

    第三步,定义驱动程序与应用程序的软件接口。

    本例定义两个接口命令:

    IOCTL_IOCardA_START:应用程序设置驱动程序内部的运行标志变量等于TRUE。

    IOCTL_IOCardA_READ:应用程序查询驱动程序内部的中断累积计数器的值。

    第四步,画流程***。这里列举本例实现的几个主要流程***,(***略)。

    系统传给驱动程序入口函数系统定义的‘设备驱动对象’DrObj,通过初始化这个对象的一些成员变量,把驱动程序其它函数与这个对象联系起来。

    ISA卡为非即插即用设备,事先把资源占用信息手工添加注册表如下:

    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IOCardA\parameters]

    "IRQ"=dword:00000009

    "IOSPAN"=dword:00000004

    "IOAdd"=dword:00000360

    "RAMAdd"=dword:000d0228

    "RAMSPAN"=dword:00000002

    其中IOCardA以下各子键及其值为自定义,设备驱动程序利用相应函数检索出这些值。

    (3)每个设备驱动程序可以创建若干系统定义的‘设备对象’,本例根据需要只创建了一个‘设备对象’Dev。‘设备对象’其中一个成员变量为指向一非分页的物理内存块DeviceExtension,这块内存大小及内容为用户自定义,由于Dev或DeviceExtension对象会被系统传给驱动程序的其它函数,这样驱动程序各函数通过访问这块内存区,实际上达到互相传递信息的功能。本例在这里存储设备硬件资源信息及RunFlag和中断计数器counter,这些数值在DriverEntry()初始化后,供驱动程序的其它函数使用。

    ***3-2为中断服务程序IOCardAIsr()流程***。操作系统接受中断,连同DeviceExtension等参数传给中断服务程序,中断服务程序利用这些参数,实现要求功能。

    ***3-3为IOCardADispatch()流程***,这个函数用于处理来自上层应用程序的命令。上层应用程序通过以下程序段设置驱动程序中RunFlag值为TRUE,从而启动中断服务程序开始计数。

    BOOL cmd=TRUE;

    hTest = CreateFile(...); //打开设备

    DeviceIoControl(hTest, //设备句柄

    IOCTL_IOCardA_START,//命令

    &cmd,sizeof(BOOL), //输入缓冲区地址及大小

    NULL,0,&c,NULL);

    CloseHandle(hTest); //关闭设备

    上层应用程序通过以下程序段查询当前的中断计数器的值并存于变量w中。

    unsigned short w;

    hTest = CreateFile(...);

    DeviceIoControl(hTest,

    IOCTL_IOCardA_READ, //命令

    NULL,0,

    &w,sizeof(unsigned short),//输出缓冲区地址及大小

    &c,NULL);

    CloseHandle(hTest);

    其中DeviceIoControl()执行后,操作系统调用IOCardADispatch()函数,如流程***所示,这个函数内部通过一个开关语句,根据命令执行相应的分支。驱动程序与应用程序通过此函数接***换数据时,操作系统提供4种可选数据缓冲方式,本例由于数据I/O量比较小,故选用‘缓冲I/O’ (METHOD_BUFFERED)。过程是,I/O管理器首先分配一个非分页池,它的大小为调用者输入缓冲区和输出缓冲区的较大者,第一段程序为sizeof(BOOL),第二段程序为sizeof(unsigned short),它的地址存到IRP(I/O请求包)的AssociatedIrp.SystemBuffer域中,然后把输入数据拷贝到这个池中,在第一段程序中cmd的值TRUE被拷贝到池中,这样驱动程序通过RtlCopyBytes()函数再把池中的值拷贝到驱动程序的RunFlag中。IOCardADispatch()函数执行完,I/O管理器把池中的内容拷贝到调用者的输出缓冲区,在第二段程序中,驱动程序通过RtlCopyBytes()函数把counter的值拷贝到池中,从而最终传递到应用程序变量w中。

    第五步,编程。在编写设备驱动程序的同时,要编写一个简单的应用程序用于测试设备驱动程序的一些功能。

    第六步,驱动程序的载入。

    驱动程序C语言源程序经过编译、连接生成扩展名为SYS的文件,本例为IOCardA.sys,把这个文件拷贝到\WINNT\system32\drivers\系统目录下,同时手工添加如下信息到注册表:

    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\IOCardA]

    "ErrorControl"=dword:00000001

    "Start"=dword:00000003

    "Type"=dword:00000001

    要保证IOCardA子键名与驱动程序文件名一致,其中Type=1表示此驱动程序为内核模式驱动程序,Start=3表示此驱动程序手动载入,ErrorControl=1表示当驱动程序发生错误时,日志记录错误并显示一个消息框。这样当Windows重新启动后,通过使用控制面板中的Device小应用程序,从列表中找到IOCardA设备名,按Start按钮,于是,设备驱动程序就驻留内存并在底层开始工作了。

    第七,调试。设备驱动程序没有界面,完全在系统底层运行,为了观察驱动程序的运行状态,WindowsNT DDK提供windbj.exe程序用于设备驱动程序的调试,调试设备驱动程序需要两台CPU体系结构完全相同的计算机,一台为‘宿主机’,运行windbj.exe程序,另一台为‘目标机’,运行设备驱动程序,两台计算机用串口线连好,进行一系列软件设置(参见[1]第17章),就可以开始调试了,从‘宿主机’可以控制及观察‘目标机’上驱动程序的运行情况。最常用的调试手段是在驱动程序中必要的位置加入DbgPrint()函数,这个函数可以把指定信息输出到‘宿主机’windbg.exe窗口中,通过分析这些信息,可以了解驱动程序当前的运行情况。

    结束语

    WindowsNT是一个复杂而严密的系统,驱动程序的开发不可避免的涉及现代操作系统理论及其它许多计算机理论,内涵相当广泛,本文围绕着开发实践从一定深度探讨了WindowsNT设备驱动程序开发最基本的知识及一般方法,希望对读者有所帮助,对于复杂,特殊应用的实现及编程技巧,有待于读者在各自实际应用中不断探索。

    参考文献

驱动程序篇6

Windows CE作为一种典型的嵌入式操作系统,通过将蓝牙应用移植到Widows CE中,对于如pSOS+、Nucleus等其它嵌入式系统,具有典型意义。由于OBEX上的文件传输应用建立在RFCOMM实现的蓝牙仿真串口上,本文介绍OBEX文件传输的蓝牙虚拟串口驱动程序的实现。

1 Windows CE设备驱动程序概述

Windows CE支持广泛的基于各种CE平台的设备驱动程序。目前,它提供了四种设备模型,其中两种是专用于Windows CE的模型,另外两种外部模型来自其它操作系统。基于Windows CE的两种模型是本机的设备驱动程序和流接口驱动程序。两种外部模型用于通用串行总线(USB)和网络驱动器接口标准(NDIS)的驱动程序。

由于蓝牙协议是在无线技术下的仿真串口,蓝牙中OBEX的许多应用正是基于蓝牙仿真串口。而流接口驱动程序通过一组流接口函数使得应用程序可以通过文件系统中的特殊文件而与设备接口,因此蓝牙仿真串口的功能性更适合流接口驱动程序的结构。

***1

2 Windows CE下蓝牙串口驱动程序的实现

虽然蓝牙设备驱动程序的实现采用流接口驱动程序设计,但由于两种驱动程序的基本结构与原理相似,所以下文从本机设备驱动程序结构开始,可以更清楚地认识蓝牙设备驱动程序的实现原理。

2.1 本机设备驱动程序结构

Windows CE中包含的样本设备驱动程序分为两种类型:单片驱动程序(Monolithic device driver)和分层的驱动程序(Layered Devicedriver)。单片驱动程序基于单个码片,该码片直接把硬件设备的功能性通过设备驱动程序接口传递给操作系统。与单片驱动程序相比,分层的驱动程序由两个设置好的层组成:上层是模型设备驱动程序(MDD),下层是依赖平台的驱动程序(PDD)。本文采用分层的驱动程序来连接蓝牙硬件和上面的文件传输应用。***1说明了两种驱动程序是如何在Windows CE操作系统中集成的。

设备驱动程序接口(DDI)是在MDD中实现的函数集,GWES模块通过这个接口调用设备驱动程序;设备驱动程序服务器提供接口(DDSI)是在PDD中实现的函数集并由MDD调用。由于微软提供了所有与MDD模块相关的源代码,所以对这部分不用做任何改动,只需将自己的PDD模块与MDD模块链结成一个公用库。

理解了本机设备驱动程序的结构后,从***1右边不难看出,流接口驱动程序只是把流接口作为它们的DDI使用。在这种情况下,不必要把这些驱动程序与GWES模块逻接起来。它们以普通的DLL方式存在并根据需要被调用。

2.2 蓝牙仿真串口驱动程序实现

蓝牙仿真串口是用蓝牙RFCOMM协议实现无电缆的无线串口通信,与本机设备驱动程序一样,实现流接口的串口驱动程序同样只需实现蓝牙的PDD模块。

PDD模块包括四部分:第一部分是必须自己实现的所有DDSI接口;第二部分是蓝牙协议栈包括FRCOMM、SDP、L2CAP以及HCI实体;第三部分是HCI传输层通过UART或者USB接口连接到蓝牙硬件;最后一部分是为蓝牙应用提供的***形界面接口和控制端口模块,用来对整个协议栈初始化、蓝牙硬件初始化、搜索附近的蓝牙设备以及发现指定设备上的服务。如***2所示。

在***2所示的蓝牙仿真串口驱动程序的系统结构中,设备管理程序是用户层的程序,在基于Windows CE的平台上不停地运行着。设备管理程序不是内核的一部分,但它是与内核、注册表和注接口驱动程序DLL有相互影响的单独部分。它主要执行以下任务:

HWOBJ BluetoothObj={ /*描述蓝牙仿真串口特征*/

(PDEVICE_LIST)&SerDL,

THREAD_IN_PDD,/*中断处理全部由PDD层处理*/

0,

NULL,

(PHW_VTBL)&BluetoothVTbl/*包含需要实现的所有标准串口DDSI函数的列表*/

};

HWOBJ BluetoothCTRLObj={/*描述蓝牙控制端口特征*/

(PDEVICE_LIST)&SerDL,

THREAD_IN_PDD,

0,

NULL,

(PHW_VTBL)&CTRLVTb1

};

PHWOBJ rgpHWObjects[]={/*包含两个PDD实例的数组*/

&BluetoothObj,

&BluetoothCTRLObj

};

DEVICE_LIST SerDL={/*存储设备驱动程序中所有串口设备*/

“CESerial.dll”,/*串口驱动程序的名字*/

sizeof(rgpHWObjects)/sizeof(PHWOBJ),/*串口设备的数目*/

regHWObjects

};

PDEVICE_LIST GetSerialObject(VOID)

{

return (&SerDL);

}

***2

    在串口驱动程序中注册了两个串口设备后,就要实现这两个PDD实例对应的流接口函数。微软为要实现的串口PDD模块提供了一个HWOBJ(Haredware Object)类型的串行对象表,这个结构列出了实现串口驱动的所有接口函数指针。见下面的描述:

typedef struct _HW_VTBL{

PVOID(*HWInit)(ULONG Identifier,PVOID pMDDCon-text,PHWOBJ pHWObj);

…,

…,

BOOL(*HWIoct1)(PVOID pHead,DWORD dwCode,

PBYTE pBufIn,DWORD dwLenIn,PBYTE pBufOut,DWORD

DwLenOut,PDWORDpdwActualOut);

} HW_VTBL,*PHW_VTBL;

用户通过修改串行口PDD的串行对象表改变函数集或函数名。下面的结构BluetoothVTbl的定义了蓝牙仿真串口DDSI函数的名称。

Const HW_VTBL BluetoothVTbl={

BluetoothInitSerial,

…,

…,

BluetoothIoctl

};

同样用结构变量CTRLVTbl定义了控制端口的DDSI函数名列表。由于这个串口设备用作内部实现特殊的功能,下面只列出了需要关心的主要函数名。

Const HW_VTBL CTRLVTbl={

CTRLInitSerial,

CTRLDeinit,

CTRLOpen,

CTRLClose,

…,

…,

CTRLIoctl

驱动程序篇7

关键词:S3C2410;ADC;Linux;字符设备驱动程序

中***分类号:TP311文献标识码:B

文章编号:1004373X(2008)2203303

Implement and Application of ADC Driver about Embedded-Linux

SUN Dehui,LIANG Xin,YANG Yang

(Key Laboratory of Beijing Municipality,The FAT Laboratory,North China University of Technology,Beijing,100041,China)

Abstract:The module of ADC in S3C2410 CMOS chip and the model about Linux drivers are expounded,the method of developing character device drivers are illuminated by realizing an ADC driver.As convenient to debug,compiling the drivers into module and "insmod" it into kernel.Updating the drivers module and application by MINICOM,one kind of consoles.Application on testing resistance and voltage using ADC driver are introduced through an example.In the end,it is obviously that ADC drivers module could be "insmoded"and called successful from the result of experiment.using the drivers testing resistance,voltage and many other standard signal.ADC drivers can collect the standard signal of plants as one part of interface of industrial sensor.

Keywords:S3C2410;ADC;Linux;character device driver

1 引 言

S3C2410开发板制造商提供了绝大部分的驱动程序,但有时出于实际开发的需要、应用程序的稳定性考虑,用户往往需要开发一个自己需要的接口驱动程序。下面分析Linux字符设备驱动程序的结构,以及ADC驱动程序的开发。本驱动可以测试电压、电流等标准工程量信号,或作为工业传感器接口的一部分对现场标准工程量信号进行采集处理(可以在平台上为传感器预留接口,本文在作电路板时已直接将测试用的滑动变阻器与A/D接口相连)。

2 S3C2410X及ADC模块

S3C2410X是韩国三星电子公司推出的一款基于ARM920T内核的16/32位RISC嵌入式微处理器,该处理器主要面向手持式设备以及高性价比、低功耗方面的应用[1]。S3C2410X芯片集成1个LCD控制器(支持STN和TFT带有触摸屏的液晶显示屏)、SDRAM控制器、3个通道的UART、4个通道的DMA、4个具有PWM功能的计时器和1个内部时钟、8通道的10位ADC,同时S3C2410X还有丰富的外部接口,例如触摸屏接口、I2C总线接口、主从USB设备接口、SPI接口、SD/MMC卡接口等[2]。

S3C2410X芯片内部集成了一个8路10位A/D转换器(其中第5、第6通道可用于支持触摸屏接口)。ADC模块是带采样保持器的,在25 MHz A/D转换时钟下,最高转换速率是500 kS/s,以片上采样、保持的方式工作,支持掉电模式,其测量模拟输入电压范围为0~3.3 V。由于ADC转换模块和触摸屏控制是共用的8通道模拟信号输入,所以要单独实现A/D转换功能需要把ADC触摸屏控制器(ADCTSC)设置成通常工作模式(ADCTSC的AUTO_PST=0,XY_PST=0)[3,4]

A/D转换时间在PCLK频率为50 MHz并且预分频值为49的情况下,转换10位数据的时间为:

A/D转换频率=50 MHz/(49+1)=1 MHz

转换时间=1/(1 MHz/5 cycles)=1/200 kHz

=5 μs

对ADC操作,主要是对下面的ADC几组寄存器进行读写操作:

ADC控制寄存器:ADCCON(2410ARM平台下寄存器物理地址是 0X5800000);

ADC触摸屏控制寄存器:ADCTSC(2410ARM平台下寄存器物理地址 0X58000004);

ADC数据寄存器:ADCDAT0/1(2410ARM平台下寄存器物理地址 0X5800000C/0X58000010) [5]。

3 Linux的设备驱动程序模型

目前Linux支持的设备驱动可分为3种:字符设备(character device)、块设备(block device)、网络接口设备(network interface)[2]。本文所涉及的ADC摸块驱动程序就是属于字符设备驱动程序。字符设备指那些无需缓冲直接存取的设备。在对字符设备发出读、写请求时,实际的硬件I/O一般就紧接着发生了,是Linux设备中最简单的一种。应用程序可以用与存取文件相同的系统调用来打开、读写及关闭它。即使此设备是将系统连接到网络中的PPP后台进程的modem也是如此。字符设备驱动程序一般要包含open,close,read,write等几个系统调用[6]。

I/O子系统向内核其他部分提供了一个统一的标准设备接口,这是通过数据结构file_operations[7]来完成的。这个结构中的每一个成员的名字都对应着一个系统调用,用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。编写设备驱动程序的主要工作就是编写子函数,并填file_operations的各个域。

4 ADC底层驱动程序的实现

ADC驱动程序的主要任务,就是把2410的ADC内置模块的使用传递给应用程序,为了便于理解,下面就按照驱动程序加载、使用的顺序,来讲述ADC底层驱动的实现。

4.1 驱动程序的加载及初始化

驱动程序的加载方式有2种:一种是将其作为内核的一部分,直接编译到内核中,即静态编译,也可以单独作为一个模块编译,在需要时再动态地把它加载入内核,不需要时也可从内核中删除,即动态连接。由于S3C2410X芯片带MMU,在此,使用动态连接方式进行加载,也便于调试[8]。

在Linux的相关路径下,使用指令insmod s3c2410-adc.o 即可将编译好的ADC驱动程序以模块的方式加载入内核。当驱动加载入内核之后,首先要调用s3c2410_adc_init()函数。绝大多数驱动程序,都要在XXX_Init()函数中完成驱动程序的初始化,这其中包括物理地址的映射、中断注册、管脚和相应寄存器的初始化等。当然也可在open(),read()函数中对寄存器进行初始化,视具体程序要求而定(本文对ADC部分寄存器的初始化是在read部分完成的)。

向系统增加一个驱动程序则意味着要赋予它一个主设备号,这一赋值过程是通过register_chrdev()函数来实现的,这个函数定义在

int register_chrdev(unsigned int major,const char *name,struct file_operations *fops);

register_chrdev()需要3个参数:参数一是希望获得的设备号,如果是零,系统将选择一个没有被占用的设备号返回;参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针[7](在上文中提到的file_operations结构体中定义)。如果登记成功,返回设备的主设备号,不成功,返回一个负值。

ADC初始化主要代码如下:

int __init s3c2410_adc_init(void)

{

int ret;

ADCTSC = 0;//将ADCTSC寄存器设成通常工作模式

ret = register_chrdev(0,DEVICE_NAME,&s3c2410_fops);

if (ret < 0) {

printk(DEVICE_NAME " can't get major number\\n");

return ret;

}//初始化主设备号为0,使系统为此驱动程序动态地分配一个主设备号

adcMajor=ret;

printk (DEVICE_NAME"\\tinitialized\\n");

return 0;

4.2 打开、读、写ADC驱动程序

当应用程序打开ADC驱动程序时,通过指针&s3c2410_fops调用对应的s3c2410_adc_open()函数,这个函数比较简单,不予介绍。S3C2410X有6个通道可做一般功能使用的ADC,在本驱动程序中只使用0,1两个通道,且在对应的应用程序中进行设置,会在下文中予以介绍。应用程序要读取某一路的ADC值时,先调用s3c2410_adc_write()函数,把用户程序要求的通道号channel和预分频比值prescale传递到ADC设备的结构体变量中。这2个结构体成员定义如下:

typedef struct {

struct semaphore lock;

wait_queue_head_t wait;

int channel;// ADC_DEV设备通道号

int prescale; // ADC_DEV设备预分频比

}ADC_DEV;

s3c2410_adc_write()函数的部分代码如下:

int data;

copy_from_user(&data,buffer,count);

adcdev.channel=ADC_WRITE_GETCH(data);//设置通道号

adcdev.prescale=ADC_WRITE_GETPRE(data); //设置预分频比

return count;

}

ADC_WRITE_GETCH(data)和ADC_WRITE_GETPRE(data)是2个带参数的宏,通过简单的算法处理,将用户程序要求的通道号和预分频比分别剃出。

接下来应用程序调用s3c2410_adc_read()函数来启动某一通道的ADC转换并读取转换后的数据,这一过程是主要对ADC物理寄存器进行操作,主要代码如下:

ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch));

ADCCON |= ADC_START//开启ADC转换

interruptible_sleep_on(&adcdev.wait);//休眠用户进程,等待转换结束后,再将其唤醒

ret = ADCDAT0; //转换结束,数据存储在ADCDAT0寄存器中

ret &= 0x3ff;

copy_to_user(buffer,(char *)&ret,sizeof(ret));//将转换结果传递给用户程序

其中PRESCALE_EN,PRSCVL(prescale),DC_INPUT((ch)),ADC_START这几个宏分别对ADCCON寄存器的相应位进行了设置,copy_to_user()将ret这个在内核空间局部变量中的10 b数据传递给用户空间。

本ADC驱动程序主要的函数介绍完毕,接下来只要完成s3c2410_adc_release(),s3c2410_adc_exit()等其他函数就可以。前文已经提过,字符设备驱动程序所有的系统调用都是通过file_operations结构体[7]集合的,对于本程序,定义如下:

static struct file_operations s3c2410_fops = {

owner:THIS_MODULE,

open:s3c2410_adc_open,

read:s3c2410_adc_read,

write:s3c2410_adc_write,

release:s3c2410_adc_release,

};

用户程序在调用时只需像使用read,write操作普通文件一样对底层进行字符设备进行操作。

5 ADC驱动程序的应用

在基于2410的Linux环境下,用这个驱动程序可以实现外部模拟信号到2410数字信号的转换,下面是一个最基本的电压测量的运用,原理***如***1所示。

通过改变2个滑动变阻器两端的电压来分别得到2个模拟输入信号,通过导线直接连接到S3C2410芯片的AIN0和AIN1引脚(暂不考虑放大和滤波处理)。

在应用程序中,通过一个for循环语句来实现对AIN0和AIN1两路通道的循环采集数据的。

while( stop==0 )

{

for(i=0;i

d=((float)GetADresult(i)*3.3)/1024.0;

printf("AIN%d=%8.4f\\t",i,d);}

}

其中GetADresult()函数即实现read和write系统调用:

static int GetADresult(int channel)

{

int PRESCALE=0X**;

int data=ADC_WRITE(channel,PRESCALE);

write(adc_fd,&data,sizeof(data));

read(adc_fd,&data,sizeof(data));

return data;

}

GetADresult()的返回值就是通过copy_to_user()传递过来的10位A/D转换结果。根据返回值data来计算模拟电压为:

date/210×3.3。

在上位机上使用相应的gcc编译器将驱动程序和应用程序编译后,***到开发板上运行,可在minicom或超级终端上看到如***2所示运行结果。

6 结 语

基于Linux和S3C2410的嵌入式产品运用已经越来越广泛,分析Linux下ADC驱动程序的开发,通过本文的介绍,读者可以对Linux的驱动程序的结构、编写以及实际应用能有一定的了解。

参考文献

[1]潘巨龙,黄宁.ARM9嵌入式Linux系统构建与应用[M].北京:北京航空航天大学出版社,2007.

[2]孙天泽,袁文菊.嵌入式设计及Linux驱动开发指南[M].2版.北京:电子工业出版社,2007.

[3]舒云.邱绍峰.基于Windows 的ADC驱动程序实现与应用研究[J].工业控制计算机,2007,20(4):57-58,61.

[4]畅卫功,丁忠林.嵌入式Linux系统中触摸屏驱动的研究[J].微计算机信息,2007,23(20):103-105.

[5]S3c4210 user′s manual.Samsung Electrionic.2004.

[6]何世烈,陈建.基于嵌入式Linux的设备驱动程序设计[J].单片机与嵌入式系统应用,2007(7):65-67.[7]JonatbanCorbet,Alessandro Rubini,Greg Kroah-Hartman.LINUX设备驱动程序[M].3版.魏永明,译.北京:中国电力出版社,2005.

[8]宋宝华,华清远见嵌入式培训中心.LINUX设备驱动开发详解[M].北京:人民邮电出版社,2008.

[9]潘辉,贾世祥.基于s3c2410和嵌入式Linux的D/A转换的实现[J].微计算机信息,2007(20):128-129,130.

[10]刘淼.嵌入式系统接口设计与Linux驱动程序开发[M].北京:北京航空航天大学出版社,2006.

[11]孙婷,田泽,闫效莺.基于S3C2440的Windows CE设备驱动的研究与实践\.现代电子技术,2008,31(6):153-155,158.

驱动程序篇8

关键词:嵌入式操作系统;Win CE;SPI;驱动程序

中***分类号:TP311文献标识码:B

文章编号:1004-373X(2009)10-069-04

Design of EP9315-SPI Driver Based on Win CE

ZHANG Dong1,XU Dijian2

(1.Chongqing University of Arts and Sciences,Chongqing,402160,China;2.Chongqing University of Science and Technology,Chongqing,401331,China)

Abstract: It is very important to compile driver connecting operating system with corresponded hardware device.Based on stream interface driver model,the design of SPI driver in embedded operating system Win CE in development environment of platform builder 4.2 and design method are introduced and analysed,realizing virtual address map,key code and the relationship between driver and SPI application program in EVC program environment is discussed.Driver and corresponded application program can be operated on FS_EP9315 development platform of ucdragon rightly.Experience indicates the methord is right and feasible.

Keywords:embedded operating system;Win CE;SPI;driver program

0 引 言

嵌入式是“以应用为中心,以计算机技术为基础,软硬件可裁剪,适合应用系统对功能、可靠性、成本、体积、功耗严格要求的计算机系统”。Windows 是Microsoft推出的功能强大的紧凑、高效、可伸缩的32位嵌入式操作系统,主要面对各种各样嵌入式系统的产品[1,2]。

该系统具有多线程、多任务、完全抢占式的特点,是为各种具有严格资源限制的硬件系统所设计的。为了将操作系统和硬件设备连接起来,硬件和软件的驱动联系就显得很重要。SPI是一种高速、全双工、同步的通信总线,在芯片的管脚上只占用4根线,节约了芯片的管脚,同时为PCB的布局节省了空间,提供了方便,正是出于这种简单易用的特性,现在越来越多的芯片都集成了这种通信协议。SPI的工作模式有两种:主模式和从模式,SPI总线可以配置成单主单从、单主多从、互为主从。为了充分利用芯片的SPI接口进行相应的驱动程序设计以及应用程序设计,通用方法的研究就显得十分重要。

1 Win CE提供的驱动模型

Win CE操作系统支持两种类型的驱动程序,一种为本地驱动程序,是把设备驱动程序作为***的任务实现的,直接在顶层任务中实现硬件操作,因此有明确和专一的目的。本地驱动程序适合于那些集成到Win CE平台的设备,诸如键盘、触摸屏等设备。另一种是具有定制接口的流接口驱动程序,它是一般类型的设备驱动程序,为用户一级的动态链接库(DLL)文件,用来实现一组固定的函数称为“流接口函数”,这些流接口函数使得应用程序可以通过文件系统访问这些驱动程序。这里论述的SPI驱动就属于流接口驱动。

2 SPI驱动程序的设计

2.1 EP9315芯片及SPI接口简介

EP9315是一款基于ARM920T,由Cirrus Logic公司生产的工业级芯片[3,4] ,内带MMU,16 KB的指令Cache,16 KB的数据Cache和数学协处理器,主频为200 MHz,系统总线为100 MHz。该芯片拥有一组SPI接口,利用它可方便实现与SPI器件进行通信,可大大简化工程应用的硬件设计软件。

SPI驱动程序采用Win CE流驱动的标准形式。下面从驱动程序具体设计步骤以及驱动代码的编写两个方面做较为详细的阐述。

2.2 SPI驱动程序设计步骤

在Platform Builder 4.2下设计Win CE流接口驱动程序可按照以下步骤进行[5-7]:

(1) 在C:\\Win CE420\\PLATFORM\\ep931x\\drivers目录下新建一个目录SPI;

(2) 从其他驱动目录下复制makefile文件到SPI目录下;

(3) 用文本编辑器建立4个文本文件,文件名分别为SPI.c,SPI.h,SPI.def和sources;

(4) 编辑目录C:\\Win CE420\\PLATFORM\\ep931x\\driver下的dirs文件。用文本编辑器打开该文件,找到“DIRS=”等式,在该等式最后添加一行, 如下面所示:

DIRS=…

SPI

(5) 在Platform Builder 4.2中打开Platform.bib文件,在该文件最后和FILES之前加入一行,指明在生成Windows CE内核映射时自动将SPI.dll加入到内核映像中,添加内容如下:

SPI.dll MYM(_FLATRELEASEDIR)\ SPI.dll NK SH

(6)具体的流接口驱动程序跟注册表密不可分,在Platform Builder 4.2中打开platform.reg文件,在该文件最后加入如下所示注册表信息,以使在生成操作系统映像时,Platform Builder将注册表信息加入到注册表中。在Platform.reg中添加内容如下:

[HKEY_LOCAL_MACHINE\\Drivers\\BuiltIn\\SPI]

"Prefix"=" SPI "

"Dll"=" SPI.dll"

"FriendlyName"=" SPI Driver"

"Index"=dword:1

"Order"=dword:0

驱动程序篇9

关键词: μC/OS-II;驱动设备管理;串口驱动

中***分类号:TP316文献标识码:A文章编号:1009-3044(2007)06-11649-02

1 引言

uC/OS-II 是一个简单、高效的嵌入式实时操作系统内核,被应用到各种嵌入式系统中。目前,它支持 x86、ARM、PowerPC、MIPS 等众多体系结构,并有上百个商业应用实例,其稳定性和可用性是经过实践验证的。uC/OS-II仅仅是操作系统内核,没有象其它嵌入式操作系统一样提供了文件系统管理和驱动设备管理部分,所以对于在uC/OS-II操作系统下开发应用程序的人员来说,应用程序是直接调用设备的底层函数,这就需要开发应用程序的人员能够比较了解底层的细节,一旦硬件设备较多,需要访问硬件设备的应用程序较多,应用程序开发人员要自己解决驱动程序的管理问题就比较麻烦。而拥有了驱动设备管理模块,驱动程序的安装可交给底层开发人员来完成,应用程序的开发人员就可以通过统一的接口函数来访问底层,同时也方便多个设备驱动程序的统一管理。

2 驱动设备管理模块的设计

驱动设备管理模块相当于操作系统和底层设备驱动程序的桥梁,一方面底层的相关函数在执行的时候可能需要操作系统提供的资源,另一方面,上层应用程序是通过操作系统来获取对底层设备的信息和对其操作的权力,在设计中,建立了驱动设备列表和驱动程序描述表来实现上述两个主要功能。下面对这两个表作详细介绍:

驱动设备列表是一个双向链表,驱动设备列表的大小是动态的,因此可以任意向其中添加新设备,应用程序通过设备来访问驱动设备列表,来找到该设备在驱动程序描述表对应的索引号,再从驱动程序描述表里面找到该设备的底层操作函数。链表设备的结构体定义如下:

typedef struct dlnode{

struct dlnode *next;

struct dlnode *previous;

unsigned short drvNum;

DEV_HDR *head;

} DL_NODE;

驱动设备列表程序中提供了3个主要功能函数供底层开发人员和应用程序的开发人员使用

(1)添加设备 dlladd(unsigned char drvName )

此函数向链表中添加新的节点,与此同时也会向操作系统申请操作该设备的需要的信号量的资源,以及存储数据所用的缓冲区,申请缓冲区的大小是由该设备的特性所决定,这个可以由底层设计人员在设备加入函数中设定。

(2)卸载设备 dlldelect(unsigned char drvName )

当开发人员决定卸载这个设备的时候就调用此函数,它在链表中删除这个设备对应得节点,并且释放该设备申请的各种资源。

(3)查找unsigned short dllGet(UNIT8 drvName)

这个函数主要是提供给应用程序的开发人员使用,对于应用程序的开发人员来说通过设备名来查找设备索引号。

驱动程序描述表主要是存储驱动程序底层函数的入口点,它不同于驱动设备列表,结构上来说它不是动态的,它向操作系统申请了固定的空间来存储这个表,故这个表的大小由底层开发人员根据可能需要的最多的设备数量来设定,整个表由多个结构体DRV_ENTRY组成,表的定义为DRV_ENTRY DrvTable[MAX_DEV];每个设备都是以结构体DRV_ENTRY的形式在表中存储,如果一个设备对应DrvTable中的第i成员DrvTable[i] ,那么I 就是它的索引号结构体定义如下:

typedef struct {

unsigned char drvName;

unsigned char in_use;

FUNCPTR de_open ;

FUNCPTR de_close ;

FUNCPTR de_read ;

FUNCPTR de_write ;

FUNCPTR de_ioctrl ;/ *de_open,……de_ioctrl就是该设备对应的不同底层函数的入口*/

}DRV_ENTRY ;

驱动程序描述表的主要功能是应用程序根据设备索引号来找到该表中对应的设备,并且找到该设备的底层函数入口,该设备的底层函数入口是在设备安装到表里的时候加入的,用户调用该安装程序传入实参来安装各种设备的驱动程序,设备安装函数如下:

unsigned char DrvInstall

(unsigned char i, FUNCPTR popen, FUNCPTR pclose,FUNCPTR pread, FUNCPTR pwrite, FUNCPTRpioctrl) // 传入的参数为底层函数

{ unsigned char *err;

unsigned char i ;

DRV_ENTRY *pDrvEntry= NULL;

OSSemPend (pdevtable_event, WAIT_FOREVER, err);/*此信号量在添加设备函数 dllall中定义并初始化*/

if(*err == OS_NO_ERR)

return(FALSE);

else{

pDrvEntry = &DrvTable[i];//查找与索引号对应的设备

pDrvEntry->in_use = TURE;

pDrvEntry->de_open = popen; // 将底层函数的入口放在表中pDrvEntry->de_close= pclose;

pDrvEntry->de_read = pread;

pDrvEntry->de_write = pwrite;

pDrvEntry->de_ioctrl = pioctrl;

OSSemPost (pdevtable_event); // 释放该信号量

if(*pDrvEntry != NULL)

return(FALSE);

else

return(TRUE);} }

除了设备安装函数之外,还有设备删除,显示设备信息等函数,它们都是对驱动程序描述表的操作,结构和设备安装函数类似。

3 驱动设备管理功能的具体应用

3.1 串口驱动程序简介

串行通信是两个设备之间的一种通讯标准,两个设备通过收、发数据信号线线进行全双工数据通讯,数据在一根数据信号线上一位一位地进行传输,每一位数据都占据一个固定的时间长度。这种通信方式的特点是,使用的数据线少,在远距离通信中可以节约通信成本,其传输速度比并行传输慢。串口驱动程序主要是对设备的串行接口进行控制,使其能够通过串行接口与通讯另一方进行数据交换,它的分5个主要部分组成:串行接口的初始化,串行接口的关闭,读取串行接口的数据,向串行接口发送数据,串行接口的通讯参数设定,这5个部分的功能就对应open,close ,red ,write ,ctrl这5个底层通用接口函数,在NEC PD78F0376 平台下分别UART_init,UART_close,UART_read,UART_SendChar,UART_ctrl。其中UART_read为读取串行接口的数据操作,它的具体结构如下:

void UART_read(unsigned char *p)

{while(buffer.status != RX_FULL);

//检查缓冲区标志位,看是否已经满

DI(); //进入临界区

memcpy(p, buffer.buffer,UART_BUFFERSIZE);

//接收缓冲区数据

EI();//退出临界区

prx_uart = buffer.buffer;

//重新将接收缓冲区数据的指针移到缓冲区开始的位置

}UART_read为向串行接口发送数据,它的具体结构如下

UART_SendChar (unsigned char *ucData){

TXB6 = *ucData;//将要传输的数据写入到发送寄存器

while(!STIF6); //等待传输标志位指示传输完成

STIF6=0;//清零传输标志位

3.2 将串口驱动程序加入操作系统中

这个部分由底层开发人员完成,它的主要工作有:初始化驱动设备列表,在驱动设备列表中加入一个名叫UART的设备 dlladd(UART ),在这个函数在申请系统资源的同时,返回该设备地索引号,并将在驱动程序描述表中该设备的结构体inuse状态置为TURE,该设备在驱动设备列表和驱动程序描述表中已经建立,执行DrvInstall(UART ,UART_init,UART_close,UART_read,UART_SendChar,UART_ctrl),这个步骤是将串口驱动程序函数的入口加入到驱动程序描述表中,最后要将串口接收的中断服务程序加入到操作系统中去。

3.3 应用程序通过设备驱动程序管理模块访问设备

应用程序需要知道要访问的设备的设备名,访问串口设备执行dllGet(UART),该函数返回该设备在驱动程序描述表中的索引号I,根据索引号执行DrvFind(I),该函数返回驱动程序描述表与该设备对应的设备入口结构体drventry;在这个结构体中就有该设备的底层函数入口,要执行设备相应的操作如打开,调用Drvopen (drventry)即可,这对应用程序开发者来说是较为方便的

4 结语

文章给出的设备驱动程序管理模块其作用是为上层控制设备驱动程序带来方便,并不针对某个特定的开发环境,仅要求在μC/OS-II操作系统下,具有通用性,实际的底层开发人员还需要根据硬件环境的不同自己编写open,close ,red ,write ,ctrl等底层操作函数。

参考文献:

[1]周启平,张扬. VxWorks下设备驱动程序及BSP开发指南[M].北京: 中国电力出版社,2004.

[2]任哲. 嵌入式实时操作系统μC/OS-Ⅱ原理及应用[M].北京: 北京航空航天大学出版社, 2005.

[3](美)Jean J.Labrosse,邵贝贝译.嵌入式实时操作系统μC/OS-II(第2版).北京: 中国电力出版社,2001.

[4]刘乐善,叶济忠,叶永坚.微型计算机接口技术原理及应用.武汉: 华中理工大学出版社,2000.

[5]孙涵芳 Intel 16位单片机[M].北京: 北京航空航天大学出版社.

[6]J. Glenn Brookshear. Computer Science: An Overview, Sixth Edition, 北京:人民邮电出版社,2003.

驱动程序篇10

关键词: 嵌入式Linux; I2C设备; I2C总线; 设备驱动程序

中***分类号: TN919?34 文献标识码: A 文章编号: 1004?373X(2013)16?0038?03

0 引 言

由于I2C总线的通用性,Linux作为一款优秀的嵌入式操作系统[1?2],也必须要对其要有很好的支持。在Linux内核源码中对I2C总线的驱动[3?4]是基于总线设备驱动模型的,其驱动程序用到了特殊的几个数据结构,对I2C总线协议进行了更抽象更通用的定义,极大的增加了设备驱动的可移植性。要写编写出自己的I2C设备驱动程序,必须对这种内核I2C总线驱动的架构有深刻的理解。

1 I2C总线的硬件构成

I2C总线协议只有两条总线线路,一条是串行数据线(SDA),一条是串行时钟线(SCL)。SDA负责数据的传输,SCL负责数据传输的时钟同步。I2C设备通过这两条总线连接到处理器的I2C总线控制器[5?6]上,不同设备之间通过7位地址来区别,而且数据的传输是双向的,方向的确定由1位二进制数确定,地址位加方向位是操作I2C设备的惟一标示,I2C设备与CPU的连接如***1所示。

I2C总线上有3种类型的信号,分别是:开始信号,结束信号和应答信号。这些信号都是由SDA和SCL上的电平变化来表示的。

开始信号(S):当SCL为高电平时,SDA由高电平向低电平跳变,表示开始传输数据。

结束信号(P):当SCL为高电平时,SDAY由低电平向高电平跳变,表示结束传输数据。

相应信号(ACK):从机接收到8位数据后,在第9个时钟周期,拉低SDA电平,表示已经接收到数据。

当总线空闲时,SDA和SCL都处于高电平,主机检测到总线空闲就可以向从机发送数据。主机首先发送开始信号S,接着发出8位数据(包括前7位的从机地址和1为的方向位),然后等待从机发回确认信号ACK。当第8位为0时,表示向从机传输数据,主机收到确认信号后就可以连续的向从机写入8位数据;当第8位为1时,表示向从读取数据,这时主机就可以接收来自从机的一系列数据。最后当总个数据传输过程完成后,由主机发送结束信号P,表示本次的数据传输完成。

2 Linux的I2C设备驱动程序的层次结构

因为I2C设备的种类繁多,如果为每一款I2C设备都编写一个驱动程序,显然不太现实也不太可能做到。所以,Linux中是对I2C设备驱动采取了层次化处理,分为总线层和设备层。将I2C设备驱动的一些共同属性抽象起来归结起来作为总线层,而将具体I2C设备特殊操作作为设备层。在Linux中I2C设备驱动中用到的数据结构[4,7?8]的关系如***2所示。关于这部分代码位于Linux内核源码树的/driver/i2c中。

理解这层次结构重点是要理解4个数据结构,分别是属于设备层的i2c_driver与i2c_client,属于总线层的i2c_adapter与i2c_algorithm。下面分别对这四个数据结构做简要的说明。

struct i2c_driver:具体的每一个I2C设备都应该对应着的一个驱动,这个结构体里面定义了Linux设备模型中用于I2C总线管理的一系列函数指针和I2C设备的信息。其中最重要的两个成员是适配器检测函数指针attach_adapter,和设备ID表id_table。

struct i2c_client:一个连接在SDA和SCL总线上的具体设备是由i2c_client结构体描述的,定义了两个成员变量表示这个具体设备所对应的适配器和驱动。

struct i2c_adapter:此结构体表示CPU里面具体的I2C控制器,本质上也是对应着一个物理设备,其中最要的成员变量是指向适配器驱动的程序的algo结构体指针。

struct i2c_algorithm:里面定义了具体适配器驱动程序的函数指针。特别是master_xfer函数指针,这个函数实现了适配器最底层的操作方法,也是I2C设备驱动中总线层里面要编写的重要函数。

i2c_dev里面定义了读写I2C设备应用层的读写接口,但由于其缺少通用性,一般很少用到所以并不做详细的介绍。

i2c_core在驱动框架中起到了承上启下的作用,里面定义了许多重要的函数。例如:adapter注册/注销函数,增加/删除设备驱动函数,增加/删除I2C设备的函数,I2C传输,发送和接收函数。这些函数都是在编写I2C设备驱动程序中必须要用到的接口函数,正是由于这些通用的接口函数才使得代码具有很强的可移植性和重用性。

3 编写I2C设备驱动的思路

在了解Linux中I2C设备驱动的基本框架后,要编写自己的设备驱动首先要弄清楚的一个问题是到底内核已经实现了那部分,需要实现的又是那部分。因为I2C设备驱动是基于总线设备驱动模型的,一般而言在移植Linux操作系统中,Linux内核已经对总线部分已经有了很好的实现,所以总线部分的驱动一般可以不必关心。在此需要实现的是设备层的i2c_driver与i2c_client结构体,并利用I2C子系统提供的接口函数挂接到I2C总线上。

每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象[8?9],该结构体包含了I2C设备探测和注销的一些基本方法和信息。其中包括设备驱动的名字,适配器的挂接/取消函数指针等。一个例子如下所示,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detach_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数。

static struct i2c_driver xxx_i2c_driver = {

.driver = {

.name = "xxx_i2c_driver",

},

.attach_adapter = &xxx_attach_adapter,

.detach_client = &xxx_detach_client,

mand = NULL,

};

上面定义的i2c_driver对象,抽象为一个I2C的驱动模型,提供对I2C设备的探测和注销方法,接下来就是要定义i2c_client结构体,其代表着一个具体的I2C设备,该结构体有一个data指针,可以指向任何私有的设备数据,在复杂点的驱动中可能会用到。

每一个I2C设备芯片,都通过硬件连接设定好了该设备的I2C设备地址。因此,I2C设备的探测一般是靠设备地址来完成的。那么,首先要在驱动代码中声明你要探测的I2C设备地址列表以及一个宏,示例如下:

static unsigned short normal_i2c[] = {

0xbc >> 1, //设备节点的地址

I2C_CLIENT_END };

I2C_CLIENT_IN***OD;

有了i2c_client结构体代表了具体的设备和设备ID后就可以实现attach_adapter和detach_client 函数。这两个函数是系统自动调用的,它的实现是有一定的框架的,可以在linux内核源码的驱动例子中找到,由于代码过长这里不做具体的分析。针对不同的设备函数的实现会略有不同,一般attach_adapte需要完成的工作是对i2c_client结构体成员赋值和调用接口函i2c_attach_client把设备挂接到适配其中。而detach_client 函数则是完成相反的工作。

最后的一步是编写模块的初始化与退出函数把驱动加进I2C驱动子系统中,示例可以是:

static int __init xxx_i2c_init(void) {

return i2c_add_driver(&xxx_i2c_driver); }

static void __exit xxx_i2c_exit(void) {

i2c_del_driver(&xxx_i2c_driver); }

至此,I2C设备的驱动已经完成了,但是到了这一步本驱动并没有实际的用处,它仅仅提供的是一个设备驱动程序的管理框架,所以必须还要进行两方面的补充。第一方面是,利用I2C总线读写外部芯片的控制/状态寄存器;第二方面是,向应用层提供I2C设备的读写接口,令应用程序可以对设备节点的读写实现对I2C具体物理设备的读写[10]。为了实现I2C设备寄存器的读写操作,必须要用到Linux的I2C子系统提供的读写接口函数:

extern int i2c_master_send(struct i2c_client *,const char* ,int); //从I2C总线发数据

extern int i2c_master_recv(struct i2c_client *,char* ,int); //从I2C总线收数据

利用这两个函数根据芯片的读写时序进行封装,就可以读写芯片内部的寄存器,以写芯片寄存器为例,必须写往总线上写寄存器的地址,然后写入要往寄存器里写入的数据,示例代码如下所示。读寄存器的时序则是则是先写入要读寄存器的地址,然后接受总线上的数据,区别不大,不做示例。

static int tvp5158_i2c_write( struct i2c_client* client,uint8_t reg,uint8_t data) {

unsigned char buffer[2];

buffer[0] = reg;

buffer[1] = data;

if( 2!= i2c_master_send(client,buffer,2) ) { //错误处理

printk( KERN_ERR " tvp5158_i2c_write fail! \n" );

return-1; }

return 0;

}

要想向应用层提供读写接口,则必须再对I2C设备驱动进行一次简单字符设备驱动的封装,将I2C设备作为一个简单字符设备,依次实现字符设备文件操作函数结构体file_operation里面的函数指针所对应的接口函数,这里只给出了大体的框架,具体的实现对于不同的芯片有很大的不同。

定义一个字符设备结构体cdev,将I2C设备当做一个普通的字符设备处理

struct xxx_i2c_device{

struct cdev cdev;

//设备其他的数据部分};

定义一个文件操作函数结构体,填写里面函数指针,指出设备操作所对应的具体函数,一般的例子是:

struct file_operations xxx_dev_fileops = {

.owner = THIS_MODULE,

.open = xxx_devOpen,

.release = xxx_devRelease,

.ioctl = xxx_devIoctl, };

接着就是编写file_operations所对应的具体函数。

最后一步是在模块的初始化和退出函数中增加对简单字符设备的注册和注销操作,包括设备号申请与注销,设备注册与注销两方面。

至此,将编译好的模块加载进内核后就可以在用户空间利用文件系统的API对设备文件进行各种操作。

4 结 语

I2C总线在电子系统设计中是十分普遍的一种接口技术,而Linux又是十分流行的嵌入式操作系统。编写嵌入式Linux下I2C总线设备的驱动驱动程序是嵌入式开发中十分重要的一项技术,不容忽视。本文首先讲述了Linux系统I2C设备驱动程序的总体框架,然后给出了编写I2C设备驱动的总体思路与框架,其中包括设备驱动,访问I2C设备寄存器,和向应用层提供访问接口三方面。希望给读者理清思路,加深对编写I2C设备驱动的理解。

参考文献

[1] 李桦,高飞,孙磊.嵌入式Linux设备驱动程序研究[J].微计算机信息,2010(14):68?70.

[2] 钱晨,徐荣华,王钦若.基于Linux操作系统的设备驱动程序开发[J].微计算机信息,2004(9):131?133.

[3] Philips Corporation. I2C bus specification, version 2.1 [R]. Netherland: Philips Corporation, 2000.

[4] Samsung Electronics. S3C2410A 200 MHz & 266 MHz 32?bit RISC microprocessor user’s manual, revision 1.0 [R]. South Korea: Samsung Electronics, 2004.

[5] 王莹.Linux下基于I2C总线的CAT1025设备驱动实现[J].枣庄学院学报,2011(2):87?91.

[6] 董志国,李式巨.嵌入式Linux设备驱动程序开发[J].计算机工程与设计,2006(10):3737?3740.

[7] 叶顺流.基于ARM的嵌入式Linux研究与实现[D].重庆:重庆大学,2005.

[8] 江泽涛,吴俊安.用Kylix实现Linux环境下的串行通信[J].计算机应用研究,2005,22(9):214?216.

转载请注明出处学文网 » 驱动程序10篇

学习

下半年采购工作计划

阅读(11)

本文为您介绍下半年采购工作计划,内容包括采购半年总结及下半年计划,采购下半年工作计划及目标。部队士兵半年工作总结通用2021公司上半年工作总结范文5篇部队个人半年总结2000字***建工作上半年工作总结2021年前半年工作总结范文10篇采

学习

国产喜剧10篇

阅读(12)

本文为您介绍国产喜剧10篇,内容包括国产喜剧十年,国产抗日喜剧。关于国产喜剧电影的再思考当前国产喜剧电影虽然数量不少,但其样式却较为单一,有学者将其分为三类:“第一类是古装喜剧,以《武林外传》、《十全九美》为代表,此类样式的影片占

学习

三维动画设计10篇

阅读(21)

本文为您介绍三维动画设计10篇,内容包括三维动画设计实例教程,做三维动画设计的电脑配置。三维动画可从多视角将工程建筑的结构、材质、布置准确地表达出来,在模型基础上,能够任意观察坝工、厂房等专业建筑结构。相较于用工程***纸加上说

学习

猪周期10篇

阅读(16)

本文为您介绍猪周期10篇,内容包括猪周期解读大全,猪周期入门知识。近期进口增速平稳经历前几年的骤升骤降后,2012年以来肉进口量增长速度相对平稳,2012年前11个月累计进口47.8万吨,增加26.4%。从单月进口趋势来看,进口量从2011年12月8.9万吨

学习

手绘画10篇

阅读(21)

本文为您介绍手绘画10篇,内容包括手绘画的作品简介,手绘画的所有过程。华新六年级:辛一婷

学习

赠与公证书

阅读(14)

本文为您介绍赠与公证书,内容包括赠与公证书一般几份,赠与公证书怎么写模板。赠与人、受赠人可以分别为公民、法人或其他组织。第三条赠与公证是公证处依法证明赠与人赠与财产、受赠人收受赠与财产或赠与人与受赠人签订赠与合同真实、合

学习

黑色喜剧10篇

阅读(17)

本文为您介绍黑色喜剧10篇,内容包括黑色喜剧排行榜前十名,黑色喜剧榜。一、怪诞先说怪诞。怪诞不是荒诞。怪诞在戏剧中的运用涉及到技巧与美学问题,极其复杂,是需要大厚本的专著来研究与阐发的。我们择其要者,取其皮毛略作说明。怪诞最早是

学习

银行凭证管理工作总结

阅读(17)

本文为您介绍银行凭证管理工作总结,内容包括银行重要凭证管理岗位的总结,银行凭证管理工作工作总结。一、会计科目的特点会计科目是对会计对象进一步分类的标志或项目。它是会计人员设置账户和登记账簿的依据,它为编制财务报表提供了条件

学习

作风建设

阅读(13)

本文为您介绍作风建设,内容包括作风建设标题金句,作风建设心得体会。

学习

历史周期律10篇

阅读(14)

本文为您介绍历史周期律10篇,内容包括历史周期律问题大全,历史周期律的启示。可以说,的群众观既是一种坚信“人民,只有人民,才是创造世界历史的动力”的科学的世界观和历史观,同时又包含着一种总体原则性的行动方针,即群众路线,就是“一切为了

学习

vb程序设计10篇

阅读(13)

本文为您介绍vb程序设计10篇,内容包括vb程序设计论文官方完整版,vb程序设计实验心得4篇。表1“时钟的时针和分针重合的问题”程序用到的控件及其属性说明2.算法分析。①请输入这次大奖赛的满分分数(如10分或100分等等)以及评委的人数,赋值

学习

欢乐喜剧10篇

阅读(18)

本文为您介绍欢乐喜剧10篇,内容包括欢乐喜剧锦集,欢乐喜剧人。

学习

三维打印10篇

阅读(14)

本文为您介绍三维打印10篇,内容包括三维打印的四个过程,三维打印全过程。与传统的生产技术相比,三维打印技术有着自己独特的魅力:通过减少对工人的技术性需求,放弃多余的生产线,减少生产材料的浪费和增加工厂的生产能力等特点改变企业的经济

学习

环境卫生整治简报

阅读(14)

本文为您介绍环境卫生整治简报,内容包括环境卫生整治简报怎么写,环境卫生整治工作简报名称。他强调,村组要组织和依靠群众力量,尽快建立长效机制,下大力气开展乡村环境卫生整治工作,从主干道的清扫保洁到乱堆乱放的清理整治,从垃圾日产日清到