讲讲对PCIE总线协议的一点理解吧。感觉每一年又会多一点理解,但不懂得地方仍很多。
PCI总线是拓扑结构,PCI总线从0开始,不超过256(但一般不会一层一层挂太多)。Device不超过32,Function不超过8。如下图,挂在总线0,即Bus 0上的为根(root)设备,下面还挂设备的则为桥(Bridge),不再挂设备的即为设备(Device)。挂在桥下的设备总线号必然大于桥的总线号,下图中,PCI桥片1位Bus 0,PCI设备11为bus 1,PCI设备31为Bus 3。所以PCI桥片1的从属总线是1-3。
挂在PCI总线上的所有桥或设备都有特定的编号,即为Bus,Device,Function,不会重复。CPU对于挂在root上的设备都有固定定义,查看datasheet即可。
PCI设备的配置空间如下图:
该空间寄存器的详细信息可查看PCIE Spec,以Class Code为例,Class Code是判断PCI类型:LAN、VGA、存储设备等等。对于下面一段代码进行分析。
if (Hdr->ClassCode[2] == 0x0C) {
if (Hdr->ClassCode[1] < sizeof (gPciSerialClassCodes) / sizeof (VOID *)) {
Str = gPciSerialClassCodes[Hdr->ClassCode[1]];
if (Hdr->ClassCode[1] == 0x03) {
switch (Hdr->ClassCode[0]) {
case 0x00:
Str = L"USB-UHCI ";
break;
case 0x10:
Str = L"USB-OHCI ";
break;
case 0x20:
Str = L"USB-EHCI ";
break;
default:
break;
}
}
}
}
首先要判断Class Code的高位为0x0c(串行总线控制器),才能找到0x0c对应的Sub-Class Code,代码对应下图:
如何访问PCI设备?
两种方式:IO或memory
IO:即地址端口0xcf8/数据端口0xcfc,特定Bus,Device,Function按下图方式得到地址(实际中寄存器地址不用偏移两位),写入0xcf8;从0xcfc得到数据。
memory:方式类似,但Bus,Device,Function全部左移四位,bit31-28也根据CPU不同而不同,以Intel为例:Address = 0xE0000000+(Bus<<20)+(Device<<15)+(Function<<12)+Register.**(这里有点忘了,不确定!)o(╥﹏╥)o
以此可以看出IO方式只能访问256字节,即为PCI的配置空间。
PCI和PCIE的不同?
每种function包含4K的配置空间,前256字节为兼容配置空间,PCI的配置空间为0x00- 0xFF,PCIe设备还支持0x100 -0xFFF这段扩展配置空间。
怎么判断是PCI还是PCIE?
PCIE扩展空间的头指针存放于Capability Pointer(Config_ddress+0x34),从偏移地址0x34开始,读取值,该值为指向下一个ID的指针,判断ID是否为0x10,不是则读取指针+1的寄存器的值,该值为下一个ID的指针,直到ID为0x10,,当ID为0x10时,则该设备为PCIE设备,此ID开始的地方为PCIE Capability结构的开始,PCI设备不能使用这段空间。当Pointer为0,结束。
原话:PCI Express Capability ID Register
This read-only field must contain the value 10h,indicating this is the start of the PCI Express Capability register set
举个栗子:如下图,地址0x70开始为PCIE Capability结构配置空间
该空间的具体寄存器信息如下,里面包含设备种类(上下游)、负载、PCIE Link速度,Link宽度等等。
以速度和宽度举例:
下列代码是判断该PCIE速度(Gen1/Gen2/Gen3)和宽度(x1/x2/x4/x8/x16/x32).
// check is pcie capability
Status = Pci->Pci.Read (Pci, EfiPciIoWidthUint8, PcieCapabilityPtr, 1, &CapabilityId);
LinkStatusPtr = (UINT16)(PcieCapabilityPtr + 0x0C);
Status = Pci->Pci.Read (Pci, EfiPciIoWidthUint16, LinkStatusPtr, 1, &LinkStatusValue);
GenValue = (UINT8)(LinkStatusValue & 0xF);
LaneValue = (UINT8)((LinkStatusValue & 0x3FF) >> 4);
switch (GenValue) {
case PCIE_GEN1:
GenStr = L"Gen1";
break;
case PCIE_GEN2:
GenStr = L"Gen2";
break;
case PCIE_GEN3:
GenStr = L"Gen3";
break;
default:
break;
}
switch (LaneValue) {
case PCIE_LANEX1:
LaneStr = L"Lane:1";
break;
case PCIE_LANEX2:
LaneStr = L"Lane:2";
break;
case PCIE_LANEX4:
LaneStr = L"Lane:4";
break;
case PCIE_LANEX8:
LaneStr = L"Lane:8";
break;
case PCIE_LANEX12:
LaneStr = L"Lane:12";
break;
case PCIE_LANEX16:
LaneStr = L"Lane:16";
break;
case PCIE_LANEX32:
LaneStr = L"Lane:32";
break;
default:
break;
}
另:附上遍历主板所有PCI设备并判断是PCI还是PCIE设备的代码(将设备的地址送入0xCF8,从数据端0xCFC读出来的vendor值为0xFFFF,即为无效,没有设备。)
注:PCI空间是以double word读取,字节和字读取都无效。
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
void delay(unsigned int max);
void PCIE_SCAN(unsigned int Address);
int main()
{
char bus,device,function;
unsigned int cfg_add,ventor_val;
for(char i=0;i<5;i++) //scan bus
{
for(char j=0;j<32;j++) //scan device
{
for(char k=0;k<8;k++) //scan function
{
bus = i;device = j;function = k;
// the base address of every device
cfg_add = 0x80000000+bus*0x10000+(device*8)*0x100+function*0x100;
outpd(0xCF8,cfg_add);
// vendor number
ventor_val = inpd(0xCFC);
//judge the device exist or not
if(ventor_val!=0xffffffff)
{
printf("%04x ",ventor_val);
printf("bus = %02x,device = %02x,function = %02x",bus,device,function);
delay(100);
//find PCIE device
PCIE_SCAN(cfg_add);
}
}
}
}
system("pause");
return 0;
}
void delay(unsigned int max)
{
unsigned int d;
for(d=0;d<max;d++);
}
void PCIE_SCAN(unsigned int Address)
{
int flag =1;
unsigned int capability_pointer,capability_ID,capability_Npointer,p;
// printf("%0x",Address);
//capability pointer register is 0x34
outpd(0xCF8,(Address+0x34));
capability_pointer = inp(0xCFC);
while(flag)
{
outpd(0xCF8,(Address+capability_pointer));
//capability ID and next pointer and other 16 bits
p = inpd(0xCFC);
capability_ID = p%256;
if(capability_ID==0x10)
{
printf(" PCIE device\n");
goto ex;
}
p = p>>8;
capability_Npointer = p%256;
//When pointer is 0x00,there is no space of extension
if(capability_Npointer==0)
{
flag = 0;
}
capability_pointer = capability_Npointer;
}
printf(" PCI device\n");
ex: ;
}
在DOS下运行结果如下图: