对PCIE设备访问及其配置空间的一点理解

讲讲对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

#include

#include

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

}

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下运行结果如下图: