pci设备的wdm驱动程序设计探讨与研究.docx
- 文档编号:9707920
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:24
- 大小:417.55KB
pci设备的wdm驱动程序设计探讨与研究.docx
《pci设备的wdm驱动程序设计探讨与研究.docx》由会员分享,可在线阅读,更多相关《pci设备的wdm驱动程序设计探讨与研究.docx(24页珍藏版)》请在冰豆网上搜索。
pci设备的wdm驱动程序设计探讨与研究
DriverStudio是一套NuMega公司为简化Windows设备驱动程序和应用程序而开发的开发、编译和调试的软件工具包。
其中的DriverWorks以面向对象(OOP)的方式,将WDM和NT下的驱动程序编写所需的与内核访问及对硬件的访问封装成类,加上设计的驱动程序代码生成向导,大大简化了驱动程序开发的难度,减少了工作量。
同时,DriverWorks被嵌入到VC中,方便了开发。
本文以DriverWorks为工具,介绍开发PCI设备驱动程序的开发。
1.利用向导生成程序框架和设备配置信息
在驱动程序向导中,生成了驱动程序的基本框架和用户自定义信息,虽然没有实现PCI设备的具体功能,但对于功能的实现设置了框架,功能代码都在该框架下完成,因此需要详细说明。
打开VC,出现DriverStudio嵌入的工具栏,如图1。
点击第一个按钮“LaunchDriverWorksDriverWizard”,打开了先导的第一页如图2。
图1DriverWorks工具栏
图2NT/WDM设备驱动程序向导第一步
在该页中填写驱动程序工程的名称和路径,点击“Next”进入下一页,如图3。
图3NT/WDM设备驱动程序向导第二步
在该页中选择驱动程序的运行系统。
有两个选项:
WindowsNT4.0和WDM,分别对应的是WindowsNT4.0和Windows98/Windows2000。
这里选择WDM。
点击“Next”进入下一页,如图4。
图4NT/WDM设备驱动程序向导第三步
在该页中,选择设备的类型并填写相应信息。
选择“PCI”,填写PCI设备的设备标识符。
“PCIVendorID”是厂商标识符;“PCIDeviceID”为设备标识符;“PCISubsystemID”为子系统ID号;“PCIRevisionID”为修订号。
这些信息由PCI设备在PCI总线配置时提供给系统的,供系统识别PCI设备的,系统根据这些信息来匹配已注册的PCI设备的驱动程序。
一般只用到“PCIDeviceID”和“PCIDeviceID”,不可为零。
“PCISubsystemID”和“PCIRevisionID”可为零,表示忽略。
这些信息必须与PCI设备的硬件是一致的。
点击“Next”进入下一页,如图5。
图5NT/WDM设备驱动程序向导第四步
DriverWorks将PCI设备驱动程序封装成三个类:
驱动程序类、设备类和IRP排队处理类。
驱动程序类处理驱动程序的加载和卸载等工作,设备类实现对PCI设备的具体功能实现,如驱动程序的PNP处理、设备功能实现、中断处理、电源管理等,IRP排队处理类处理驱动程序的IRP排队串行处理,有两种类型:
系统管理的和驱动程序管理的。
在该页中,填写驱动程序类的名称和文件名称,一般不必修改,与工程名称相同即可。
点击“Next”进入下一页,如图6。
图6NT/WDM设备驱动程序向导第五步
在该页中,选择需要驱动程序处理的分发例程。
“Read”和“Write”项对应的是应用程序调用驱动程序的ReadFile和WriteFile函数,这两个函数都是单向的,当处理数量较大的数据最好使用。
“DeviceControl”对应的是应用程序的DeviceIoControl函数,该函数处理用户自定义的IOCTL,可以向驱动程序发送数据和返回数据。
“Flush”项提供刷新I/O缓冲区的例程,一般用不到,可不选。
“InternalDeviceControl”项提供其它驱动程序的调用,一般的PCI设备驱动程序无需与其它驱动程序通信,可不选。
“Cleanup”项提供了清除所有IRP的例程,当驱动程序停止或卸载之前,需要清楚所有的IRP,保证驱动程序安全地停止。
其他两项为默认。
点击“Next”进入下一页,如图7。
图7NT/WDM设备驱动程序向导第六步
在该页中选择IRP串行处理的类型和串行处理的函数。
在对PCI设备的操作中往往不能将应用程序的请求立刻处理,或有多个应用程序同时请求,因此需要将这些请求进行串行化,使对IRP的处理不至于混乱。
如果驱动程序可以立即将IRP处理完毕,就不必串行化。
分发例程可以将IRP进行排队,交给统一处理IRP的例程StartIo串行处理。
串行的处理例程有两种类型:
驱动程序自行处理和系统处理。
驱动程序可以设置多个StartIo处理函数,对于PCI设备来说,一般只对一个设备操作,只需一个StartIo处理。
一般处理ReadFile和WriteFile函数的分发例程需要进行IRP排队,在此全部选中。
点击“Next”进入下一页,如图8。
图8NT/WDM设备驱动程序向导第七步
在该页中,添加需要存储在系统注册表中的内容。
当驱动程序开始运行后,驱动程序就将信息从注册表中读出,当驱动程序卸载后,就存入注册表中。
点击“Next”进入下一页,如图9。
图9NT/WDM设备驱动程序向导第八步
在该页中,设置PCI设备类的信息。
可以重新设置设备类的名称。
在“Resources”项中,设置PCI设备的硬件资源。
点击“AddIOPort(s)”设置PCI设备分配的I./O端口,如图10。
图10I/O端口设置
“Name”是对应I/O端口资源的变量,DriveWorks将端口访问的操作封装成类KIoRange。
对于每个映射为I/O端口的PCI地址映射空间,都需要定义一个变量,负责管理I/O端口的信息和操作。
在“PCIBaseAddress”项中,选定PCI地址映射空间。
在“Shareoptions”中,选定访问的限制范围,对于PCI设备,其他驱动程序和系统没有兴趣来访问别的设备,另外考虑安全性,防止非法操作,一般选择“Exclusivetothisdevice”。
点击“AddMemoryRange”设置内存资源,如图11。
图11内存设置
内存设置与I/O端口设置类似,DriverWorks将对内存的操作封装成类KMomeryRange。
点击“AddIRQ”设置中断服务程序,一个PCI设备最多可以有4个中断源,每个诊断源需要一个服务程序。
但一般的PCI设备只有一个中断源,这里只设置一个。
如图12。
图12设置中断资源
DriverStudio将对中断的处理封装成了类KInterrupt,同时将DPC也封装成类KDeferredCall。
如果不需要DPC,可以不选择“CreateDPC”。
其他中断的信息将在下面说明。
同时,对DMA的操作也封装成类KDmaAdapter,点击“AddDMA”设置DMA内容,如图13。
图13设置DMA资源
一般的主模式PCI设备都支持“BusMasterDMA”和“Scatter/Gethersupport”,即总线主控能力和分散/集中能力。
共享选项选择“Exclusivetothisdevice”。
这些PCI设备类的信息设置好后,进入下一页,如图14。
图14NT/WDM设备驱动程序向导第九步
在该页中添加自定义IOCTL,其对话框如图15。
图15添加自定义IOCTL
首先填写IOCTL的名称,然后填写功能代码的系列号,0x000~0x7FF为系统保留,0x800~0xFFF为用户代码。
“Method”项为数据传输的类型,可选Buffered、IN_DIRECT、OUT_DIRECT或Neither。
参数Buffered适合少量的数据的传输;IN_DIRECT或OUT_DIRECT适合有大量的需要快速处理的应用DMA或PIO的数据上行或下行的传输;参数Neither很少使用,在PCI设备驱动程序中更不使用。
这些参数在AddDevice中定义后,在驱动程序不可再更改。
“Access”项定义应用程序访问IOCTL的权限类型,可选Any、Read、Write、Read/Write。
如果IOCTL需要进行串行化,选择“Queue(Serialize)thisrequest”。
图16其他设置
驱动程序一般需要测试应用程序来连接驱动程序,并进行测试,DriverWorks可以直接生成一个简单的控制台应用程序。
DriverWorks还可以在代码中生成调试代码等一些辅助代码和信息,在该页中可以选择,建议都选择,反正再生成Free版的驱动程序就会自动去掉这些代码。
点击“Finish”,驱动程序框架就生成好了。
由于DriverWorks将DDK中定义的驱动程序模型构架封装成了类,在VC中可以看到,有两个工程,一个驱动程序工程和测试应用程序工程。
在驱动程序工程中,如果在第六步中选择了“DriverMeneged”的DPC类型将有三个类:
从基类KDriver派生的PCIDemo类、从基类KPnpDevice派生的PCIDemoDevice类和从基类KDriverManagedQueueEx派生的PCIDemoDevice_DriverManagedQueue类,如果选择了“SystemMenaged”的DPC类型就只有前两个类。
PCIDemo类负责驱动程序的装载和卸载,处理的内容包括对应的DDK中的DriverEntry、AddDevice和Unload例程(封装,没有显示)。
PCIDemoDevice负责PCI设备的功能实现,从设备的Pnp处理、电源管理、分发例程处理和中断处理等,主要的代码编写都在该类中。
如果没有对分发例程进行串行化,就没有类PCIDemoDevice_DriverManagedQueue,该类管理StartIo例程。
由于WDM驱动程序的机制基本是系统内核回调例程的方式,因此有些例程之间的关系不明显,但是又互相联系紧密,这在实现具体功能时,再分析。
2.PCI设备的配置空间的访问
当驱动程序启动后,如果PNP管理器检测到PCI设备,就会发送IRP_MN_START_DEVICE的IRP给驱动程序处理。
由于PCI总线为PnP总线,PCI设备的硬件资源都是有PCI总线配置机构动态分配的。
驱动程序必须先将该IRP交给PCI总线驱动程序处理,取得PCI设备的配置信息后,驱动程序再处理该IRP,取得PCI设备的资源,进行分配和处理。
该IRP在类PCIDemoDevice中对应的函数为OnStartDevice。
如果在先导中设置好了PCI设备的资源,就不必再处理了,先导已把资源处理完毕。
因此,在一般情况下驱动程序无需再访问PCI设备的配置空间,但是如果需要访问配置空间可通过类KPciConfiguration。
在WDM中,访问PCI设备配置空间通过向PCI总线发送读写配置空间的IRP来实现的,该类封装了这些操作,使配置空间的操作非常方便。
KPciConfiguration在使用之前,需要初始化,使之连接到访问的设备上。
对于WDM,传递在物理设备对象栈的栈顶的设备对象。
如下:
KPciConfigurationPciDemoConfiguration(m_Lower.TopOfStack());
其中m_Lower是KpnpLowerDevice类的实例,在AddDevice例程中初始化后,连接到该驱动程序上。
函数TopOfStack返回当前设备堆栈顶部的设备对象。
对KPciConfiguration或者调用函数Initialize也可。
函数ReadHeader/WriteHeader、ReadBaseAddress/WriteBaseAddress等函数对应访问PCI设备的配置空间,具体请常见DriverWorks的帮助文档。
3.I/O端口的访问
类KIoRange封装了对I/O端口的操作。
对PCI设备首先需要在函数OnStartDevice函数中取得I/O资源,然后初始化。
如下:
status=m_IoPortRange1.Initialize(
pResListTranslated,//转换后的资源列表指针
pResListRaw,//原始的资源列表指针
PciConfig.BaseAddressIndexToOrdinal
(1)//‘1’代表分配的I/O资源在PCI基地址的序数,PCI设备最多可以有6个内存或I/O资源空间。
);
参数pResListTranslated和pResListRaw在DDK中从IRP_MN_START_DEVICE的IRP中取出。
DriverWorks封装了IRP为KIrp。
在OnStartDevice函数中由函数AllocatedResources和TranslatedResources得到上述参数。
在WDM中,对于I/O端口,系统将其看成寄存器,必须将I/O资源分配成可由系统访问的非分页内存,函数Initialize完成该工作。
在处理资源的代码中必须进行运行错误处理,否则就会在驱动程序的运行中,引起系统崩溃。
在其他例程中,就可以调用类KIoRange的成员函数inX/outX函数进行访问PCI设备。
这些函数没有运行级别的限制,可自由使用。
例如:
VOIDoutb(
ULONGByteOffset,//以字节为单位的地址偏移量
PUCHARBuffer,//指向输入数据的指针
ULONGCount//输入数据的字节数
);
当驱动程序停止运行之前,一定要卸载资源,对于I/O端口调用成员函数Invalidate。
4.内存的访问
类KMemoryRange封装了对PCI设备的映射内存的访问。
在WDM中,驱动程序得到的只是PCI总线配置机构分配的物理内存,如果在驱动程序中是无法使用的,必须将其转换成系统可以访问的非分页内存,在DDK中,调用函数MmMapIoSpace。
在KMemoryRange的成员函数Initialize中实现这个转换。
在OnStartDevice函数中,如下:
status=m_MemoryRange0.Initialize(
pResListTranslated,
pResListRaw,
PciConfig.BaseAddressIndexToOrdinal(0)
);
函数的参量同I/O端口的操作。
KMemoryRange的其他成员函数操作同I/O端口的操作。
5.中断的处理
在DDK中,中断资源的获得也是在IRP_MN_START_DEVICE的处理中,然后调用函数IoConnectInterrupt进行连接。
在DriverWorks中,将中断的处理封装成类KInterrupt和宏MEMBER_ISR。
其应用如下:
在类PCIDemoDevice的定义中,
public:
DEVMEMBER_DISPATCHERS//自动声明用KDevice继承来的分发例程
MEMBER_ISR(PCIDemoDevice,Isr_Irq);//声明连接的中断服务例程
BOOLEANIsr_Irq(void);//声明中断服务例程函数
protected:
KInterruptm_Irq;//中断变量
在OnStartDevice函数中,调用KInterrupt的成员函数InitializeAndConnect初始化中断变量和调用宏LinkTo连接中断例程。
status=m_Irq.InitializeAndConnect(
pResListTranslated,//转换后的资源列表指针
LinkTo(Isr_Irq),//连接中断服务例程
this
);
在初始化和连接中断之前,最好将PCI设备的中断源进行屏蔽,PCI设备一般会有中断管理寄存器,屏蔽其中断许可位。
当中断连接成功后,再将中断位打开。
并且一定要加运行错误处理,防止驱动程序将系统崩溃。
中断服务例程的框图如下:
图17中断服务例程框图
在中断服务例程中,首先必须根据硬件信息来判断该中断是否是自己的设备发出的。
这是因为PCI总线共享中断,系统在接收到中断后,顺序调用各个注册该中断资源的驱动程序的中断处理例程,如果有返回TRUE的例程,就代表该中断已处理,就不再调用其他例程,如果是返回FALSE的例程,则说明该中断没有处理,则继续调用其他的例程。
如果返回错误,就会扰乱系统,造成系统崩溃。
PCI设备一般都有寄存器来标志中断源的状态,可以通过访问这些寄存器来判断是否是自己设备的中断。
在中断服务例程中,相应的处理最好简洁快速,因为中断例程运行的级别很高,当有中断请求时,不但会打断应用程序的执行,而且会打断在硬件中断级以下的所有运行程序。
在WDM中,提供了DPC(DeferredProcedureCall)例程,将在中断例程中耗时的但不需要立即处理的任务延时处理。
比如,驱动程序接受应用程序的写PCI设备的数据,当写完后,硬件产生中断标志执行完毕,这时需要结束该IRP,就可以将结束IRP这个耗时的任务交给DPC完成。
对于DPC的操作也封装成类KDeferredCall,在PCI设备中一般会用到一个DPC来支持中断,其用法在函数OnStartDevice中与中断服务例程连接,如下:
m_DpcFor_Irq.Setup(LinkTo(DpcFor_Irq),this);
典型的中断-DPC用法的实例如下图:
图18中断处理过程示例
在该实例中,由应用程序调用函数WriteFile,将数据传递给驱动程序,驱动程序的类PCIDemoDevice的成员函数Write例程负责处理该IRP,由于需要中断的配合(假定),无法立即执行完毕,必须将IRP串行化,类PCIDemoDevice_DriverManagedQueue的成员函数StartIo如果没有其他任务,就开始处理该IRP,处理完毕后立即返回,但不能结束IRP,当PCI设备完成操作后,就会产生中断,在中断服务例程中把IRP交给类PCIDemoDevice的成员函数DpcFor_Irq,在DPC中处理完后结束该IRP。
当驱动程序停止运行之前,一定要调用类KInterrupt的成员函数Invalidate断开中断的连接,释放资源。
在DriverWorks中,只需在中断服务例程和DPC中增加功能代码,其他的先导已经都做好了。
6.DMA的处理
PCI设备根据总线访问能力分为主模式和从模式两种,主模式设备可以发起总线操作,而从模式只能接受总线操作。
在PC机中,主模式设备可以进行DMA操作,从模式没有该功能,但可依靠系统的DMA控制器进行DMA数据传输。
从模式设备应用DMA很少,这里只讨论主模式设备。
在Windows2000中的DMA传输基于如图19所示的抽象模型。
在这个模型中,计算机被认为有这样一组“映射寄存器”,它们在CPU的物理地址和总线地址之间做相互转换。
每个映射寄存器保存着一个物理页帧的地址。
硬件使用一个“逻辑的”或总线专有的地址来读写内存。
对驱动程序来说映射寄存器扮演了与页表项相同的角色。
图19DMA传输中的抽象计算机模型
Windows2000内核使用一个称为适配器对象的数据结构来描述设备上的DMA特征,并用它来控制访问潜在的共享资源,如系统DMA通道和映射寄存器。
在DriverWorks中,由类KDmaTransfer描述DMA适配器。
DMA数据传输的具体操作则是由类KDmaTransfer来实现的。
在DMA的传输中,PCI设备将数据直接传输到系统物理内存中,管理这些内存的方式有两种:
CommonBuffer和Packet。
第一种方式是在系统的物理内存中预先开辟一段地址连续的内存空间,是CPU和PCI设备都能对其访问,管理这种功能的是类KCommonDmaBuffer。
另一种方式是使用MDL(MemoryDescriptorList)描述的内存空间来作为DMA传输数据的源或目标。
如果驱动程序需要不断地进行DMA传输,最好使用CommonBuffer方式;如果驱动程序间断性的使用DMA则可以使用Packet方式,因为该方式每次的传输使用的物理地址不一样,而CommonBuffer方式使用的物理地址在驱动程序的一次运行期间都是一样的,适合不断的DMA传输。
当DMA启动命令交给系统后,系统就开始进行分配映射寄存器等工作,当完成后,系统调用驱动程序的自定义回调函数,开始DMA传输。
因此,需要定义回调函数,并与中断服务例程和DPC例程联合工作。
首先在类PCIDemoDevice中声明:
public:
DEVMEMBER_DMAREADY(PCIDemoDevice,OnDmaReady)//声明DMA传输启动后的回调函数
protected:
KDmaTransferm_DmaTransfer;
KDmaAdapterm_Dma;
KCommonDmaBufferm_CommonDmaBuffer;
在函数OnStartDevice中,初始化DMA变量。
m_Dma.Initialize(&dd,m_Lower.TopOfStack());
m_CommonDmaBuffer.Initialize(m_Dma,0x100);
在SerialWrite函数中,启动DMA如下:
voidPCIDemoDevice:
:
SerialWrite(KIrpI)
{
…
status=m_DmaTransfer.Initiate(this,//设备指针
&m_Dma,//KdmaAdapter指针0
I.Mdl(),//数据的源或目的
FromMemoryToDevice,//传输方向,或FromDeviceToMemory
LinkTo(OnDmaReady),//连接的回调函数
&m_CommonDmaBuffer,//物理内存
NULL,//如果使用宏MEMBER_DMAREADYWITHCONTEXT,作为其回调函数的参数
FALSE);//指示当DMA传输完毕,是否释放DMA适配器和映射寄存器
…
}
典型的DMA传输流程如图20。
图20DMA传输流程
在上图中DMA启动后,系统调用回调函数OnDmaReady,如果有数据需要传输,就操作PCI设备的DMA寄存器,并开始数据的传输。
当传输完毕后,PCI设备产生中断请求,在驱动程序的中断服务例程中,启动DPC。
在DPC中,调用类KDmaTransfer的成员函数Continue,再次启动DMA,系统就再次调用回调函数OnDmaReady,如果数据传输完毕,则调用类KDmaTransfer的成员函数Terminate结束DMA操作,并结束IRP;如果数据没有传输完毕,则继续循环,直到数据传输完毕为止。
回调函数OnDmaReady的原型和处理如下:
voidPCIDemoDevice:
:
OnDmaReady(KDmaTransfer*pXfer,KIrpI)
{
if(pXfer->BytesRemaining()>0)
{
PTRANSFER_DESCRIPTORpTransferDescriptor;
ULONGCount;
Count=pXfer->GetTransferDescriptors(&pTransferDescriptor);
//将内存物理地址信息发送给硬件
…
}
else
{
pXfer->Terminate();
I.Complete(STATUS_SUCCESS,0);
}
}
7.驱动程序的安装
DriverWorks根据先导已生成INF文件,需要更改的只是用户的信息
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- pci 设备 wdm 驱动程序 设计 探讨 研究