ArmVirtPkg/FdtPciHostBridgeLib: map ECAM and I/O spaces in GCD memory map
[mirror_edk2.git] / ArmVirtPkg / Library / FdtPciHostBridgeLib / FdtPciHostBridgeLib.c
CommitLineData
d4cb9a30
AB
1/** @file\r
2 PCI Host Bridge Library instance for pci-ecam-generic DT nodes\r
3\r
4 Copyright (c) 2016, Linaro Ltd. All rights reserved.<BR>\r
5\r
6 This program and the accompanying materials are licensed and made available\r
7 under the terms and conditions of the BSD License which accompanies this\r
8 distribution. The full text of the license may be found at\r
9 http://opensource.org/licenses/bsd-license.php.\r
10\r
11 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
12 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR\r
13 IMPLIED.\r
14\r
15**/\r
16#include <PiDxe.h>\r
17#include <Library/PciHostBridgeLib.h>\r
18#include <Library/DebugLib.h>\r
19#include <Library/DevicePathLib.h>\r
66e06a72 20#include <Library/DxeServicesTableLib.h>\r
d4cb9a30
AB
21#include <Library/MemoryAllocationLib.h>\r
22#include <Library/PcdLib.h>\r
23#include <Library/UefiBootServicesTableLib.h>\r
24\r
25#include <Protocol/FdtClient.h>\r
26#include <Protocol/PciRootBridgeIo.h>\r
27#include <Protocol/PciHostBridgeResourceAllocation.h>\r
28\r
29#pragma pack(1)\r
30typedef struct {\r
31 ACPI_HID_DEVICE_PATH AcpiDevicePath;\r
32 EFI_DEVICE_PATH_PROTOCOL EndDevicePath;\r
33} EFI_PCI_ROOT_BRIDGE_DEVICE_PATH;\r
34#pragma pack ()\r
35\r
36STATIC EFI_PCI_ROOT_BRIDGE_DEVICE_PATH mEfiPciRootBridgeDevicePath = {\r
37 {\r
38 {\r
39 ACPI_DEVICE_PATH,\r
40 ACPI_DP,\r
41 {\r
42 (UINT8) (sizeof(ACPI_HID_DEVICE_PATH)),\r
43 (UINT8) ((sizeof(ACPI_HID_DEVICE_PATH)) >> 8)\r
44 }\r
45 },\r
46 EISA_PNP_ID(0x0A03),\r
47 0\r
48 },\r
49\r
50 {\r
51 END_DEVICE_PATH_TYPE,\r
52 END_ENTIRE_DEVICE_PATH_SUBTYPE,\r
53 {\r
54 END_DEVICE_PATH_LENGTH,\r
55 0\r
56 }\r
57 }\r
58};\r
59\r
60GLOBAL_REMOVE_IF_UNREFERENCED\r
61CHAR16 *mPciHostBridgeLibAcpiAddressSpaceTypeStr[] = {\r
62 L"Mem", L"I/O", L"Bus"\r
63};\r
64\r
65//\r
66// We expect the "ranges" property of "pci-host-ecam-generic" to consist of\r
67// records like this.\r
68//\r
69#pragma pack (1)\r
70typedef struct {\r
71 UINT32 Type;\r
72 UINT64 ChildBase;\r
73 UINT64 CpuBase;\r
74 UINT64 Size;\r
75} DTB_PCI_HOST_RANGE_RECORD;\r
76#pragma pack ()\r
77\r
78#define DTB_PCI_HOST_RANGE_RELOCATABLE BIT31\r
79#define DTB_PCI_HOST_RANGE_PREFETCHABLE BIT30\r
80#define DTB_PCI_HOST_RANGE_ALIASED BIT29\r
81#define DTB_PCI_HOST_RANGE_MMIO32 BIT25\r
82#define DTB_PCI_HOST_RANGE_MMIO64 (BIT25 | BIT24)\r
83#define DTB_PCI_HOST_RANGE_IO BIT24\r
84#define DTB_PCI_HOST_RANGE_TYPEMASK (BIT31 | BIT30 | BIT29 | BIT25 | BIT24)\r
85\r
66e06a72
AB
86STATIC\r
87EFI_STATUS\r
88MapGcdMmioSpace (\r
89 IN UINT64 Base,\r
90 IN UINT64 Size\r
91 )\r
92{\r
93 EFI_STATUS Status;\r
94\r
95 Status = gDS->AddMemorySpace (EfiGcdMemoryTypeMemoryMappedIo, Base, Size,\r
96 EFI_MEMORY_UC);\r
97 if (EFI_ERROR (Status)) {\r
98 DEBUG ((DEBUG_ERROR,\r
99 "%a: failed to add GCD memory space for region [0x%Lx+0x%Lx)\n",\r
100 __FUNCTION__, Base, Size));\r
101 return Status;\r
102 }\r
103\r
104 Status = gDS->SetMemorySpaceAttributes (Base, Size, EFI_MEMORY_UC);\r
105 if (EFI_ERROR (Status)) {\r
106 DEBUG ((DEBUG_ERROR,\r
107 "%a: failed to set memory space attributes for region [0x%Lx+0x%Lx)\n",\r
108 __FUNCTION__, Base, Size));\r
109 }\r
110 return Status;\r
111}\r
112\r
d4cb9a30
AB
113STATIC\r
114EFI_STATUS\r
115ProcessPciHost (\r
116 OUT UINT64 *IoBase,\r
117 OUT UINT64 *IoSize,\r
9d64ac23
AB
118 OUT UINT64 *Mmio32Base,\r
119 OUT UINT64 *Mmio32Size,\r
120 OUT UINT64 *Mmio64Base,\r
121 OUT UINT64 *Mmio64Size,\r
d4cb9a30
AB
122 OUT UINT32 *BusMin,\r
123 OUT UINT32 *BusMax\r
124 )\r
125{\r
126 FDT_CLIENT_PROTOCOL *FdtClient;\r
127 INT32 Node;\r
128 UINT64 ConfigBase, ConfigSize;\r
129 CONST VOID *Prop;\r
130 UINT32 Len;\r
131 UINT32 RecordIdx;\r
132 EFI_STATUS Status;\r
133 UINT64 IoTranslation;\r
9d64ac23
AB
134 UINT64 Mmio32Translation;\r
135 UINT64 Mmio64Translation;\r
d4cb9a30
AB
136\r
137 //\r
138 // The following output arguments are initialized only in\r
139 // order to suppress '-Werror=maybe-uninitialized' warnings\r
140 // *incorrectly* emitted by some gcc versions.\r
141 //\r
142 *IoBase = 0;\r
9d64ac23
AB
143 *Mmio32Base = 0;\r
144 *Mmio64Base = MAX_UINT64;\r
d4cb9a30
AB
145 *BusMin = 0;\r
146 *BusMax = 0;\r
147\r
148 //\r
9d64ac23 149 // *IoSize, *Mmio##Size and IoTranslation are initialized to zero because the\r
d4cb9a30
AB
150 // logic below requires it. However, since they are also affected by the issue\r
151 // reported above, they are initialized early.\r
152 //\r
153 *IoSize = 0;\r
9d64ac23
AB
154 *Mmio32Size = 0;\r
155 *Mmio64Size = 0;\r
d4cb9a30
AB
156 IoTranslation = 0;\r
157\r
158 Status = gBS->LocateProtocol (&gFdtClientProtocolGuid, NULL,\r
159 (VOID **)&FdtClient);\r
160 ASSERT_EFI_ERROR (Status);\r
161\r
162 Status = FdtClient->FindCompatibleNode (FdtClient, "pci-host-ecam-generic",\r
163 &Node);\r
164 if (EFI_ERROR (Status)) {\r
165 DEBUG ((EFI_D_INFO,\r
166 "%a: No 'pci-host-ecam-generic' compatible DT node found\n",\r
167 __FUNCTION__));\r
168 return EFI_NOT_FOUND;\r
169 }\r
170\r
171 DEBUG_CODE (\r
172 INT32 Tmp;\r
173\r
174 //\r
175 // A DT can legally describe multiple PCI host bridges, but we are not\r
176 // equipped to deal with that. So assert that there is only one.\r
177 //\r
178 Status = FdtClient->FindNextCompatibleNode (FdtClient,\r
179 "pci-host-ecam-generic", Node, &Tmp);\r
180 ASSERT (Status == EFI_NOT_FOUND);\r
181 );\r
182\r
183 Status = FdtClient->GetNodeProperty (FdtClient, Node, "reg", &Prop, &Len);\r
184 if (EFI_ERROR (Status) || Len != 2 * sizeof (UINT64)) {\r
185 DEBUG ((EFI_D_ERROR, "%a: 'reg' property not found or invalid\n",\r
186 __FUNCTION__));\r
187 return EFI_PROTOCOL_ERROR;\r
188 }\r
189\r
190 //\r
191 // Fetch the ECAM window.\r
192 //\r
193 ConfigBase = SwapBytes64 (((CONST UINT64 *)Prop)[0]);\r
194 ConfigSize = SwapBytes64 (((CONST UINT64 *)Prop)[1]);\r
195\r
196 //\r
197 // Fetch the bus range (note: inclusive).\r
198 //\r
199 Status = FdtClient->GetNodeProperty (FdtClient, Node, "bus-range", &Prop,\r
200 &Len);\r
201 if (EFI_ERROR (Status) || Len != 2 * sizeof (UINT32)) {\r
202 DEBUG ((EFI_D_ERROR, "%a: 'bus-range' not found or invalid\n",\r
203 __FUNCTION__));\r
204 return EFI_PROTOCOL_ERROR;\r
205 }\r
206 *BusMin = SwapBytes32 (((CONST UINT32 *)Prop)[0]);\r
207 *BusMax = SwapBytes32 (((CONST UINT32 *)Prop)[1]);\r
208\r
209 //\r
210 // Sanity check: the config space must accommodate all 4K register bytes of\r
211 // all 8 functions of all 32 devices of all buses.\r
212 //\r
213 if (*BusMax < *BusMin || *BusMax - *BusMin == MAX_UINT32 ||\r
214 DivU64x32 (ConfigSize, SIZE_4KB * 8 * 32) < *BusMax - *BusMin + 1) {\r
215 DEBUG ((EFI_D_ERROR, "%a: invalid 'bus-range' and/or 'reg'\n",\r
216 __FUNCTION__));\r
217 return EFI_PROTOCOL_ERROR;\r
218 }\r
219\r
220 //\r
221 // Iterate over "ranges".\r
222 //\r
223 Status = FdtClient->GetNodeProperty (FdtClient, Node, "ranges", &Prop, &Len);\r
224 if (EFI_ERROR (Status) || Len == 0 ||\r
225 Len % sizeof (DTB_PCI_HOST_RANGE_RECORD) != 0) {\r
226 DEBUG ((EFI_D_ERROR, "%a: 'ranges' not found or invalid\n", __FUNCTION__));\r
227 return EFI_PROTOCOL_ERROR;\r
228 }\r
229\r
230 for (RecordIdx = 0; RecordIdx < Len / sizeof (DTB_PCI_HOST_RANGE_RECORD);\r
231 ++RecordIdx) {\r
232 CONST DTB_PCI_HOST_RANGE_RECORD *Record;\r
233\r
234 Record = (CONST DTB_PCI_HOST_RANGE_RECORD *)Prop + RecordIdx;\r
235 switch (SwapBytes32 (Record->Type) & DTB_PCI_HOST_RANGE_TYPEMASK) {\r
236 case DTB_PCI_HOST_RANGE_IO:\r
237 *IoBase = SwapBytes64 (Record->ChildBase);\r
238 *IoSize = SwapBytes64 (Record->Size);\r
239 IoTranslation = SwapBytes64 (Record->CpuBase) - *IoBase;\r
240\r
241 ASSERT (PcdGet64 (PcdPciIoTranslation) == IoTranslation);\r
242 break;\r
243\r
244 case DTB_PCI_HOST_RANGE_MMIO32:\r
9d64ac23
AB
245 *Mmio32Base = SwapBytes64 (Record->ChildBase);\r
246 *Mmio32Size = SwapBytes64 (Record->Size);\r
247 Mmio32Translation = SwapBytes64 (Record->CpuBase) - *Mmio32Base;\r
d4cb9a30 248\r
9d64ac23
AB
249 if (*Mmio32Base > MAX_UINT32 || *Mmio32Size > MAX_UINT32 ||\r
250 *Mmio32Base + *Mmio32Size > SIZE_4GB) {\r
d4cb9a30
AB
251 DEBUG ((EFI_D_ERROR, "%a: MMIO32 space invalid\n", __FUNCTION__));\r
252 return EFI_PROTOCOL_ERROR;\r
253 }\r
254\r
9d64ac23 255 ASSERT (PcdGet64 (PcdPciMmio32Translation) == Mmio32Translation);\r
d4cb9a30 256\r
9d64ac23 257 if (Mmio32Translation != 0) {\r
d4cb9a30 258 DEBUG ((EFI_D_ERROR, "%a: unsupported nonzero MMIO32 translation "\r
9d64ac23
AB
259 "0x%Lx\n", __FUNCTION__, Mmio32Translation));\r
260 return EFI_UNSUPPORTED;\r
261 }\r
262\r
263 break;\r
264\r
265 case DTB_PCI_HOST_RANGE_MMIO64:\r
266 *Mmio64Base = SwapBytes64 (Record->ChildBase);\r
267 *Mmio64Size = SwapBytes64 (Record->Size);\r
268 Mmio64Translation = SwapBytes64 (Record->CpuBase) - *Mmio64Base;\r
269\r
270 ASSERT (PcdGet64 (PcdPciMmio64Translation) == Mmio64Translation);\r
271\r
272 if (Mmio64Translation != 0) {\r
273 DEBUG ((EFI_D_ERROR, "%a: unsupported nonzero MMIO64 translation "\r
274 "0x%Lx\n", __FUNCTION__, Mmio64Translation));\r
d4cb9a30
AB
275 return EFI_UNSUPPORTED;\r
276 }\r
277\r
278 break;\r
279 }\r
280 }\r
9d64ac23 281 if (*IoSize == 0 || *Mmio32Size == 0) {\r
d4cb9a30
AB
282 DEBUG ((EFI_D_ERROR, "%a: %a space empty\n", __FUNCTION__,\r
283 (*IoSize == 0) ? "IO" : "MMIO32"));\r
284 return EFI_PROTOCOL_ERROR;\r
285 }\r
286\r
287 //\r
288 // The dynamic PCD PcdPciExpressBaseAddress should have already been set,\r
289 // and should match the value we found in the DT node.\r
290 //\r
291 ASSERT (PcdGet64 (PcdPciExpressBaseAddress) == ConfigBase);\r
292\r
293 DEBUG ((EFI_D_INFO, "%a: Config[0x%Lx+0x%Lx) Bus[0x%x..0x%x] "\r
9d64ac23
AB
294 "Io[0x%Lx+0x%Lx)@0x%Lx Mem32[0x%Lx+0x%Lx)@0x0 Mem64[0x%Lx+0x%Lx)@0x0\n",\r
295 __FUNCTION__, ConfigBase, ConfigSize, *BusMin, *BusMax, *IoBase, *IoSize,\r
296 IoTranslation, *Mmio32Base, *Mmio32Size, *Mmio64Base, *Mmio64Size));\r
66e06a72
AB
297\r
298 // Map the ECAM space in the GCD memory map\r
299 Status = MapGcdMmioSpace (ConfigBase, ConfigSize);\r
300 ASSERT_EFI_ERROR (Status);\r
301 if (EFI_ERROR (Status)) {\r
302 return Status;\r
303 }\r
304\r
305 //\r
306 // Map the MMIO window that provides I/O access - the PCI host bridge code\r
307 // is not aware of this translation and so it will only map the I/O view\r
308 // in the GCD I/O map.\r
309 //\r
310 Status = MapGcdMmioSpace (*IoBase + IoTranslation, *IoSize);\r
311 ASSERT_EFI_ERROR (Status);\r
312\r
313 return Status;\r
d4cb9a30
AB
314}\r
315\r
316STATIC PCI_ROOT_BRIDGE mRootBridge;\r
317\r
318/**\r
319 Return all the root bridge instances in an array.\r
320\r
321 @param Count Return the count of root bridge instances.\r
322\r
323 @return All the root bridge instances in an array.\r
324 The array should be passed into PciHostBridgeFreeRootBridges()\r
325 when it's not used.\r
326**/\r
327PCI_ROOT_BRIDGE *\r
328EFIAPI\r
329PciHostBridgeGetRootBridges (\r
330 UINTN *Count\r
331 )\r
332{\r
333 UINT64 IoBase, IoSize;\r
334 UINT64 Mmio32Base, Mmio32Size;\r
9d64ac23 335 UINT64 Mmio64Base, Mmio64Size;\r
d4cb9a30
AB
336 UINT32 BusMin, BusMax;\r
337 EFI_STATUS Status;\r
338\r
339 if (PcdGet64 (PcdPciExpressBaseAddress) == 0) {\r
340 DEBUG ((EFI_D_INFO, "%a: PCI host bridge not present\n", __FUNCTION__));\r
341\r
342 *Count = 0;\r
343 return NULL;\r
344 }\r
345\r
9d64ac23
AB
346 Status = ProcessPciHost (&IoBase, &IoSize, &Mmio32Base, &Mmio32Size,\r
347 &Mmio64Base, &Mmio64Size, &BusMin, &BusMax);\r
d4cb9a30
AB
348 if (EFI_ERROR (Status)) {\r
349 DEBUG ((EFI_D_ERROR, "%a: failed to discover PCI host bridge: %r\n",\r
350 __FUNCTION__, Status));\r
351 *Count = 0;\r
352 return NULL;\r
353 }\r
354\r
355 *Count = 1;\r
356\r
357 mRootBridge.Segment = 0;\r
358 mRootBridge.Supports = EFI_PCI_ATTRIBUTE_ISA_IO_16 |\r
359 EFI_PCI_ATTRIBUTE_ISA_MOTHERBOARD_IO |\r
360 EFI_PCI_ATTRIBUTE_VGA_IO_16 |\r
361 EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO_16;\r
362 mRootBridge.Attributes = mRootBridge.Supports;\r
363\r
4c0b2d25 364 mRootBridge.DmaAbove4G = TRUE;\r
d4cb9a30
AB
365 mRootBridge.NoExtendedConfigSpace = FALSE;\r
366 mRootBridge.ResourceAssigned = FALSE;\r
367\r
368 mRootBridge.AllocationAttributes = EFI_PCI_HOST_BRIDGE_COMBINE_MEM_PMEM;\r
369\r
370 mRootBridge.Bus.Base = BusMin;\r
371 mRootBridge.Bus.Limit = BusMax;\r
372 mRootBridge.Io.Base = IoBase;\r
373 mRootBridge.Io.Limit = IoBase + IoSize - 1;\r
374 mRootBridge.Mem.Base = Mmio32Base;\r
375 mRootBridge.Mem.Limit = Mmio32Base + Mmio32Size - 1;\r
9d64ac23
AB
376\r
377 if (sizeof (UINTN) == sizeof (UINT64)) {\r
378 mRootBridge.MemAbove4G.Base = Mmio64Base;\r
379 mRootBridge.MemAbove4G.Limit = Mmio64Base + Mmio64Size - 1;\r
380 if (Mmio64Size > 0) {\r
381 mRootBridge.AllocationAttributes |= EFI_PCI_HOST_BRIDGE_MEM64_DECODE;\r
382 }\r
383 } else {\r
384 //\r
385 // UEFI mandates a 1:1 virtual-to-physical mapping, so on a 32-bit\r
386 // architecture such as ARM, we will not be able to access 64-bit MMIO\r
387 // BARs unless they are allocated below 4 GB. So ignore the range above\r
388 // 4 GB in this case.\r
389 //\r
390 mRootBridge.MemAbove4G.Base = MAX_UINT64;\r
391 mRootBridge.MemAbove4G.Limit = 0;\r
392 }\r
d4cb9a30
AB
393\r
394 //\r
395 // No separate ranges for prefetchable and non-prefetchable BARs\r
396 //\r
397 mRootBridge.PMem.Base = MAX_UINT64;\r
398 mRootBridge.PMem.Limit = 0;\r
399 mRootBridge.PMemAbove4G.Base = MAX_UINT64;\r
400 mRootBridge.PMemAbove4G.Limit = 0;\r
401\r
402 mRootBridge.DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)&mEfiPciRootBridgeDevicePath;\r
403\r
404 return &mRootBridge;\r
405}\r
406\r
407/**\r
408 Free the root bridge instances array returned from\r
409 PciHostBridgeGetRootBridges().\r
410\r
411 @param Bridges The root bridge instances array.\r
412 @param Count The count of the array.\r
413**/\r
414VOID\r
415EFIAPI\r
416PciHostBridgeFreeRootBridges (\r
417 PCI_ROOT_BRIDGE *Bridges,\r
418 UINTN Count\r
419 )\r
420{\r
421 ASSERT (Count == 1);\r
422}\r
423\r
424/**\r
425 Inform the platform that the resource conflict happens.\r
426\r
427 @param HostBridgeHandle Handle of the Host Bridge.\r
428 @param Configuration Pointer to PCI I/O and PCI memory resource\r
429 descriptors. The Configuration contains the resources\r
430 for all the root bridges. The resource for each root\r
431 bridge is terminated with END descriptor and an\r
432 additional END is appended indicating the end of the\r
433 entire resources. The resource descriptor field\r
434 values follow the description in\r
435 EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL\r
436 .SubmitResources().\r
437**/\r
438VOID\r
439EFIAPI\r
440PciHostBridgeResourceConflict (\r
441 EFI_HANDLE HostBridgeHandle,\r
442 VOID *Configuration\r
443 )\r
444{\r
445 EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Descriptor;\r
446 UINTN RootBridgeIndex;\r
447 DEBUG ((EFI_D_ERROR, "PciHostBridge: Resource conflict happens!\n"));\r
448\r
449 RootBridgeIndex = 0;\r
450 Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) Configuration;\r
451 while (Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR) {\r
452 DEBUG ((EFI_D_ERROR, "RootBridge[%d]:\n", RootBridgeIndex++));\r
453 for (; Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR; Descriptor++) {\r
454 ASSERT (Descriptor->ResType <\r
455 (sizeof (mPciHostBridgeLibAcpiAddressSpaceTypeStr) /\r
456 sizeof (mPciHostBridgeLibAcpiAddressSpaceTypeStr[0])\r
457 )\r
458 );\r
459 DEBUG ((EFI_D_ERROR, " %s: Length/Alignment = 0x%lx / 0x%lx\n",\r
460 mPciHostBridgeLibAcpiAddressSpaceTypeStr[Descriptor->ResType],\r
461 Descriptor->AddrLen, Descriptor->AddrRangeMax\r
462 ));\r
463 if (Descriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM) {\r
464 DEBUG ((EFI_D_ERROR, " Granularity/SpecificFlag = %ld / %02x%s\n",\r
465 Descriptor->AddrSpaceGranularity, Descriptor->SpecificFlag,\r
466 ((Descriptor->SpecificFlag &\r
467 EFI_ACPI_MEMORY_RESOURCE_SPECIFIC_FLAG_CACHEABLE_PREFETCHABLE\r
468 ) != 0) ? L" (Prefetchable)" : L""\r
469 ));\r
470 }\r
471 }\r
472 //\r
473 // Skip the END descriptor for root bridge\r
474 //\r
475 ASSERT (Descriptor->Desc == ACPI_END_TAG_DESCRIPTOR);\r
476 Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *)(\r
477 (EFI_ACPI_END_TAG_DESCRIPTOR *)Descriptor + 1\r
478 );\r
479 }\r
480}\r