PCI设备Windows通用驱动程序设计
时间:04-16
来源:电子技术应用 作者:北京理工大学电子工程系 李海
点击:
(6)设备内存
许多设备自身带有内存,PCI设备大多是采用映射的方式映射到PC系统的物理内存。有的设备还要通过驱动程序设置设备的接口寄存器。
有关驱动程序的加载和响应用户请求的内容,在DDK文档中有规定,所以设计设备驱动程序主要的面临问题是如何进行硬件操作,这是根据设备的不同而不同的。而硬件驱动程序的功能虽然千差万别,但基本功能就是完成设备的初始化、对端口的读写操作、中断的设置、响应和调用以及对内存的直接读写。如前面所说,Windows 9x和Windows NT的操作系统模型不同,但驱动程序所要完成的工作却是相同的,所以下面以Windows 9x为主进行介绍,仅在需要的地方指出两个操作系统的不同。
下面从这几方面讨论解决这些问题的途径:
(1)设备初始化
PCI设备驱动程序要实现识别PCI器件、寻址PCI器件的资源和对PCI器件中断的服务。PCI系统BIOS功能提供了BIOS的访问与控制的具体特征,所有软件(设备驱动程序、扩展ROM码)将通过标准中断号1AH调用BIOS功能访问特殊部件。PCI BIOS规范有完整的有关PCI BIOS功能的描述[3]。
在PCI设备驱动程序的初始化过程中,利用指定器件识别号(device_id)、厂商识别号(vendor_id)、检索号(index)搜索PCI器件,通过调用PCI BIOS确认其存在,并确定其物理位置:总线号、器件号和功能号,这是该器件/功能在系统中的唯一寻址标志。利用总线号、器件号和功能号可以寻址该器件/功能的PCI配置空间(configuration space)。
接下来,设备驱动就需要从配置空间获得硬件的参数。PCI设备的许多参数,包括所用的中断号,端口地址的范围(I/O)方式、存储器的地址(存储器映射方式)等,都可以从PCI配置空间的各基址所对应的寻址空间中得到。读写配置空间可以调用BIOS中断1AH。
也可以先向配置空间地址寄存器(0CF8H)写入总线和设备号(在前面搜索PCI器件时得到的)和寄存器号,再对配置空间数据寄存器(0CFCH)进行读写。对设备驱动来说,最重要的是获得基址寄存器(BADR),不能认为PCI器件资源总是设计设备时设置的初值,系统可能会根据硬件情况为PCI设备分配新的资源。我们所设计的PCI设备使用的基址1-3都是按I/O空间映射的,而基址4是按内存方式映射的。确定一个端口是按什么方式映射的,可以读对应端口的配置寄存器(Configuration Register)。读出后,判断其0位,如果0位为数值0,表示其是按内存方式设置的,否则为I/O方式设置的。内存方式和I/O方式的配置寄存器的含义参见文献[3]。如果要获得基址的大小,可以向基址寄存器写入FFFFH,然后读基址寄存器,如果是内存方式,从第4位开始的0的数目表示基址的大小,如果是I/O方式,则从第2位开始的0的数目表示基址的大小。
在Windows NT下,查找PCI设备的工作是由HalGetBusData完成的,也可以使用前述的办法读取配置寄存器,但DDK推荐使用HaiGetBusDataOffset函数。
(2)端口操作
在PC机上,I/O端口寻址空间和内存寻址空间是不同的,所以处理方法也不同。I/O空间是一个64K字节的寻址空间,它不象内存有实模式和保护模式之分,在各种模式下寻址方式相同。在Windows 9x下,用户程序可以直接使用I/O指令,而不一定非通过专门的驱动程序来完成,所以如果软件对硬件的操作完全是通过I/O端口操作来完成的,甚至可以不用专门设计驱动程序,直接由应用程序来完成对硬件的控制。由于PCI总线是32位的总线标准,在进行I/O操作时通常要进行双字(DWORD)操作,而且以前大多数C/C++编译软件都没有提供双字的函数,所以需要构造双字操作读写函数inpd/outpd。
在Windows NT下,系统不允许处于优先级3级的用户程序和用户模式驱动程序直接使用I/O指令,如果使用了I/O指令将会导致特权指令意外(privileged instruction exception)。所以任何对I/O的操作都需要借助内核模式驱动来完成。具体的做法有两种:一是在驱动程序中使用IoReportResourceUsage报告资源占用,然后使用READ_PORT_XXX、WRITE_PORT_XXX函数读写,最后使用IoReportResourceUsage取消资源占用;另一种是驱动程序修改NT的I/OPermissions Map (IOPM),以使系统允许用户程序对指定的I/O端口进行操作,这时用户程序采用通常的I/O指令进行操作。后者的优点是速度快、用户程序设计简单,但牺牲了移植性,程序不能移植到非Intel的系统中,而且如果多个程序同时读写同一端口容易导致冲突。
(3)内存的读写
Windows工作在32位保护模式下,保护模式与实模式的根本区别在于CPU寻址方式上的不同,这也是Windows驱动程序设计中需要着重解决的问题。Windows采用了分段、分页机制(图1),这样使应用程序产生一种错觉,好象程序中可以使用非常大的物理存储空间。这样做最大的好处就是一个程序可以很容易地在物理内存容量不一样的、配置范围差别很大的计算机上运行,编程人员使用虚拟存储器可以写出比任何实际配置的物理存储器都大得多的程序。每个虚拟地址由16位的段选择子和32位段偏移量组成。通过分段机制,系统由虚拟地址产生线性地址。再通过分页机制,由线性地址产生物理地址。线性地址被分割成页目录(Page Directory)、页表(Page Table)和页偏移(Offset)三个部分。当建立一个新的Win32进程时,操作系统会为它分配一块内存,并建立它自己的页目录、页表,页目录的地址也同时放入进程的现场信息中。当计算一个地址时,系统首先从CPU控制器CR3中读出页目录所在的地址,然后根据页目录得到页表所在的地址,再根据页表得到实际代码/数据页的页帧,最后再根据页偏移访问特定的单元。硬件设备读写的是物理内存,但应用程序读写的是虚拟地址,所以存在着将物理内存地址映射到用户程序线性地址的问题。
许多设备自身带有内存,PCI设备大多是采用映射的方式映射到PC系统的物理内存。有的设备还要通过驱动程序设置设备的接口寄存器。
有关驱动程序的加载和响应用户请求的内容,在DDK文档中有规定,所以设计设备驱动程序主要的面临问题是如何进行硬件操作,这是根据设备的不同而不同的。而硬件驱动程序的功能虽然千差万别,但基本功能就是完成设备的初始化、对端口的读写操作、中断的设置、响应和调用以及对内存的直接读写。如前面所说,Windows 9x和Windows NT的操作系统模型不同,但驱动程序所要完成的工作却是相同的,所以下面以Windows 9x为主进行介绍,仅在需要的地方指出两个操作系统的不同。
下面从这几方面讨论解决这些问题的途径:
(1)设备初始化
PCI设备驱动程序要实现识别PCI器件、寻址PCI器件的资源和对PCI器件中断的服务。PCI系统BIOS功能提供了BIOS的访问与控制的具体特征,所有软件(设备驱动程序、扩展ROM码)将通过标准中断号1AH调用BIOS功能访问特殊部件。PCI BIOS规范有完整的有关PCI BIOS功能的描述[3]。
在PCI设备驱动程序的初始化过程中,利用指定器件识别号(device_id)、厂商识别号(vendor_id)、检索号(index)搜索PCI器件,通过调用PCI BIOS确认其存在,并确定其物理位置:总线号、器件号和功能号,这是该器件/功能在系统中的唯一寻址标志。利用总线号、器件号和功能号可以寻址该器件/功能的PCI配置空间(configuration space)。
接下来,设备驱动就需要从配置空间获得硬件的参数。PCI设备的许多参数,包括所用的中断号,端口地址的范围(I/O)方式、存储器的地址(存储器映射方式)等,都可以从PCI配置空间的各基址所对应的寻址空间中得到。读写配置空间可以调用BIOS中断1AH。
也可以先向配置空间地址寄存器(0CF8H)写入总线和设备号(在前面搜索PCI器件时得到的)和寄存器号,再对配置空间数据寄存器(0CFCH)进行读写。对设备驱动来说,最重要的是获得基址寄存器(BADR),不能认为PCI器件资源总是设计设备时设置的初值,系统可能会根据硬件情况为PCI设备分配新的资源。我们所设计的PCI设备使用的基址1-3都是按I/O空间映射的,而基址4是按内存方式映射的。确定一个端口是按什么方式映射的,可以读对应端口的配置寄存器(Configuration Register)。读出后,判断其0位,如果0位为数值0,表示其是按内存方式设置的,否则为I/O方式设置的。内存方式和I/O方式的配置寄存器的含义参见文献[3]。如果要获得基址的大小,可以向基址寄存器写入FFFFH,然后读基址寄存器,如果是内存方式,从第4位开始的0的数目表示基址的大小,如果是I/O方式,则从第2位开始的0的数目表示基址的大小。
在Windows NT下,查找PCI设备的工作是由HalGetBusData完成的,也可以使用前述的办法读取配置寄存器,但DDK推荐使用HaiGetBusDataOffset函数。
(2)端口操作
在PC机上,I/O端口寻址空间和内存寻址空间是不同的,所以处理方法也不同。I/O空间是一个64K字节的寻址空间,它不象内存有实模式和保护模式之分,在各种模式下寻址方式相同。在Windows 9x下,用户程序可以直接使用I/O指令,而不一定非通过专门的驱动程序来完成,所以如果软件对硬件的操作完全是通过I/O端口操作来完成的,甚至可以不用专门设计驱动程序,直接由应用程序来完成对硬件的控制。由于PCI总线是32位的总线标准,在进行I/O操作时通常要进行双字(DWORD)操作,而且以前大多数C/C++编译软件都没有提供双字的函数,所以需要构造双字操作读写函数inpd/outpd。
在Windows NT下,系统不允许处于优先级3级的用户程序和用户模式驱动程序直接使用I/O指令,如果使用了I/O指令将会导致特权指令意外(privileged instruction exception)。所以任何对I/O的操作都需要借助内核模式驱动来完成。具体的做法有两种:一是在驱动程序中使用IoReportResourceUsage报告资源占用,然后使用READ_PORT_XXX、WRITE_PORT_XXX函数读写,最后使用IoReportResourceUsage取消资源占用;另一种是驱动程序修改NT的I/OPermissions Map (IOPM),以使系统允许用户程序对指定的I/O端口进行操作,这时用户程序采用通常的I/O指令进行操作。后者的优点是速度快、用户程序设计简单,但牺牲了移植性,程序不能移植到非Intel的系统中,而且如果多个程序同时读写同一端口容易导致冲突。
(3)内存的读写
Windows工作在32位保护模式下,保护模式与实模式的根本区别在于CPU寻址方式上的不同,这也是Windows驱动程序设计中需要着重解决的问题。Windows采用了分段、分页机制(图1),这样使应用程序产生一种错觉,好象程序中可以使用非常大的物理存储空间。这样做最大的好处就是一个程序可以很容易地在物理内存容量不一样的、配置范围差别很大的计算机上运行,编程人员使用虚拟存储器可以写出比任何实际配置的物理存储器都大得多的程序。每个虚拟地址由16位的段选择子和32位段偏移量组成。通过分段机制,系统由虚拟地址产生线性地址。再通过分页机制,由线性地址产生物理地址。线性地址被分割成页目录(Page Directory)、页表(Page Table)和页偏移(Offset)三个部分。当建立一个新的Win32进程时,操作系统会为它分配一块内存,并建立它自己的页目录、页表,页目录的地址也同时放入进程的现场信息中。当计算一个地址时,系统首先从CPU控制器CR3中读出页目录所在的地址,然后根据页目录得到页表所在的地址,再根据页表得到实际代码/数据页的页帧,最后再根据页偏移访问特定的单元。硬件设备读写的是物理内存,但应用程序读写的是虚拟地址,所以存在着将物理内存地址映射到用户程序线性地址的问题。
- 系统安全之解开恶意锁上的注册表 (01-05)
- 卡巴斯基误杀致XP系统崩溃的解决方案(04-23)
- 让黑莓在Windows Mobile上运行(04-25)
- 解决Vista与VPN的兼容问题(04-24)
- 六步完成Windows IT业务管理(04-28)
- Windows Home Server是什么?(05-04)