blob: 74f80d1d2b78bc30c1e2a56eb3e15b688f4b31a2 [file] [log] [blame]
/** @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;
}