/*
 * Copyright (c) 2020, Rebecca Cran <rebecca@bsdio.com>
 * Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.<BR>
 * Copyright (C) 2012, Red Hat, Inc.
 * Copyright (c) 2014, Pluribus Networks, Inc.
 *
 * SPDX-License-Identifier: BSD-2-Clause-Patent
 */
#include "AcpiPlatform.h"

#include <Library/BaseMemoryLib.h>
#include <Library/BhyveFwCtlLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/QemuFwCfgLib.h>             // QemuFwCfgFindFile()

STATIC
EFI_STATUS
EFIAPI
BhyveGetCpuCount (
  OUT UINT32  *CpuCount
  )
{
  FIRMWARE_CONFIG_ITEM  Item;
  UINTN                 Size;

  if (QemuFwCfgIsAvailable ()) {
    if (EFI_ERROR (QemuFwCfgFindFile ("opt/bhyve/hw.ncpu", &Item, &Size))) {
      return EFI_NOT_FOUND;
    } else if (Size != sizeof (*CpuCount)) {
      return EFI_BAD_BUFFER_SIZE;
    }

    QemuFwCfgSelectItem (Item);
    QemuFwCfgReadBytes (Size, CpuCount);

    return EFI_SUCCESS;
  }

  //
  // QemuFwCfg not available, try BhyveFwCtl.
  //
  Size = sizeof (*CpuCount);
  if (BhyveFwCtlGet ("hw.ncpu", CpuCount, &Size) == RETURN_SUCCESS) {
    return EFI_SUCCESS;
  }

  return EFI_UNSUPPORTED;
}

STATIC
EFI_STATUS
EFIAPI
BhyveInstallAcpiMadtTable (
  IN   EFI_ACPI_TABLE_PROTOCOL  *AcpiProtocol,
  IN   VOID                     *AcpiTableBuffer,
  IN   UINTN                    AcpiTableBufferSize,
  OUT  UINTN                    *TableKey
  )
{
  UINT32                                               CpuCount;
  UINTN                                                NewBufferSize;
  EFI_ACPI_1_0_MULTIPLE_APIC_DESCRIPTION_TABLE_HEADER  *Madt;
  EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC_STRUCTURE          *LocalApic;
  EFI_ACPI_1_0_IO_APIC_STRUCTURE                       *IoApic;
  EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE_STRUCTURE     *Iso;
  VOID                                                 *Ptr;
  UINTN                                                Loop;
  EFI_STATUS                                           Status;

  ASSERT (AcpiTableBufferSize >= sizeof (EFI_ACPI_DESCRIPTION_HEADER));

  // Query the host for the number of vCPUs
  Status = BhyveGetCpuCount (&CpuCount);
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "Retrieved CpuCount %d\n", CpuCount));
    ASSERT (CpuCount >= 1);
  } else {
    DEBUG ((DEBUG_INFO, "CpuCount retrieval error\n"));
    CpuCount = 1;
  }

  NewBufferSize = 1                     * sizeof (*Madt) +
                  CpuCount              * sizeof (*LocalApic) +
                  1                     * sizeof (*IoApic) +
                  1                     * sizeof (*Iso);

  Madt = AllocatePool (NewBufferSize);
  if (Madt == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (&(Madt->Header), AcpiTableBuffer, sizeof (EFI_ACPI_DESCRIPTION_HEADER));
  Madt->Header.Length    = (UINT32)NewBufferSize;
  Madt->LocalApicAddress = 0xFEE00000;
  Madt->Flags            = EFI_ACPI_1_0_PCAT_COMPAT;
  Ptr                    = Madt + 1;

  LocalApic = Ptr;
  for (Loop = 0; Loop < CpuCount; ++Loop) {
    LocalApic->Type            = EFI_ACPI_1_0_PROCESSOR_LOCAL_APIC;
    LocalApic->Length          = sizeof (*LocalApic);
    LocalApic->AcpiProcessorId = (UINT8)Loop;
    LocalApic->ApicId          = (UINT8)Loop;
    LocalApic->Flags           = 1; // enabled
    ++LocalApic;
  }

  Ptr = LocalApic;

  IoApic                   = Ptr;
  IoApic->Type             = EFI_ACPI_1_0_IO_APIC;
  IoApic->Length           = sizeof (*IoApic);
  IoApic->IoApicId         = (UINT8)CpuCount;
  IoApic->Reserved         = EFI_ACPI_RESERVED_BYTE;
  IoApic->IoApicAddress    = 0xFEC00000;
  IoApic->SystemVectorBase = 0x00000000;
  Ptr                      = IoApic + 1;

  //
  // IRQ0 (8254 Timer) => IRQ2 (PIC) Interrupt Source Override Structure
  //
  Iso                              = Ptr;
  Iso->Type                        = EFI_ACPI_1_0_INTERRUPT_SOURCE_OVERRIDE;
  Iso->Length                      = sizeof (*Iso);
  Iso->Bus                         = 0x00; // ISA
  Iso->Source                      = 0x00; // IRQ0
  Iso->GlobalSystemInterruptVector = 0x00000002;
  Iso->Flags                       = 0x0000; // Conforms to specs of the bus
  Ptr                              = Iso + 1;

  ASSERT ((UINTN)((UINT8 *)Ptr - (UINT8 *)Madt) == NewBufferSize);
  Status = InstallAcpiTable (AcpiProtocol, Madt, NewBufferSize, TableKey);

  FreePool (Madt);

  return Status;
}

EFI_STATUS
EFIAPI
BhyveInstallAcpiTable (
  IN   EFI_ACPI_TABLE_PROTOCOL  *AcpiProtocol,
  IN   VOID                     *AcpiTableBuffer,
  IN   UINTN                    AcpiTableBufferSize,
  OUT  UINTN                    *TableKey
  )
{
  EFI_ACPI_DESCRIPTION_HEADER        *Hdr;
  EFI_ACPI_TABLE_INSTALL_ACPI_TABLE  TableInstallFunction;

  Hdr = (EFI_ACPI_DESCRIPTION_HEADER *)AcpiTableBuffer;
  switch (Hdr->Signature) {
    case EFI_ACPI_1_0_APIC_SIGNATURE:
      TableInstallFunction = BhyveInstallAcpiMadtTable;
      break;
    default:
      TableInstallFunction = InstallAcpiTable;
  }

  return TableInstallFunction (
           AcpiProtocol,
           AcpiTableBuffer,
           AcpiTableBufferSize,
           TableKey
           );
}
