最近有些人问我PCI
设备驱动的问题, 和他们交流过后, 我建议他们先看一看<<The Windows NT Device Driver Book>>
这本书, 个人感觉, 这本书写得非常连贯流畅.
PCI
设备驱动基本包括了PCI
的资源获取, 配置空间的读写, 中断的处理, 中断后半部在DPC
中的处理.
同时, 也必须了解DMA, ScatterGater, MapRegister, Common Buffer
等基础.
1.1 PCI
设备资源获取
PCI
设备的资源是系统根据设备的属性(配置空间中寄存器的值)来动态分配的.
驱动中只需在PNP START
中获取这些系统分配的资源:
例如: 笔者开发的PCI
电视卡驱动中, 就使用到了其中了两类资源, CmResourceTypePort
与CmResourceTypeInterrupt
.
Port
地址作为设备寄存器首地址, 之后, 可以使用WRITE_PORT_ULONG
与READ_PORT_ULONG
加上相应的OFFSET
来对设备寄存器进行访问.
Interrupt
资源中解释出来的内容, 则主要作为IoConnectInterrupt
系统函数的参数, 将设备的硬件中断与ISR
相关联, KINTERRUPT
的实例则是设备中断的软件形式的载体.
1.2 DMA
DMA
设备, 在系统中分为MASTER
与SLAVE
, 另外一个很重要的能力就是是否支持Scatter/Gather
.
这些能力最终表现在DEVICE_DESCRIPTION
所定义的数据结构的成员中, 例如:DmaWidth
, ScatterGather
, Master, Dma32BitAddresses, Dma64BitAddresses
.
系统最终将各种不同类型的设备DMA
抽象为DMA_ADAPTER
的实例, 它是设备DMA
软件形式的载体.
驱动代码通过IoGetDmaAdapter
系统调用, 将物理设备对象PDO
与DMA
描述结构作为参数, 最终得到这个DMA_ADAPTER
对象, 作为后续一系列DMA
相关操作的实体对象.
1.3 Map Register
用户空间, 内核空间的虚拟内存与物理内存的关联是通过页表来映射的, 驱动程序员常常会使用MDL
, 它也是某一特定区域虚拟内存与物理内存的映射关系.
而DMA
设备则需要从总线地址(MSDN中又叫逻辑地址)与内存物理的映射关系角度去看待系统内存.
这个映射的关系就是由Map Register
承担的.
不过, 这批Map Register
则根据系统而定, 有些是硬件实现, 有些是软件中划分出来的特定的一块内存.
IoGetDmaAdapter
的调用, 也是向系统申请Map Register
的过程.
1.4 Common Buffer
这也是大家问得最多的问题
简单地讲, Common buffer
是以DMA_ADAPTER
为代表所申请的, 申请成功后, 既能通过虚拟地址访问, 也可以通过DMA
控制器所属逻辑地址空间的地址来访问的连续物理内存.
它的好处就是物理上连续, 存在的问题是系统中连续物理内存是随着系统的运行时间的流逝, 越来越稀缺.
AllocateCommonBuffer
系统调用是作为DMA_ADAPTER
的DmaOperations
形式存在的, 所以, 具体的一块Common Buffer
可以说, 是与具体的一个DMA
控制器所关联的.
AllocateCommonBuffer
成功调用后, 会返回虚拟地址与DMA
控制器所属逻辑空间的逻辑地址.
笔者开发的PCI
电视卡, 就是通过AllocateCommonBuffer
分配一块较小的连续物理内存, 用来存放Scatter/Gather
列表 (某块内存的逻辑地址SCATTER_GATHER_LIST.Elements[i].Address.LowPart
与该内存的长度SCATTER_GATHER_LIST.Elements[i].Length
, 相应操作通过common buffer
的虚拟地址 ).
这个Scatter/Gather
List列表最终由具有S/G
能力的DMA
控制器来读取(相应操作通过common buffer
的逻辑地址), 根据其中的表项, 进行DMA
读/写操作.
1.5 S/G
S/G的
能力是DMA
控制器的特性, 如果具有S/G
的能力, 则可以批量地DMA
操作, 否则, 必须一次一次地使用MapTransfer
来完成DMA
操作.
系统空间的中虚拟内存与物理内存之间的联系通过IoAllocateMdl
与MmBuildMdlForNonPagedPool
建立特定的MDL
来表示.
其后,通过DMA_ADAPTER
的DmaOperations
中的GetScatterGatherList
获取MDL
所描述的虚拟地址内存的S/G
列表, 最后, 在GetScatterGatherList
的
ExecutionRoutine
函数中, 将该列表填入Common buffer
的TABLE
(起始逻辑地址 与 长度)中, 以供DMA Controller
所用.
1.6 ISR
与DPC
刚才已经提到, ISR
是通过IoConnectInterrupt
注册的.ISR
在设备中断到来时实调用, 但具体的事项则交由(KeInsertQueueDpc
)DPC
来处理.而DPC
则是通过KeInitializeDpc
系统调用, 将DPC
对象KDPC
与具体的KDEFERRED_ROUTINE DPC
处理函数相关联的.
1.7 PCI设备配置空间的访问
事实上, 一般情况下, Windows PCI
设备并不需要访问PCI
设备配置空间.但作为一个完整的PCI
设备驱动, 这里提及一下.
由于PCI
设备的配置空间与IO/MEM
空间是分开的, 前面已经提及IO/MEM
的访问方式, 配置空间的访问如下:定义变量:BUS_INTERFACE_STANDARD m_BusInterfaceStandard
;建立: IRP
, 主与次分别为IRP_MJ_PNP
, IRP_MN_QUERY_INTERFACE
, 得到BUS_INTERFACE_STANDARD
数据结构.之后, 通过BUS_INTERFACE_STANDARD
中的SetBusData
与GetBusData
来进行PCI
配置空间的寄存器读写.
PCI
设备驱动完全可以用在PCIe
设备上, 毕竟上层来讲, 他们没有太多的区别.
与USB
驱动不同, PCI
设备需要考虑驱动设计中的方方面面, 希望这篇文章对大家有所借鉴作用.