/** @file * Device tree enumeration DXE driver for ARM Virtual Machines * * Copyright (c) 2014, Linaro Ltd. All rights reserved.<BR> * * This program and the accompanying materials are * licensed and made available under the terms and conditions of the BSD License * which accompanies this distribution. The full text of the license may be found at * http://opensource.org/licenses/bsd-license.php * * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. * **/ #include <Library/BaseLib.h> #include <Library/DebugLib.h> #include <Library/UefiLib.h> #include <Library/BaseMemoryLib.h> #include <Library/UefiDriverEntryPoint.h> #include <Library/MemoryAllocationLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/VirtioMmioDeviceLib.h> #include <Library/DevicePathLib.h> #include <Library/PcdLib.h> #include <Library/DxeServicesLib.h> #include <Library/HobLib.h> #include <libfdt.h> #include <Library/XenIoMmioLib.h> #include <Guid/Fdt.h> #include <Guid/VirtioMmioTransport.h> #include <Guid/FdtHob.h> #pragma pack (1) typedef struct { VENDOR_DEVICE_PATH Vendor; UINT64 PhysBase; EFI_DEVICE_PATH_PROTOCOL End; } VIRTIO_TRANSPORT_DEVICE_PATH; #pragma pack () typedef enum { PropertyTypeUnknown, PropertyTypeGic, PropertyTypeRtc, PropertyTypeVirtio, PropertyTypeUart, PropertyTypeTimer, PropertyTypePsci, PropertyTypeFwCfg, PropertyTypePciHost, PropertyTypeGicV3, PropertyTypeXen, } PROPERTY_TYPE; typedef struct { PROPERTY_TYPE Type; CHAR8 Compatible[32]; } PROPERTY; STATIC CONST PROPERTY CompatibleProperties[] = { { PropertyTypeGic, "arm,cortex-a15-gic" }, { PropertyTypeRtc, "arm,pl031" }, { PropertyTypeVirtio, "virtio,mmio" }, { PropertyTypeUart, "arm,pl011" }, { PropertyTypeTimer, "arm,armv7-timer" }, { PropertyTypeTimer, "arm,armv8-timer" }, { PropertyTypePsci, "arm,psci-0.2" }, { PropertyTypeFwCfg, "qemu,fw-cfg-mmio" }, { PropertyTypePciHost, "pci-host-ecam-generic" }, { PropertyTypeGicV3, "arm,gic-v3" }, { PropertyTypeXen, "xen,xen" }, { PropertyTypeUnknown, "" } }; typedef struct { UINT32 Type; UINT32 Number; UINT32 Flags; } INTERRUPT_PROPERTY; STATIC PROPERTY_TYPE GetTypeFromNode ( IN CONST CHAR8 *NodeType, IN UINTN Size ) { CONST CHAR8 *Compatible; CONST PROPERTY *CompatibleProperty; // // A 'compatible' node may contain a sequence of NULL terminated // compatible strings so check each one // for (Compatible = NodeType; Compatible < NodeType + Size && *Compatible; Compatible += 1 + AsciiStrLen (Compatible)) { for (CompatibleProperty = CompatibleProperties; CompatibleProperty->Compatible[0]; CompatibleProperty++) { if (AsciiStrCmp (CompatibleProperty->Compatible, Compatible) == 0) { return CompatibleProperty->Type; } } } return PropertyTypeUnknown; } // // We expect the "ranges" property of "pci-host-ecam-generic" to consist of // records like this. // #pragma pack (1) typedef struct { UINT32 Type; UINT64 ChildBase; UINT64 CpuBase; UINT64 Size; } DTB_PCI_HOST_RANGE_RECORD; #pragma pack () #define DTB_PCI_HOST_RANGE_RELOCATABLE BIT31 #define DTB_PCI_HOST_RANGE_PREFETCHABLE BIT30 #define DTB_PCI_HOST_RANGE_ALIASED BIT29 #define DTB_PCI_HOST_RANGE_MMIO32 BIT25 #define DTB_PCI_HOST_RANGE_MMIO64 (BIT25 | BIT24) #define DTB_PCI_HOST_RANGE_IO BIT24 #define DTB_PCI_HOST_RANGE_TYPEMASK (BIT31 | BIT30 | BIT29 | BIT25 | BIT24) /** Process the device tree node describing the generic PCI host controller. param[in] DeviceTreeBase Pointer to the device tree. param[in] Node Offset of the device tree node whose "compatible" property is "pci-host-ecam-generic". param[in] RegProp Pointer to the "reg" property of Node. The caller is responsible for ensuring that the size of the property is 4 UINT32 cells. @retval EFI_SUCCESS Parsing successful, properties parsed from Node have been stored in dynamic PCDs. @retval EFI_PROTOCOL_ERROR Parsing failed. PCDs are left unchanged. **/ STATIC EFI_STATUS EFIAPI ProcessPciHost ( IN CONST VOID *DeviceTreeBase, IN INT32 Node, IN CONST VOID *RegProp ) { UINT64 ConfigBase, ConfigSize; CONST VOID *Prop; INT32 Len; UINT32 BusMin, BusMax; UINT32 RecordIdx; UINT64 IoBase, IoSize, IoTranslation; UINT64 MmioBase, MmioSize, MmioTranslation; // // Fetch the ECAM window. // ConfigBase = fdt64_to_cpu (((CONST UINT64 *)RegProp)[0]); ConfigSize = fdt64_to_cpu (((CONST UINT64 *)RegProp)[1]); // // Fetch the bus range (note: inclusive). // Prop = fdt_getprop (DeviceTreeBase, Node, "bus-range", &Len); if (Prop == NULL || Len != 2 * sizeof(UINT32)) { DEBUG ((EFI_D_ERROR, "%a: 'bus-range' not found or invalid\n", __FUNCTION__)); return EFI_PROTOCOL_ERROR; } BusMin = fdt32_to_cpu (((CONST UINT32 *)Prop)[0]); BusMax = fdt32_to_cpu (((CONST UINT32 *)Prop)[1]); // // Sanity check: the config space must accommodate all 4K register bytes of // all 8 functions of all 32 devices of all buses. // if (BusMax < BusMin || BusMax - BusMin == MAX_UINT32 || DivU64x32 (ConfigSize, SIZE_4KB * 8 * 32) < BusMax - BusMin + 1) { DEBUG ((EFI_D_ERROR, "%a: invalid 'bus-range' and/or 'reg'\n", __FUNCTION__)); return EFI_PROTOCOL_ERROR; } // // Iterate over "ranges". // Prop = fdt_getprop (DeviceTreeBase, Node, "ranges", &Len); if (Prop == NULL || Len == 0 || Len % sizeof (DTB_PCI_HOST_RANGE_RECORD) != 0) { DEBUG ((EFI_D_ERROR, "%a: 'ranges' not found or invalid\n", __FUNCTION__)); return EFI_PROTOCOL_ERROR; } // // IoBase, IoTranslation, MmioBase and MmioTranslation are initialized only // in order to suppress '-Werror=maybe-uninitialized' warnings *incorrectly* // emitted by some gcc versions. // IoBase = 0; IoTranslation = 0; MmioBase = 0; MmioTranslation = 0; // // IoSize and MmioSize are initialized to zero because the logic below // requires it. // IoSize = 0; MmioSize = 0; for (RecordIdx = 0; RecordIdx < Len / sizeof (DTB_PCI_HOST_RANGE_RECORD); ++RecordIdx) { CONST DTB_PCI_HOST_RANGE_RECORD *Record; Record = (CONST DTB_PCI_HOST_RANGE_RECORD *)Prop + RecordIdx; switch (fdt32_to_cpu (Record->Type) & DTB_PCI_HOST_RANGE_TYPEMASK) { case DTB_PCI_HOST_RANGE_IO: IoBase = fdt64_to_cpu (Record->ChildBase); IoSize = fdt64_to_cpu (Record->Size); IoTranslation = fdt64_to_cpu (Record->CpuBase) - IoBase; break; case DTB_PCI_HOST_RANGE_MMIO32: MmioBase = fdt64_to_cpu (Record->ChildBase); MmioSize = fdt64_to_cpu (Record->Size); MmioTranslation = fdt64_to_cpu (Record->CpuBase) - MmioBase; if (MmioBase > MAX_UINT32 || MmioSize > MAX_UINT32 || MmioBase + MmioSize > SIZE_4GB) { DEBUG ((EFI_D_ERROR, "%a: MMIO32 space invalid\n", __FUNCTION__)); return EFI_PROTOCOL_ERROR; } if (MmioTranslation != 0) { DEBUG ((EFI_D_ERROR, "%a: unsupported nonzero MMIO32 translation " "0x%Lx\n", __FUNCTION__, MmioTranslation)); return EFI_UNSUPPORTED; } break; } } if (IoSize == 0 || MmioSize == 0) { DEBUG ((EFI_D_ERROR, "%a: %a space empty\n", __FUNCTION__, (IoSize == 0) ? "IO" : "MMIO32")); return EFI_PROTOCOL_ERROR; } PcdSet64 (PcdPciExpressBaseAddress, ConfigBase); PcdSet32 (PcdPciBusMin, BusMin); PcdSet32 (PcdPciBusMax, BusMax); PcdSet64 (PcdPciIoBase, IoBase); PcdSet64 (PcdPciIoSize, IoSize); PcdSet64 (PcdPciIoTranslation, IoTranslation); PcdSet32 (PcdPciMmio32Base, (UINT32)MmioBase); PcdSet32 (PcdPciMmio32Size, (UINT32)MmioSize); PcdSetBool (PcdPciDisableBusEnumeration, FALSE); DEBUG ((EFI_D_INFO, "%a: Config[0x%Lx+0x%Lx) Bus[0x%x..0x%x] " "Io[0x%Lx+0x%Lx)@0x%Lx Mem[0x%Lx+0x%Lx)@0x%Lx\n", __FUNCTION__, ConfigBase, ConfigSize, BusMin, BusMax, IoBase, IoSize, IoTranslation, MmioBase, MmioSize, MmioTranslation)); return EFI_SUCCESS; } EFI_STATUS EFIAPI InitializeVirtFdtDxe ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { VOID *Hob; VOID *DeviceTreeBase; INT32 Node, Prev; INT32 RtcNode; EFI_STATUS Status; CONST CHAR8 *Type; INT32 Len; PROPERTY_TYPE PropType; CONST VOID *RegProp; VIRTIO_TRANSPORT_DEVICE_PATH *DevicePath; EFI_HANDLE Handle; UINT64 RegBase; UINT64 DistBase, CpuBase, RedistBase; CONST INTERRUPT_PROPERTY *InterruptProp; INT32 SecIntrNum, IntrNum, VirtIntrNum, HypIntrNum; CONST CHAR8 *PsciMethod; UINT64 FwCfgSelectorAddress; UINT64 FwCfgSelectorSize; UINT64 FwCfgDataAddress; UINT64 FwCfgDataSize; UINT64 FwCfgDmaAddress; UINT64 FwCfgDmaSize; Hob = GetFirstGuidHob(&gFdtHobGuid); if (Hob == NULL || GET_GUID_HOB_DATA_SIZE (Hob) != sizeof (UINT64)) { return EFI_NOT_FOUND; } DeviceTreeBase = (VOID *)(UINTN)*(UINT64 *)GET_GUID_HOB_DATA (Hob); if (fdt_check_header (DeviceTreeBase) != 0) { DEBUG ((EFI_D_ERROR, "%a: No DTB found @ 0x%p\n", __FUNCTION__, DeviceTreeBase)); return EFI_NOT_FOUND; } Status = gBS->InstallConfigurationTable (&gFdtTableGuid, DeviceTreeBase); ASSERT_EFI_ERROR (Status); DEBUG ((EFI_D_INFO, "%a: DTB @ 0x%p\n", __FUNCTION__, DeviceTreeBase)); RtcNode = -1; // // Now enumerate the nodes and install peripherals that we are interested in, // i.e., GIC, RTC and virtio MMIO nodes // for (Prev = 0;; Prev = Node) { Node = fdt_next_node (DeviceTreeBase, Prev, NULL); if (Node < 0) { break; } Type = fdt_getprop (DeviceTreeBase, Node, "compatible", &Len); if (Type == NULL) { continue; } PropType = GetTypeFromNode (Type, Len); if (PropType == PropertyTypeUnknown) { continue; } // // Get the 'reg' property of this node. For now, we will assume // 8 byte quantities for base and size, respectively. // TODO use #cells root properties instead // RegProp = fdt_getprop (DeviceTreeBase, Node, "reg", &Len); ASSERT ((RegProp != NULL) || (PropType == PropertyTypeTimer) || (PropType == PropertyTypePsci)); switch (PropType) { case PropertyTypePciHost: ASSERT (Len == 2 * sizeof (UINT64)); Status = ProcessPciHost (DeviceTreeBase, Node, RegProp); ASSERT_EFI_ERROR (Status); break; case PropertyTypeFwCfg: ASSERT (Len == 2 * sizeof (UINT64)); FwCfgDataAddress = fdt64_to_cpu (((UINT64 *)RegProp)[0]); FwCfgDataSize = 8; FwCfgSelectorAddress = FwCfgDataAddress + FwCfgDataSize; FwCfgSelectorSize = 2; // // The following ASSERT()s express // // Address + Size - 1 <= MAX_UINTN // // for both registers, that is, that the last byte in each MMIO range is // expressible as a MAX_UINTN. The form below is mathematically // equivalent, and it also prevents any unsigned overflow before the // comparison. // ASSERT (FwCfgSelectorAddress <= MAX_UINTN - FwCfgSelectorSize + 1); ASSERT (FwCfgDataAddress <= MAX_UINTN - FwCfgDataSize + 1); PcdSet64 (PcdFwCfgSelectorAddress, FwCfgSelectorAddress); PcdSet64 (PcdFwCfgDataAddress, FwCfgDataAddress); DEBUG ((EFI_D_INFO, "Found FwCfg @ 0x%Lx/0x%Lx\n", FwCfgSelectorAddress, FwCfgDataAddress)); if (fdt64_to_cpu (((UINT64 *)RegProp)[1]) >= 0x18) { FwCfgDmaAddress = FwCfgDataAddress + 0x10; FwCfgDmaSize = 0x08; // // See explanation above. // ASSERT (FwCfgDmaAddress <= MAX_UINTN - FwCfgDmaSize + 1); PcdSet64 (PcdFwCfgDmaAddress, FwCfgDmaAddress); DEBUG ((EFI_D_INFO, "Found FwCfg DMA @ 0x%Lx\n", FwCfgDmaAddress)); } break; case PropertyTypeVirtio: ASSERT (Len == 16); // // Create a unique device path for this transport on the fly // RegBase = fdt64_to_cpu (((UINT64 *)RegProp)[0]); DevicePath = (VIRTIO_TRANSPORT_DEVICE_PATH *)CreateDeviceNode ( HARDWARE_DEVICE_PATH, HW_VENDOR_DP, sizeof (VIRTIO_TRANSPORT_DEVICE_PATH)); if (DevicePath == NULL) { DEBUG ((EFI_D_ERROR, "%a: Out of memory\n", __FUNCTION__)); break; } CopyMem (&DevicePath->Vendor.Guid, &gVirtioMmioTransportGuid, sizeof (EFI_GUID)); DevicePath->PhysBase = RegBase; SetDevicePathNodeLength (&DevicePath->Vendor, sizeof (*DevicePath) - sizeof (DevicePath->End)); SetDevicePathEndNode (&DevicePath->End); Handle = NULL; Status = gBS->InstallProtocolInterface (&Handle, &gEfiDevicePathProtocolGuid, EFI_NATIVE_INTERFACE, DevicePath); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "%a: Failed to install the EFI_DEVICE_PATH " "protocol on a new handle (Status == %r)\n", __FUNCTION__, Status)); FreePool (DevicePath); break; } Status = VirtioMmioInstallDevice (RegBase, Handle); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "%a: Failed to install VirtIO transport @ 0x%Lx " "on handle %p (Status == %r)\n", __FUNCTION__, RegBase, Handle, Status)); Status = gBS->UninstallProtocolInterface (Handle, &gEfiDevicePathProtocolGuid, DevicePath); ASSERT_EFI_ERROR (Status); FreePool (DevicePath); } break; case PropertyTypeGic: ASSERT (Len == 32); DistBase = fdt64_to_cpu (((UINT64 *)RegProp)[0]); CpuBase = fdt64_to_cpu (((UINT64 *)RegProp)[2]); ASSERT (DistBase < MAX_UINT32); ASSERT (CpuBase < MAX_UINT32); PcdSet32 (PcdGicDistributorBase, (UINT32)DistBase); PcdSet32 (PcdGicInterruptInterfaceBase, (UINT32)CpuBase); PcdSet32 (PcdArmGicRevision, 2); DEBUG ((EFI_D_INFO, "Found GIC @ 0x%Lx/0x%Lx\n", DistBase, CpuBase)); break; case PropertyTypeGicV3: // // The GIC v3 DT binding describes a series of at least 3 physical (base // addresses, size) pairs: the distributor interface (GICD), at least one // redistributor region (GICR) containing dedicated redistributor // interfaces for all individual CPUs, and the CPU interface (GICC). // Under virtualization, we assume that the first redistributor region // listed covers the boot CPU. Also, our GICv3 driver only supports the // system register CPU interface, so we can safely ignore the MMIO version // which is listed after the sequence of redistributor interfaces. // This means we are only interested in the first two memory regions // supplied, and ignore everything else. // ASSERT (Len >= 32); // RegProp[0..1] == { GICD base, GICD size } DistBase = fdt64_to_cpu (((UINT64 *)RegProp)[0]); ASSERT (DistBase < MAX_UINT32); // RegProp[2..3] == { GICR base, GICR size } RedistBase = fdt64_to_cpu (((UINT64 *)RegProp)[2]); ASSERT (RedistBase < MAX_UINT32); PcdSet32 (PcdGicDistributorBase, (UINT32)DistBase); PcdSet32 (PcdGicRedistributorsBase, (UINT32)RedistBase); PcdSet32 (PcdArmGicRevision, 3); DEBUG ((EFI_D_INFO, "Found GIC v3 (re)distributor @ 0x%Lx (0x%Lx)\n", DistBase, RedistBase)); break; case PropertyTypeRtc: ASSERT (Len == 16); RegBase = fdt64_to_cpu (((UINT64 *)RegProp)[0]); ASSERT (RegBase < MAX_UINT32); PcdSet32 (PcdPL031RtcBase, (UINT32)RegBase); DEBUG ((EFI_D_INFO, "Found PL031 RTC @ 0x%Lx\n", RegBase)); RtcNode = Node; break; case PropertyTypeTimer: // // - interrupts : Interrupt list for secure, non-secure, virtual and // hypervisor timers, in that order. // InterruptProp = fdt_getprop (DeviceTreeBase, Node, "interrupts", &Len); ASSERT (Len == 36 || Len == 48); SecIntrNum = fdt32_to_cpu (InterruptProp[0].Number) + (InterruptProp[0].Type ? 16 : 0); IntrNum = fdt32_to_cpu (InterruptProp[1].Number) + (InterruptProp[1].Type ? 16 : 0); VirtIntrNum = fdt32_to_cpu (InterruptProp[2].Number) + (InterruptProp[2].Type ? 16 : 0); HypIntrNum = Len < 48 ? 0 : fdt32_to_cpu (InterruptProp[3].Number) + (InterruptProp[3].Type ? 16 : 0); DEBUG ((EFI_D_INFO, "Found Timer interrupts %d, %d, %d, %d\n", SecIntrNum, IntrNum, VirtIntrNum, HypIntrNum)); PcdSet32 (PcdArmArchTimerSecIntrNum, SecIntrNum); PcdSet32 (PcdArmArchTimerIntrNum, IntrNum); PcdSet32 (PcdArmArchTimerVirtIntrNum, VirtIntrNum); PcdSet32 (PcdArmArchTimerHypIntrNum, HypIntrNum); break; case PropertyTypePsci: PsciMethod = fdt_getprop (DeviceTreeBase, Node, "method", &Len); if (PsciMethod && AsciiStrnCmp (PsciMethod, "hvc", 3) == 0) { PcdSet32 (PcdArmPsciMethod, 1); } else if (PsciMethod && AsciiStrnCmp (PsciMethod, "smc", 3) == 0) { PcdSet32 (PcdArmPsciMethod, 2); } else { DEBUG ((EFI_D_ERROR, "%a: Unknown PSCI method \"%a\"\n", __FUNCTION__, PsciMethod)); } break; case PropertyTypeXen: ASSERT (Len == 16); // // Retrieve the reg base from this node and wire it up to the // MMIO flavor of the XenBus root device I/O protocol // RegBase = fdt64_to_cpu (((UINT64 *)RegProp)[0]); Handle = NULL; Status = XenIoMmioInstall (&Handle, RegBase); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "%a: XenIoMmioInstall () failed on a new handle " "(Status == %r)\n", __FUNCTION__, Status)); break; } DEBUG ((EFI_D_INFO, "Found Xen node with Grant table @ 0x%Lx\n", RegBase)); break; default: break; } } // // UEFI takes ownership of the RTC hardware, and exposes its functionality // through the UEFI Runtime Services GetTime, SetTime, etc. This means we // need to disable it in the device tree to prevent the OS from attaching its // device driver as well. // if ((RtcNode != -1) && fdt_setprop_string (DeviceTreeBase, RtcNode, "status", "disabled") != 0) { DEBUG ((EFI_D_WARN, "Failed to set PL031 status to 'disabled'\n")); } return EFI_SUCCESS; }