/** @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;
}