Pci中segment的实现
龙芯新一代处理器3C5000L, 从4W(64核)板卡开始,将支持双桥7A.
文章目录
本章节就双桥7A,在UEFI添加segment的支持.
简介
PCI设备编号: Segment: Bus: DeviceNum: FunctionNum .
PCI Scaning
自PC在1981年被IBM发明以来,主板上都有扩展槽用于扩充计算机功能。现在最常见的扩展槽是PCIe插槽,实际上在你看不见的计算机主板芯片内部,各种硬件控制模块大部分也是以PCIe设备的形式挂载到了一颗或者几颗PCI/PCIe设备树上。固件和操作系统正是通过枚举设备树们才能发现绝大多数即插即用(PNP)设备的。
如何枚举?: PCI设备的扫描是基于深度优先搜索算法(DFS:Depth First Search),也就是说,下级分支最多的PCI桥将最先完成其子设备的扫描。下面我们以图片来具体说明,如何一步步完成PCI 设备扫描的?
- PciHost (主桥)扫描Bus0(与HOST主桥直接相连的PCI总线)上的设备: 首先先忽略Bus0上的D1,D2等不会挂接PCI桥的设备,主桥发现Bridge1后, 将Bridge1下面Bus定为Bus1,系统将初始化Bridge1的配置空间,并将该桥的PrimaryBus核SecondaryBus分别设置成0,1(即上下游总线),由于还无法确定Bridge1下挂载设备的具体情况,系统先暂时将SubordinateBus设为0xFF.
- 系统开始扫描Bus1, 发现Bridge2, 系统将Bridge2下面的PciBus定为Bus2, 并将PrimaryBus和SecondaryBus设置成1和2, SubordinateBus设置为0xFF.
- 系统开始扫描Bus2, 发现Bridge4, 系统将Bridge4下面的PciBus定为Bus3,并将PrimaryBus和SecondaryBus设置成2和3.
- 系统继续扫描Bus3, 发现总线上没有Bridge了,意味着该PCI总线上已经没有任何挂载下游总线了,因此Bridge4的SubordinateBus的值已经可以确定为3.
- 完成Bus3的扫描后,系统返回到了Bus2继续扫描, 发现Bus2下面已经没有其他Bridge了,此时Bridge2的SubordinateBus的值也已经可以确定为3.
- 完成Bus2的扫描后,系统返回到Bus1继续扫描, 会发现Bridge3,系统将Bridge3下面的PciBus定为Bus4, 并将Bridge4的PrimaryBus和SecondaryBus寄存器分别设置成1和4(即上游总线Bus1,下游总线Bus4).
- 继续扫描Bus4,发现下面没有任何Bridge了,意味着该PCI总线下已经没有挂载下游总线了, 因此Bridge3的SubordinateBus的值已经可以确定为4了.
- 完成Bus4的扫描后, 系统返回到Bus1继续扫描, 发现Bus1下面已经没有Bridge了, 此时Bridge1的SubordinateBus的值已经可以确定为4.
- 继续返回Bus0进行扫描, (如果Bus0上还有其它的Bridge,将重复上述的步骤进行扫描),至此本例中的PCI设备扫描已经完成.
标准PCI & X86 & LoongArch定义的PCI访问方式
1) UEFI标准PCI配置空间访问
UEFI下 PCI_SEGMENT_LIB_ADDRESS_STRUCTURE 该结构很好的定义了这个标准, 其中32-47bit定义了segment字段.
19 typedef struct {
20 UINT64 Register : 12;
21 UINT64 Function : 3;
22 UINT64 Device : 5;
23 UINT64 Bus : 8;
24 UINT64 Reserved1 : 4;
25 UINT64 Segment : 16;
26 UINT64 Reserved2 : 16;
27 } PCI_SEGMENT_LIB_ADDRESS_STRUCTURE;
2) X86-PCI访问
在X86体系中, 我们可以通过两个寄存器来访问PCI的配置空间: CONFIG_ADDRESS & CONFIG_DATA: 即 0xCF8 & 0xCFC
UEFI下标准 X86 CF8:
3) LoongArch-PCI访问
龙芯PCI配置访问:
为何增添Segment?
- 我们在扫描PCI的时候,或者说访问PCI设备的时候,我们只需知道这个PCIE设备的Bus & Device & Function & Offset 即可访问到对应的配置空间, 所以我们可以将双桥看成一个桥来分配资源(我们也这样做过,Segment=0), 但是访问也相应的受限了(两颗Chipset需一致).
所以我们针对双桥的产品来说, 往往使用Segment来区别访问每一个桥的信息与分配资源更加独立,更加的规范, 并有效的防止桥片间差异化的影响, 比如我们假想可以使用下一代产品7A2000 + 7A1000的搭配方式.
如需了解更多优点 & UEFI对Segment的支持 & Linux对Segment的支持详解对应Code.
Code Adding
InitializePciHostBridgeDxe
- 初始化PCI主桥资源, 即7A1000的PCIE资源.
- InitializePciHostBridge -> PciHostBridgeGetRootBridges (这是个NULL函数需要我们自己实现, 是与平台强相关来初始化RootBridge信息的). 即我们需要初始化两个HostBridge的如下资源信息(桥0 Segment=0; 桥1 Segment=1 ):
46 typedef struct {
47 UINT32 Segment; ///< Segment number.
48 UINT64 Supports; ///< Supported attributes.
49 ///< Refer to EFI_PCI_ATTRIBUTE_xxx used by GetAttributes()
50 ///< and SetAttributes() in EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
51 UINT64 Attributes; ///< Initial attributes.
52 ///< Refer to EFI_PCI_ATTRIBUTE_xxx used by GetAttributes()
53 ///< and SetAttributes() in EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
54 BOOLEAN DmaAbove4G; ///< DMA above 4GB memory.
55 ///< Set to TRUE when root bridge supports DMA above 4GB memory.
56 BOOLEAN NoExtendedConfigSpace; ///< When FALSE, the root bridge supports
57 ///< Extended (4096-byte) Configuration Space.
58 ///< When TRUE, the root bridge supports
59 ///< 256-byte Configuration Space only.
60 BOOLEAN ResourceAssigned; ///< Resource assignment status of the root bridge.
61 ///< Set to TRUE if Bus/IO/MMIO resources for root bridge have been assigned.
62 UINT64 AllocationAttributes; ///< Allocation attributes.
63 ///< Refer to EFI_PCI_HOST_BRIDGE_COMBINE_MEM_PMEM and
64 ///< EFI_PCI_HOST_BRIDGE_MEM64_DECODE used by GetAllocAttributes()
65 ///< in EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL.
66 PCI_ROOT_BRIDGE_APERTURE Bus; ///< Bus aperture which can be used by the root bridge.
67 PCI_ROOT_BRIDGE_APERTURE Io; ///< IO aperture which can be used by the root bridge.
68 PCI_ROOT_BRIDGE_APERTURE Mem; ///< MMIO aperture below 4GB which can be used by the root bridge.
69 PCI_ROOT_BRIDGE_APERTURE MemAbove4G; ///< MMIO aperture above 4GB which can be used by the root bridge.
70 PCI_ROOT_BRIDGE_APERTURE PMem; ///< Prefetchable MMIO aperture below 4GB which can be used by the root bridge.
71 PCI_ROOT_BRIDGE_APERTURE PMemAbove4G; ///< Prefetchable MMIO aperture above 4GB which can be used by the root bridge.
72 EFI_DEVICE_PATH_PROTOCOL *DevicePath; ///< Device path.
73 } PCI_ROOT_BRIDGE;
- InitializePciHostBridge -> CreateRootBridge 创建 PCI_ROOT_BRIDGE_INSTANCE 实例, 即初始化Instance : PCI_ROOT_BRIDGE 是 PCI_ROOT_BRIDGE_INSTANCE的子集(父类).
60 #define PCI_ROOT_BRIDGE_SIGNATURE SIGNATURE_32 ('_', 'p', 'r', 'b')
61
62 typedef struct {
63 UINT32 Signature;
64 LIST_ENTRY Link;
65 EFI_HANDLE Handle;
66 UINT64 AllocationAttributes;
67 UINT64 Attributes;
68 UINT64 Supports;
69 PCI_RES_NODE ResAllocNode[TypeMax];
70 PCI_ROOT_BRIDGE_APERTURE Bus;
71 PCI_ROOT_BRIDGE_APERTURE Io;
72 PCI_ROOT_BRIDGE_APERTURE Mem;
73 PCI_ROOT_BRIDGE_APERTURE PMem;
74 PCI_ROOT_BRIDGE_APERTURE MemAbove4G;
75 PCI_ROOT_BRIDGE_APERTURE PMemAbove4G;
76 BOOLEAN DmaAbove4G;
77 BOOLEAN NoExtendedConfigSpace;
78 VOID *ConfigBuffer;
79 EFI_DEVICE_PATH_PROTOCOL *DevicePath;
80 CHAR16 *DevicePathStr;
81 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL RootBridgeIo; // PCI_ROOT_BRIDGE
82
83 BOOLEAN ResourceSubmitted;
84 LIST_ENTRY Maps;
85 } PCI_ROOT_BRIDGE_INSTANCE;
gEfiPciRootBridgeIoProtocolGuid : 即&RootBridge->RootBridgeIo (注册一系列回调函数, 用于对This->Segment指向各自桥的PciBus:Device:Function:Reg访问(包含配置空间及MEM等空间访问方式)). 即底层的配置为上一层产生配置和产生配置服务.(具体针对Segment的访问修改看步骤4)
- ResourceAssigned(资源分配Flag) 控制 HostBridge->ResAlloc的注册:
在初始化HostBridge时, 若已经分配Host资源将不再注册PciBus扫描时所需的gEfiPciHostBridgeResourceAllocationProtocolGuid.
结尾必须注册的Protocol: gEfiDevicePathProtocolGuid & gEfiPciRootBridgeIoProtocolGuid.
- 注册配置空间与MEM&IO空间区分Segment的访问: RootBridge->RootBridgeIo.Pci.Read(RootBridgeIoPciRead)->RootBridgeIoPciAccess
964 EFI_STATUS
965 EFIAPI
966 RootBridgeIoPciAccess (
967 IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
968 IN BOOLEAN Read,
969 IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,
970 IN UINT64 Address,
971 IN UINTN Count,
972 IN OUT VOID *Buffer
973 )
974 {
975 EFI_STATUS Status;
976 PCI_ROOT_BRIDGE_INSTANCE *RootBridge;
977 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_PCI_ADDRESS PciAddress;
978 UINT8 *Uint8Buffer;
979 UINT8 InStride;
980 UINT8 OutStride;
981 UINTN Size;
982
983 Status = RootBridgeIoCheckParameter (This, PciOperation, Width, Address, Count, Buffer);
984 if (EFI_ERROR (Status)) {
985 return Status;
986 }
987
988 //
989 // Read Pci configuration space
990 //
991 RootBridge = ROOT_BRIDGE_FROM_THIS (This);
992 CopyMem (&PciAddress, &Address, sizeof (PciAddress));
993
994 if (PciAddress.ExtendedRegister == 0) {
995 PciAddress.ExtendedRegister = PciAddress.Register;
996 }
997
998 Address = PCI_SEGMENT_LIB_ADDRESS (
999 RootBridge->RootBridgeIo.SegmentNumber,
1000 PciAddress.Bus,
1001 PciAddress.Device,
1002 PciAddress.Function,
1003 PciAddress.ExtendedRegister
1004 );
1005
1006 //
1007 // Select loop based on the width of the transfer
1008 //
1009 InStride = mInStride[Width];
1010 OutStride = mOutStride[Width];
1011 Size = (UINTN) (1 << (Width & 0x03));
1012 for (Uint8Buffer = Buffer; Count > 0; Address += InStride, Uint8Buffer += OutStride, Count--) {
1013 if (Read) {
1014 PciSegmentReadBuffer (Address, Size, Uint8Buffer);
1015 } else {
1016 PciSegmentWriteBuffer (Address, Size, Uint8Buffer);
1017 }
1018 }
1019
1020 return EFI_SUCCESS;
1021 }
梳理代码最终调用PciSegmentReadBuffer(这个Address是标准PCI访问地址) -> PciRead(需要我们自己实现来解析包含Segment的标准PCI地址来转换成我们架构的地址空间进行访问) 先借助Cf8参考修改LoongArch转换如下:
124 UINT8
125 EFIAPI
126 PciCf8Read8 (
127 IN UINTN Address
128 )
129 {
130 UINTN Tmp;
131 UINT32 Val;
132 UINT32 Result;
133 UINT32 ValRaw;
134 UINT64 HtConfType0Addr;
135 UINT64 HtConfType1Addr;
136 UINT64 BusNum;
137
138 HtConfType0Addr = HT_CONF_TYPE0_ADDR;
139 HtConfType1Addr = HT_CONF_TYPE1_ADDR;
140 if (((Address >> 32) & 0xffff) == 1) { // Address >> 32 is Segment , LoongArch's chipset1 on Node5
141 HtConfType0Addr |= (NODE5 << NODE_OFFSET);
142 HtConfType1Addr |= (NODE5 << NODE_OFFSET);
143 }
144 Address &= 0xffffffff;
145
146
147 Tmp = Address & 0xff;
148 Address &= ~0xff;
149 Address >>= 4;
150 Address |= Tmp;
151 Result = 0;
152 ...
同理MemRead&IoRead等回调函数都是可以通过 RootBridge->RootBridgeIo.SegmentNumber 来区分访问的Chipset 以此来访问不同桥的地址空间, 简贴代码如下:
661 EFI_STATUS
662 EFIAPI
663 RootBridgeIoMemRead (
664 IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
665 IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,
666 IN UINT64 Address,
667 IN UINTN Count,
668 OUT VOID *Buffer
669 )
670 {
671 EFI_STATUS Status;
672 UINT64 Addr;
673 PCI_ROOT_BRIDGE_INSTANCE *RootBridge;
674
675 Status = RootBridgeIoCheckParameter (This, MemOperation, Width, Address,
676 Count, Buffer);
677 RootBridge = ROOT_BRIDGE_FROM_THIS (This);
678
679 if (EFI_ERROR (Status)) {
680 return Status;
681 }
682
683 Addr = (Address & ADDR_MASK_32BIT) | PCI_UNCACHE_ADDR_BASE;
684 if (RootBridge->RootBridgeIo.SegmentNumber == 1) { //Chipset 1 on Node5
685 Addr |= 0x500000000000ULL;
686 }
687
688 return mCpuIo->Mem.Read (mCpuIo, (EFI_CPU_IO_PROTOCOL_WIDTH) Width, Addr, Count, Buffer);
689 }
我们借助了Cf8的库进行了修改配置空间的访问梳理, 我们真正的Code将脱离Cf8,写我们自己PciCfgAccess.
- 关于我们为PciBus注册的Protocol如何被使用,就不详细展开了,它是标准的PCI深度优先扫描, 最底层的访问即是回调的这一章节的地址访问, 而扫描时如何详细分配资源还需大家一同学习了解.
总结
使用Segment来区分双桥ID, 可以更好的维护与清晰代码结构, 创建的两个RootBridge可以彼此分离, 各自维护各自不同类的Instance.Ops, 并且EDK2 Core内代码对Segment有更好的功能延伸, 减少后期成本.
知识小提示
PCI接口带宽:
关于PCIE-Spec详细手册请点击 PCIE-SPEC手册, 包含配置空间及Capability寄存器.
- 临近7.1百年: 祝好!
- 虚心使人进步,骄傲使人落后,我们应当永远记住这个真理~