给出一个解决方法
把PCI_COMMON_CONFIG的第一个成员:(wdm.h的32015行)
PCI_COMMON_HEADER DUMMYSTRUCTNAME
修改为
PCI_COMMON_HEADER PCIHeader//
////////////////////////////////////////////////////////////////////////////////////
DisplayCfgSpace(3, 0, 0) // 3,0,0 需要看下设备管理器的器纤漏件属性观察得到 第一个是总线迹核号,第二个是设备号码,第三个是功能号,可以在 evtPreparaHardware这个函数的末尾调用获取配置空间的信息~
///////////////////////////////////////////////////////////////////////////////////
测试实现代码:
#define PCI_CONFIG_ADDRESS (PULONG)(0xCF8)
#define PCI_CONFIG_DATA (PULONG)(0xCFC)
#define KdrPrint DbgPrint
void DisplayCfgSpace(int bus, int dev, int fn){
unsigned long dwAddr, dwData
PCI_COMMON_CONFIG PciConfig
PCI_SLOT_NUMBER SlotNumber
// PCI_CAPABILITIES_HEADER CapabilityHeader
// UCHAR CapabilityOffset
SlotNumber.u.AsULONG = 0
SlotNumber.u.bits.DeviceNumber = dev// 设置设备号
SlotNumber.u.bits.FunctionNumber = fn// 设置姿竖掘功能号
dwAddr = 0x80000000 |( bus <<16 ) | ( SlotNumber.u.AsULONG <<8 )
// 得到配置空间
for (int i = 0i <0x100i += 4){/* 256 字节的 PCI 配置空间 */
#if 0
SetPortVal ( PCI_CONFIG_ADDRESS, dwAddr | i, 4 )
GetPortVal ( PCI_CONFIG_DATA , &dwData , 4 )
#endif
WRITE_PORT_ULONG(PCI_CONFIG_ADDRESS, dwAddr | i)
dwData = READ_PORT_ULONG(PCI_CONFIG_DATA)
memcpy (((PUCHAR )&PciConfig) + i,&dwData,4)
}
if (PciConfig.PCIHeader.VendorID == 0xFFFF){
KdrPrint("Read cfg space err!\n")
return// 无效设备
}
else
{
KdrPrint("bus: %d dev : %d fn : %d \n",bus, dev, fn)
KdrPrint("%x %x \n", PciConfig.PCIHeader.VendorID, PciConfig.PCIHeader.DeviceID)
}
}
///////////////////////////////////////////////////////////////////////////////////
目前用于访问PCIe配置空间寄存器的方法需要追溯到原始的PCI规范。为了发起PCI总线配置周期,Intel实现的PCI规范使用IO空间的CF8h和CFCh来分别作为索引和数据寄存器,这种方法可以访问所有PCI设备的255 bytes配置寄存器。Intel Chipsets目前仍然支持这种访问PCI配置空间的方法。PCIe规范在PCI规范的基础上,将配置空间扩展到4K bytes,至于为什么扩展到4K,具体可以参考PCIe规范,这些功能都需要配置空间。原来的CF8/CFC方法仍然可以访问所有PCIe设备配置空间的头255 bytes,但是该方法访问不了剩下的(4K-255)配置空间。怎么办呢?Intel提供了另外一种PCIe配置空间访问方法。Intel Chipset通过将配置空间映射到内存地址空间,PCIe配置空间可以像对映射正宏亏范围内的内存进行read/write一样来访问了。这种映射是由北桥芯片来完成的,但是不同芯片的映射方式也是不同的。
1、CF8h/CFCH Method
Intel Chipsets使用IO空间的举神CF8h/CFCh地址来访问PCI设备的配置寄存器,该方法同样可以访问PCIe设备的头255配置寄存器。
为了对已知PCI设备发起一个PCI总线配置周期,软件必须执行以下步骤:
PCI设备的总线号必须被填写到IO地址CF8h的[23:16] bits
PCI设备的设备号必须被填写到IO地址CF8h的[15:11] bits
PCI设备的功能号必须被填写到IO地址CF8h的[10:8] bits
需要访问的寄存器双字地址必须被填写到IO地址CF8h的[7:2] bits
CF8h的最高位为配置位,该位必须设置为1
对于写 *** 作,将设备的特定信息组合成一个双字(4bytes)后,写到CFCh地址
对于读 *** 作,将设备的特定信息组合成一个双字后,把数据从CFCh读回来
当执行6或者7步骤时,相应的PCI配置read/write cycle被Created by Intel Chipset,并在需要时传递到整个系统。在步骤4配置需要读写的寄存器地址时,该空间只有6位,也就说只有64个地址可写,但是PCI配置空间不是256吗?别急,记得是双字绝姿地址,一个Dword=4 bytes,也就是说4 * 64 = 256,刚好,不是吗?
2、Memory Mapped Method
PCIe规范为每个PCIe设备添加了更多的配置寄存器,空间为4K,尽管CF8h/CFCh方法仍然能够访问lower 255 bytes,但是必须提供另外一种方法来访问剩下的4K range寄存器。Intel的解决方案是使用了预留256MB内存地址空间,对这段内存的任何访问都会发起PCI 配置cycle。但是为什么是256MB???听我慢慢解释给大家听:犹豫4K的配置空间是directly mapped to memory的,那么PCIe规范必须保证所有的PCIe设备的配置空间占用不同的内存地址,按照PCIe规范,支持最多256个buses,每个Bus支持最多32个PCI devices,每个device支持最多8个function,也就是说:占用内存的最大值为:256 * 32 * 8 * 4K = 256MB。
这段256MB的内存区将根据intel chipset的不同,可以映射到系统内存映射范围内的任何位置,一般北桥芯片都会有一个寄存器来指明PCI配置空间的内存映射地址,它叫PCIe Configuration Register Base Address Register (BAR),如下图:
当软件访问指定PCIe设备的配置寄存器时,必须正确计算该寄存器映射到内存的具体地址,那么怎么计算呢,参考上图我们可以知道,busNo=0,deviceNo=0,funcNo=0的地址刚好是BAR,一条总线占用的最大空间计算如下:
SIZE_PER_BUS = 4K * 32 * 8 = 256K = 1M = 100000h
SIZE_PER_DEVICE = 4K * 8 = 8000h
SIZE_PER_FUNC = 4K = 1000h
访问总线号为busNo,设备号为DevNo,功能号为funcNo的offset寄存器的计算公式是:
Memory Address = PCIe Configuration Register Base Address Register (BAR)
+ busNo * SIZE_PER_BUS
+ devNo * SIZE_PER_DEVICE
+ funcNo * SIZE_PER_FUNC
+ offset
For example, to access the following configuration register:
• PCI Express Configuration Register F0000000h
• Bus Number 15h
• Device Number 00h
• Function Number 05h
• Register Offset 84h
Memory Address = F0000000h + 15h * 100000h + 00h * 8000h + 05h * 1000h + 84h
= F1505084h
现在我们可以从已知的busNo,devNo,funcNo和offset来计算映射后的内存地址,那么反过来,给定的内存地址,我们想知道这个地址的busNo, devNo, funcNo和offset信息,可以吗?当然可以,计算公式如下:
busNo = (Memory Address - BAR) / SIZE_PER_BUS
devNo = (Memory Address - BAR - busNo * SIZE_PER_BUS) / SIZE_PER_DEVICE
funcNo = (Memory Address - BAR - busNo * SIZE_PER_BUS
- devNo * SIZE_PER_DEVICE) / SIZE _PER_FUNC
offset = Memory Address - BAR - busNo * SIZE_PER_BUS - devNo * SIZE_PER_DEVICE
- funcNo * SIZE_PER_FUNC
又或offset = Memory Address &0x0FFFh(为什么是0x0FFFh?自己想想啦)
想起来了么?因此PCIe的配置空间大小就是4K啊。
3、芯片组的异同
上面说的BAR,也就是PCI配置空间寄存器映射到内存的基地址寄存器,在intel chipset中的实现方式也千差万别。在前期的intel chipset中,该寄存器被包含在芯片组(MCH ,GMCH)的内存控制器部分。
另外,由于被PCIe配置空间占用的256M内存空间会屏蔽掉DRAM使用该段内存区,大部分的Intel Chipset允许BIOS来配置该空间大小,因此在实际应用中,一般就应用前面几个总线号,BIOS通过检测PCIe总线的扩展深度来动态设置该映射内存区的大小,比如PM965芯片组,如果配置软件检测系统使用不大于64的总线号,那么该软件将编程内存映射大小为64M,剩下的(256M-64M = 192M)留给DRAM。
4、PCIe配置空间的内存映射对32bit系统的影响
由于PCIe配置空间占用了256M内存空间,而且该被占用空间对DRAM来说是不可用的,这意味着256M空间消失于系统内存,这在32bit系统中更为明显。
比如,在32 bit WINxp中,理论上可以访问到的内存是4G,如果4G空间都被DRAM给占用,由于PCIe的存在,被PCIe占用的那部分内存空间对OS来说是不可用的,莫名的消失了最多256M内存,这也是大部分Intel Chipset允许BIOS来配置该空间大小的原因。
在64 bit 系统中,不存在这个问题,因为系统可以访问超过4G的内存空间,Intel Chipset会包含控制逻辑把该PCIe的内存映射到above 4G,这样跟DRAM就没有冲突。在64bit系统中,不可能使用2的64次方的内存吧。哈哈,总会没有使用到的内存空间。
5、访问PCIe配置空间的C转换代码
//**********************************************************************
unsigned long PCIeBase = 0xF0000000UL
unsigned long FinalAddress
unsigned long Bus = 0
unsigned long Device = 0
unsigned long Function = 0
unsigned long Register = 0
//**********************************************************************
void Convert_to_Memory()
{
FinalAddress = PCIeBase +
(Bus*0x100000UL) +
(Device*0x8000) +
(Function*0x1000) +
Register
}
//**********************************************************************
void Convert_to_Register()
{
Bus = (FinalAddress-PCIeBase) / (0x100000UL)
Device = (FinalAddress-PCIeBase - (Bus*0x100000UL)) / (0x8000)
Function = (FinalAddress-PCIeBase - (Bus*0x100000UL) -
(Device*0x8000)) / (0x1000)
Register = (FinalAddress) &(0x00000FFF)
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)