开发者社区> andyro1984> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

PCI设备驱动开发

简介:  PCI设备驱动开发 1. PCI 简介     PCI 总线标准是一种将系统外部设备连接起来的总线标准,是 PC 中最重要的总线,实际上是系统的各个部分如何交互的接口。
+关注继续查看

 PCI设备驱动开发

1. PCI 简介
     PCI 总线标准是一种将系统外部设备连接起来的总线标准,是 PC 中最重要的总线,实际上是系统的各个部分如何交互的接口。传输速率可达到 133MB/s。在当前的 PC 体系结构中,几乎所有的外部设备采用的各种各样的接口总线,均是通过桥接电路挂接到 PCI 系统上。在这种 PCI 系统中, Host/PCI 桥称为北桥,连接主处理器总线到基础 PCI 局部总线。 PCI 与其他总线的接口称为南桥,其中南桥还通常含有中断控制器、IDE 控制器、USB 控制器和 DMA 控制器等。南桥和北桥组成主板的芯片组。

2. PCI配置空间
    每个PCI设备都有自己的配置空间,用于支持即插即用,使之满足现行的系统配置结构。下面对PCI配置空间做一下简要介绍。
配 置空间是一容量为256字节并具有特定结构的地址空间。这个空间又分为头标区和设备有关区两部分。头标区的长度是64字节,每个设备都必须配置该区的寄存 器。该区中的各个字段用来唯一地识别设备。其余的192字节因设备而异。配置空间的头标区64个字节的使用情况如图1示。为了实现即插即用,系统可根据硬件资源的使用情况,为PCI设备分配新的资源。因此编写设备驱动程序重点是获得基址寄存器(Base Address)和中断干线寄存器的内容。配置空间共有六个基址寄存器和一个中断干线寄存器,具体用法如下:PCI Base Address 0 寄存器:系统利用此寄存器为PCI接口芯片的配置寄存器分配一段PCI地址空间,通过这段地址我们可以以内存映射的形式访问PCI接口芯片的配置寄存器。

     PCI Base Address 1寄存器:系统利用此寄存器为 PCI 接口芯片的配置寄存器分配一段PCI地址空间,通过这段地址我们可以以I/O的形式访问PCI接口芯片的配置寄存器。

     PCI Base Address 2、3、4、5寄存器:系统BIOS利用这些寄存器分配PCI地址空间以支持PCI接口芯片的局部配置寄存器0、1、2、3的访问。

     在所有基址寄存器中,第0位均为只读位,表示这段地址映射到存储器空间还是I/O空间,如果是“1”表示映射到I/O空间,如果是“0”则表示映射到存储器空间。

     中断干线寄存器(Interrupt Line):用于说明中断线的连接情况,这个寄存器的值与标准8259的IRQ编号(0~15)对应。


表1 PCI配置空间

3.设备初始化


     PCI 设备驱动程序要完成识别 PCI 器件、寻找 PCI 硬件的资源和对 PCI 器件中断的服务。在驱动程序初始化过程中,使用 HalGetBusData()函数完 成寻找 PCI 设备的工作。在初始化过程中,使用器件识别号(Device ID)和厂商识别号(Vendor ID),通过遍历总线上的所有设备,寻找到指定的PCI设备,并获取设备的总线号,器件号与功能号。通过这些配置信息,可以在系统中寻址该设备的资源配置 列表。

     在此之后,驱动程序需要从配置空间获取硬件的参数。PCI设备的中断号、端口地址的范围(I/O)方式、存储器的地址与映射 方式等,都可以从硬件资源列表数据结构中获取。在Windows NT中,调用HalAssignSlotResources()函数来获得指定设备的资源列表数据结构指针,然后通过遍历该列表中的所有资源描述符,获取 该设备的I/O端口基地址与长度,中断的中断级、中断向量与模式,存储器基地址与长度等硬件资源数据。

     我们设计的DMA通信采用总线主控方式进行通信,在 设备初始化时需要对DMA适配器进行初始化,使用HalGetAdapter()获得操作系统分配的适配器对象指针。
示例代码如下:

// 遍历总线,获得指定设备的总线号,器件号与功能号
for ( busNumber = 0; busNumber < MAX_PCI_BUSES; busNumber++ ) {
for ( deviceNumber = 0;deviceNumber < PCI_MAX_DEVICES;deviceNumber++ ) {
slotNumber.u.bits.DeviceNumber = deviceNumber;
for ( functionNumber = 0; functionNumber < PCI_MAX_FUNCTION; functionNumber++ ) {
slotNumber.u.bits.FunctionNumber = functionNumber;
if (!HalGetBusData(PCIConfiguration, busNumber, slotNumber.u.AsULONG,     
&pciData,sizeof(ULONG)) ) {
deviceNumber = PCI_MAX_DEVICES;
break;
}

if (pciData.VendorID == PCI_INVALID_VENDORID ) {
continue;
}

if ( ( VendorId != PCI_INVALID_VENDORID ) &&
( pciData.VendorID != VendorId || pciData.DeviceID != DeviceId )) {
continue;
}
pPciDeviceLocation->BusNumber = busNumber;
pPciDeviceLocation->SlotNumber = slotNumber;
pPciDeviceLocation = &PciDeviceList->List[++count];
status = STATUS_SUCCESS;
}
}
}
// 获取设备的资源列表数据指针
status = HalAssignSlotResources(RegistryPath,
&pDevExt->ClassUnicodeString,
DriverObject,
DeviceObject,
pDevExt->InterfaceType,
pDevExt->BusNumber,
pDevExt->SlotNumber,
&pCmResourceList );

4. I/O端口访问

      在 PC机上,I/O寻址方式与内存寻址方式不同,所以处理方法也不同。I/O空间是一个64K字节的寻址空间,I/O寻址没有实模式与保护模式之分,在各种 模式下寻址方式相同。在Windows NT下,系统不允许处于Ring3级的用户程序和用户模式驱动程序直接使用I/O指令,对I/O端口进行访问,任何对I/O的操作都需要借助内核模式驱动 来完成。在访问I/O端口时,使用READ_PORT_XXX与WRITE_PORT_XXX函数来进行读写。I/O端口基地址使用从配置空间基址寄存器 PCI Base Address 1中返回的I/O端口基地址。

      示例代码如下:
RegValue = READ_PORT_ULONG(pBaseAddr+RegOffSet);
WRITE_PORT_ULONG(pBaseAddr+ RegOffset, RegValue);

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

     从物理内存到线性地址的转换是驱动程序需要完成的工作,可以在初始化驱动程序的进行。在已经获得设备的存 储器基地址后,首先调用HalTranslateBusAddress()函数将总线相关的内存地址转换成系统的物理地址,然后调用 MmMapIoSpace()函数将系统的物理地址映射到线性地址空间。在需要访问设备内存时,调用READ_REGISTER_XXX()与 WRITE_REGISTER_XXX ()函数来进行,基地址使用前面映射后的线性地址。在设备卸载时,调用MmUnmapIoSpace()断开设备内存与线性地址空间的映射。
示例代码如下:
HalTranslateBusAddress(InterfaceType,
BusNumber,
BaseAddress->RangeStart,
&addressSpace,
&cardAddress)

BaseAddress->MappedRangeStart = MmMapIoSpace(cardAddress,
BaseAddress->RangeLength,
MmCached );
……
RegValue = READ_REGISTER_ULONG(pRegister);
WRITE_REGISTER_ULONG(pRegister, pInBuf->RegValue);
……
MmUnmapIoSpace(pBaseAddress->MappedRangeStart, pBaseAddress->RangeLength );

6. 中断处理
     中 断的设置、响应与调用在驱动程序中完成。设置中断应该在设备创建时完成,使用从CmResourceTypeInterrupt描述符中提取的参数,先调 用HalGetInterruptVector()将与总线有关的中断向量参数转换为系统的中断向量,然后调用IoConnectInterrupt() 指定中断服务,注册中断服务函数ISR(Interrupt Service Routine)的函数指针。

     当硬件设备产生中断时,系统 会自动调用ISR函数来响应中断。ISR函数运行的中断请求级较高,主要完成对硬件设备中断的清除,不适合执行过多的代码。在传输大块数据时,需要使用延 迟过程调用(Delay Process Call,DPC)机制。例如,使用PCI设备进行DMA通信时,在ISR函数中完成对指定设备中断的判断以及清除中断,在退出ISR前,调用DPC函 数;在DPC函数中,完成DMA通信的过程,并将数据返回给用户程序。
示例代码如下:
DeviceExtension->InterruptLevel = partialData->u.Interrupt.Level;
DeviceExtension->InterruptVector = partialData->u.Interrupt.Vector;
DeviceExtension->InterruptAffinity = partialData->u.Interrupt.Affinity;
if (partialData->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
{

    DeviceExtension->InterruptMode = Latched;
} else {
    DeviceExtension->InterruptMode = LevelSensitive;
}
……
vector = HalGetInterruptVector(pDevExt->InterfaceType,
pDevExt->BusNumber,
pDevExt->InterruptLevel,
pDevExt->InterruptVector,
&irql,
&affinity );

status = IoConnectInterrupt(&pDevExt->InterruptObject,
(PKSERVICE_ROUTINE)PciDmaISR,
DeviceObject,
NULL,
vector,
irql,
irql,
pDevExt->InterruptMode,
TRUE,
affinity,
FALSE );
7. DMA通信过程
      DMA通信在驱动程序中实现,需要多个例程才能完成一次DMA通信。
1) DriverEntry例程
      构造DEVICE_DESCRIPTION结构,并调用HalGetAdapter,找到与设备关联的Adapter对象,并将返回的Adapter对象的地址和映射寄存器的数目保存在设备扩展的数据结构中。
示例代码:
// 申请DMA的适配器对象
deviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
deviceDescription.Master = TRUE;
deviceDescription.ScatterGather = pDevExt->ScatterGather;
deviceDescription.DemandMode = FALSE;
deviceDescription.AutoInitialize = FALSE;
deviceDescription.Dma32BitAddresses = TRUE;
deviceDescription.BusNumber = pDevExt->BusNumber;
deviceDescription.InterfaceType = pDevExt->InterfaceType;
deviceDescription.MaximumLength = pDevExt->MaxTransferLength;
pDevExt->AdapterObject = HalGetAdapter(&deviceDescription,
&numberOfMapRegisters
);
……
2)Start I/O例程
该例程请求Adapter对象的拥有权,然后把其余的工作留给AdapterControl回调例程。
a) 调用KeFlushIoBuffers从CPU的Cache把数据清到物理内存,然后计算映射寄存器的数目和用户缓冲区的大小,及在第一次设备操作中传输的字节数。
b) 调用MmGetMdlVirtualAddress,从MDL中恢复用户缓冲区的虚地址,并存入设备扩展数据结构中。
c) 调用IoAllocateAdapterChannel请求Adapter对象的拥有权。如果调用成功,其余的设置工作由AdapterControl例程去做;如果失败了,则完成本次IRP包处理,开始处理下一个IRP。
3) AdapterControl例程
该例程完成初始化DMA控制器,并启动设备的工作。
a) 调用IoMapTransfer,装入Adapter对象的映射寄存器。
b) 向设备发送合适的命令开始传输操作。
c) 返回值KeepObject保留Adapter对象的拥有权。
4)中断服务(ISR)例程
在设备中断时,由系统调用。
a) 向硬件设备发出中断响应的指令。
b) 调用IoRequestDpc在驱动程序的DpcForIsr中继续处理该请求。
c) 返回TRUE,表示已经服务了本次中断。
5)DpcForIsr例程
由ISR在每个部分数据传输操作的结束时触发,完成当前IRP请求。
a) 调用IoFlushAdapterBuffers,清除Adapter对象的Cache中的任何剩余数据。
b) 调用IoFreeMapRegisters,释放所使用的映射寄存器。
c) 检查有未传完的剩余数据,如果有,则计算下次设备操作中需要传输的字节数,调用IoMapTransfer重设映射寄存器,并启动设备;如果没有剩余数据,则完成当前IRP请求,并开始下一个请求。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
vxworks的pci设备驱动调试
vxworks的pci设备驱动调试
0 0
PCI-X总线
<p><span style="">PCI-X接口是并连的</span><a target="_blank" href="http://baike.baidu.com/view/2815.htm" style="">PCI总线</a><span style="">(Peripheral Components Interconnect)的更新版本,仍采用传统的</span><a target
751 0
1.5 PCI-X总线简介
<div class="bct fc05 fc11 nbw-blog ztag"> <p style="TEXT-INDENT: 21pt;"><span lang="EN-US" xmllang="EN-US">PCI-X</span><span style="FONT-FAMILY: 宋体;">总线仍采用并行总线技术。</span><span lang="EN-US" xmllang="
1026 0
2.4 PCI总线的配置
<div class="bct fc05 fc11 nbw-blog ztag"><div> <p style="TEXT-INDENT: 21pt;"><span lang="EN-US" xmllang="EN-US">PCI</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times new roman'; mso
1023 0
第1章 PCI总线的基本知识
<div class="bct fc05 fc11 nbw-blog ztag"> <p style="TEXT-INDENT: 21pt;"><span lang="EN-US" xmllang="EN-US">PCI</span><span style="FONT-FAMILY: 宋体;">总线作为处理器系统的局部总线,主要目的是为了连接外部设备,而不是作为处理器的系统总线连接</spa
973 0
37、SDIO设备驱动
SD(Secure Digital Memory Card)IO卡是在SD内存卡接口基础之上发展起来的接口。SDIO协议类似于USB总线协议。 和USB总路线类似,SDIO总路线也有两端,一端是HOST端,另外一端是DEVICE端,所有指令都是由HOST端发出指令开始,在DEVICE端只要能解析HOST的命令,就可以和HOST进行通信。
677 0
38、其它设备驱动
关于RS-232,485等系列的驱动编写,类似于USB等,见[1]。 摄像头驱动程序 WDM摄像头驱动程序分两部分,一部分是类驱动(Class Driver)程序,由MS提供,提供了标准接口,一部分是小驱动(Mini Driver)程序,用户编写。
504 0
35、PCI设备驱动简介
PCI(Peripheral Component Interconnect)总线标准是一种将系统外部设备连接起来的总线标准,速度可以达到133MB/s,它是PC中最重要的总线,其他总路线如ISA总线,USB总线等,都挂载在PCI总线上(通过桥接电路)。
525 0
+关注
andyro1984
本团队有11年以上的解决方案端到端开发经验,涉及的行业有云计算、应用软件(包括WEB)、嵌入式、分布式、大型服务程序(Windows/Linux)、操作系统等。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载