PCIE学习与递归实现遍历PCIe设备
UEFI学习(五)——PCIE学习
基本概念
-
编码技术:在高速的串行数据传输中,传送的数据被编码成自同步的数据流,就是将数据和时钟组合成单一的信号进行传送,使得接收方能容易准确地将数据和时钟分离,而且要达到令人满意的误码率,其关键技术在于串行传输中数据的编码方法。
-
直流均衡: 确保“0” 码元与“1” 码元个数的一致。
-
8B/10B 码的优势:(1)采用8B/10B编码方法,数据流中连续的“1” 或连续的“0”不超过5个,使接收端锁相环( PLL)能正常工作,避免接收端时钟漂移或同步丢失而引起数据丢失。 保证了1和0的相对平衡组合,而与数据值无关,简化了时钟恢复,降低了接收机成本。(2)DC补偿(3)检错:8B/10B编码采用冗余方式,将8位的数据和一些特殊字符按照特定的规则编码成10位的数据,根据这些规则,能检测出传输过程中单个和多个比特误码
-
每一个PCIe Function都有一个唯一的 PFA(PCI Function Address) 。 PFA由 bus number、device number、function number组成。在PFA中,Bus Number占用8位,Device Number占用5位,Function Number占用3位。显然,PCIe总线最多支持256个子总线,每个子总线最多支持32个设备,每个设备最多支持8个功能。
-
PCIe设备的连接关系:
-
PCIe Device的地址映射:
-
PCIe配置空间:PCIe spec规定PCIe设备必须提供独立的配置空间。配置空间里面存储了一些基本信息,如生产商、设备ID、设备类型、IRQ中断号,还有内部IO空间和MMIO空间的起始地址和大小。
每个Function(功能)有自己的4K配置空间。PCIe配置空间由两部分组成:PCI兼容配置空间(256Byte) 和 PCIe扩展配置空间(4K-256Bytes)。配置空间的定义分为两个Type, Type0和Type1。Type0 定义Device, Type1定义Bridge。 -
Type 0 Endpoint(终端设备)的配置:
-
Type 1 Bridge(桥接器)的配置:
递归实现PCIe设备遍历
实现功能:
- 串口输出函数编写
- 仿照printf函数编写自己的串口格式化输出函数,即传入参数个数不定
- 递归实现PCIe设备遍历并通过串口将信息输出(做完才发现自己其实也是基于深度优先的)
要点:
(1)串口配置:个人采用的是NCT5585D,在datasheet的第10章有描述了UART PORT的相关内容,如果要调用串口则先需要将全局变量寄存器中的相关使能位打开,而根据datesheet得知串口的配置寄存器位于IO space中,其基地址存放在logical device 2的CR 60h,61h中。而串口的配置寄存器就是基于此基地址加上偏移得到从下图的串口配置总表可以看到某些寄存器对于基地址有相同的偏移,其实是描述读和写两个过程的对应作用,根据描述可知,通过设置了UART Control Register (UCR) (Read/Write)的最高位BDLAB为0后,便能通过对偏移为0的寄存器进行串口读写操作。
(2)仿写printf函数:
void myDebugWriteStr(UINT8 *str,...)
{
UINT8 x = 0;
UINTN receviexfrommat=0;
UINT8 receiveBuffer[20];
char *pstr = NULL;
va_list arg; //定义va_list类型变量arg
va_start(arg,str); //arg初始化,将arg指向第一个可选参数
assert(str); //保证str不为空
while (*str)
{
if(*str == '%')
{
str++;
switch(*str)
{
case 's':
{
pstr = va_arg(arg,char*); //va_arg的第二个参数是你要返
//回的参数的类型,此处是char *
printf("%s\n", pstr);
while(*pstr!='\n')
{
myDebugWrite(*pstr);
gBS->Stall(1000);
pstr++;
}
break;
}
case 'c':
{
x = va_arg(arg,char); //返回参数列表中的当前参数,并使a
//rg指向参数列表中的下一个参数
printf("%c\n",x);
myDebugWrite((UINT8)x);
gBS->Stall(1000);
x++;
break;
}
case 'x':
{
receviexfrommat=va_arg(arg,UINTN);
printf("%08x\n",receviexfrommat );
sprintf(receiveBuffer,"%08x",receviexfrommat);
printf("%s\n",receiveBuffer);
for(int i=0;i<8;i++)
{
myDebugWrite((UINT8)receiveBuffer[i]);
gBS->Stall(1000);
}
printf("\n");
break;
}
case '0':
{
printf("testpoint:%c %c\n", *(str+1),*(str+2));
if(*(str+1)=='2'&&(*(str+2)=='x'||*(str+2)=='X'))
{
receviexfrommat=va_arg(arg,UINTN);
//printf("%02x\n",receviexfrommat );
sprintf(receiveBuffer,"%02x",receviexfrommat);
//printf("%s\n",receiveBuffer);
for(int i=0;i<2;i++)
{
myDebugWrite((UINT8)receiveBuffer[i]);
gBS->Stall(1000);
}
str++;
str++;
break;
}
else if(*(str+1)=='4'&&(*(str+2)=='x'||*(str+2)=='X'))
{
receviexfrommat=va_arg(arg,UINTN);
//printf("%02x\n",receviexfrommat );
sprintf(receiveBuffer,"%04x",receviexfrommat);
//printf("%s\n",receiveBuffer);
for(int i=0;i<4;i++)
{
myDebugWrite((UINT8)receiveBuffer[i]);
gBS->Stall(1000);
}
str++;
str++;
break;
}
else if(*(str+1)=='8'&&(*(str+2)=='x'||*(str+2)=='X'))
{
receviexfrommat=va_arg(arg,UINTN);
//printf("%02x\n",receviexfrommat );
sprintf(receiveBuffer,"%08x",receviexfrommat);
//printf("%s\n",receiveBuffer);
for(int i=0;i<8;i++)
{
myDebugWrite((UINT8)receiveBuffer[i]);
gBS->Stall(1000);
}
str++;
str++;
break;
}
else
{
myDebugWrite(*(str-1));
gBS->Stall(1000);
myDebugWrite(*str);
gBS->Stall(1000);
break;
}
}
default:
{
myDebugWrite(*(str-1));
gBS->Stall(1000);
myDebugWrite(*str);
gBS->Stall(1000);
break;
}
}
str++;
}
else
{
myDebugWrite(*str);
gBS->Stall(1000);
str++;
}
}
va_end(arg); //把arg指针清为NULL
}
(3)递归实现PCIe设备遍历:
1. 获取分配给PCIe设备的内存空间的基地址:通过调用gEfiPciRootBridgeIoProtocolGuid,根据
gPCIRootBridgeIO->Pci.Read (
gPCIRootBridgeIO,
EfiPciWidthUint8,
Address,
1,
®Receive
);
来读取指定地址:Address,数据长度:1,返回接受到的数据到指定buffer:®Receive,其中Address = EFI_PCI_ADDRESS (Bus, Device, Func, Offset);
其中EFI_PCI_ADDRESS (Bus, Device, Func, Offset)
为#define EFI_PCI_ADDRESS(bus, dev, func, reg) \
((UINT64) ((((UINTN) bus) << 24) + (((UINTN) dev) << 16) + (((UINTN) func) << 8) + ((UINTN) reg)))
通过此功能将其封装成读取指定BDF(Bus, Device, Func, Offset)的功能函数
2. 递归实现的思想:参考文章链接: [https://www.cnblogs.com/szhb-5251/p/11620310.html](https://www.cnblogs.com/szhb-5251/p/11620310.html)
在实现遍历的时候遇到的难点是:设备号(device)与其功能号(func)因为由硬件分配并不连续所以采用枚举的方式去遍历寻找;
而面对switch桥时,也要考虑桥是否有其他功能号,桥的其他功能号也可能作为switch桥或者设备,但此时若为设备,则旗下没有第二功能,也无需进行功能号的循环遍历寻找;