新闻中心

EEPW首页 > 模拟技术 > 设计应用 > PCI设备WINDOWS驱动程序的开发

PCI设备WINDOWS驱动程序的开发

作者:时间:2012-04-18来源:网络收藏
统BIOS功能调用,通过供应商识别号(VendorID)和设备识别号 (DeviceID)直接访问设备,也可以利用配置管理器(Configuration Manager)封装的功能函数,根据供应商识别号(VendorID)和设备识别号 (DeviceID)搜索设备结点树,查询。由于编写PCI系统BIOS功能调用程序更为简捷,所以本文采用这种方法。
PCI系统BIOS功能提供了BIOS的访问与控制的具体方法,所有软件(设备、扩展ROM码)将通过标准中断号1AH调用BIOS功能访问特殊部件。在中调用VtoolsD系统服务Exec_VxD_Int()来实现PCI系统BIOS的1AH中断。
首先,通过的供应商识别号(VendorID)、 设备识别号 (DeviceID)和索引号(Index)查找特定设备所在的总线号(Bus Num)、设备号(Device NUM)、功能号(Function Num)和寄存器号(Register Num)。总线号是从0到255的数值,在一个系统中,可把多达256条的PCI总线用桥连接在一起。由于编号是从0开始的,所以当系统有N条总线时,总线号会达到N-1;设备号是在0到31之间分配的任意值,并不拘于从0开始按顺序分配;功能号分配从0到7的值。代码如下:

ALLREGS* pRegisters; // pRegisters是指向寄存器结构体的指针
pRegisters->REAX =0xb102; // 0xb102是功能号
pRegisters->RECX =0x1001; // 假设Device ID=0x1001
pRegisters->REDX=0x102b; // 假设Vendor ID=0x102b
Exec_VxD_Int(0x1a,pRegisters); // 调用1AH中断

返回值是pRegisters->REBX。BH寄存器是总线号,BL寄存器的高5位是设备号,低3位是功能号。
然后,向配置空间地址寄存器CF8h写入总线号、设备号、功能号、索引号, 从配置空间数据寄存器CFCh读出配置空间的内容。
配置空间地址寄存器(CF8h)格式如下:
Bit31 30-24 23-16 15-11 10-8 7-2 10
使能位 保 留 总线号 设备号 功能号 寄存器号 00

使能位为“1”表示允许访问

配置空间数据寄存器(CFCh)存放要读写的数据。
代码如下:

DWORD d=0;
d=pRegisters->REBX;
(d=8)|=0x80000000;
for (short i=0;i16;i++) // 读取64字节配置空间
{
_outpd(0xcf8,d+4*i); // 按DWORD类型一次读取四个字节
dprintf("%8x",_inpd(0xcfc)); // 打印输出
}

3、内存的读写
Winsows工作在32位保护模式下,保护模式与实模式的根本区别在于CPU寻址方式上的不同,这也是Windows设计中需要着重解决的问题。Windows采用了分段、分页机制,这样使应用程序产生一种错觉,好象程序中可以使用非常大的物理存储空间。这样做最大的好处就是一个程序可以很容易地在物理内存容量不一样的、配置范围差别很大的计算机上运行,编程人员使用虚拟存储器可以写出比任何实际配置的物理存储器都大得多的程序。每个虚拟地址由16位的段选择字和32位段偏移量组成。通过分段机制,系统由虚拟地址产生线性地址。再通过分页机制,由线性地址产生物理地址(如图2)。线性地址被分割成页目录(Page Directory)、页表(Page Table)和页偏移(Offset)三个部分。当建立一个新的Win32进程时,操作系统会为它分配一块内存,并建立它自己的页目录、页表,页目录的地址也同时放入进程的现场信息中。当计算一个地址时,系统首先从CPU控制器CR3中读出页目录所在的地址,然后根据页目录得到页表所在的地址,再根据页表得到实际代码/数据页的页帧,最后再根据页偏移访问特定的单元。硬件设备读写的是物理内存,但应用程序读写的是虚拟地址,所以存在着将物理内存地址映射到用户程序线性地址的问题。

15 0 31 0 31 0 31 0


图2 虚拟地址转换为物理地址

从物理地址到线性地址的转换工作是由驱动程序来完成的。驱动程序的内存映射部分主要是调用VxD的系统服务MapPhysToLinear。在VtoolsD中这个函数的定义如下:

PVOID MapPhysToLineag(CONST VOID * PhysAddr,DWORD nBytes,DWORD Flags);

其中第一个参数PhysAddr就是要映射的内存的物理地址的起始位置,这个物理地址可以从PCI配置空间的基址寄存器中获得,nBytes是内存区域的长度,Flags必须设置为0。这个函数返回的就是这段物理地址映射的线性内存地址。如果指定的内存不能存取,函数将返回FFFFFFFFH。
如:PDWORD pBase = (PDWORD)MapPhysToLinear((PVOID)PhysAddress,PhysSize,0);
将pBase传递给调用驱动的用户程序,用户程序就可以像使用指针一样利用pBase访问内存。

4、I/O端口的操作
在PC机上,I/O寻址方式与内存寻址方式不同,所以处理方法也不同。I/O空间是一个64K字节的寻址空间,I/O寻址没有实模式与保护模式之分,在各种模式下寻址方式相同。在Windows9x系统下,运行于第3级的应用程序也可以直接使用I/O指令访问I/O空间。在设备初始化访问配置空间时,已用到了I/O指令,在对硬件进行配置时,也可以根据从配置空间基址寄存器PCI Base Address 1中返回的I/O端口基地址使用I/O指令。

5、响应中断
VTOOLSD提供了VHardwareInt类,虚拟IRQ,处理硬件中断。在Windows9x中,VPICD虚拟了物理可编程中断控制器的端口,从而可以控制物理中断控制器。虚拟IRQ的编程思路:首先从VHardwareInt类中派生出一个类,重载OnHardwareInt函数;然后,动态创建一个派生类对象实例;最后钩挂处理程序,这就是需要编写的中断服务程序。(关于VHardwareInt类可参考VTOOLSD)

四、驱动程序的调用和封装
编写设备驱动并不是最终的目的,需要由用户程序来调用驱动并实现一定的功能。一般调用设备驱动是使用CreateFile函数打开设备文件,得到一个文件句柄。使用如下的语句就可以打开文件。

HANDLE hVxD=CreateFile("\\.\PCIDEVICE.VXD",0,0,0, OPEN_EXISTING,
FILE_FLAG_DELETE_ON_CLOSE,
0);
打开设备文件后,调用DeviceIoControl函数就可以实现应用程序与设备驱动程序的通信。完成硬件操作之后,可以调用函数CloseHandle(hVxD);关闭设备驱动。(关于这三个函数的详细说明请参考MSDN)
至此,我们完成了对驱动程序的初步设计,考虑到有的函数(如DeviceIoControl)调用起来非常复杂,为了提高程序的通用性,要对部分函数进行封装。因为动态链接库(DLL)可以在多数软件开发平台中调用,所以常用DLL封装形式。另外需要注意的是由于驱动程序具有与操作系统相同的特权,并且直接操作硬件,如果程序工作不稳定,会造成死机甚至系统崩溃,所以要对驱动程序进行全面细致的测试。
参考文献

c++相关文章:c++教程




关键词: PCI设备 WINDOWS 驱动程序

评论


相关推荐

技术专区

关闭