/** @file

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2004 - 2016 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains 'Framework Code' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may not be 
  modified, except as allowed by additional terms of your license agreement.

@par Specification Reference:
**/

#include "SpcrAcpi.h"

#define PCANSITYPE                0
#define VT100TYPE                 1
#define VT100PLUSTYPE             2
#define VTUTF8TYPE                3

#define PCI_MAX_BAR_NO            6

UINT8  TerminalTypeValue[4] =
{
  PCANSITYPE,
  VT100TYPE,
  VT100PLUSTYPE,
  VTUTF8TYPE
};

///
/// Guid for messaging path, used in Serial port setting.
///
EFI_GUID  TerminalTypeGuid[4] = {
  DEVICE_PATH_MESSAGING_PC_ANSI,
  DEVICE_PATH_MESSAGING_VT_100,
  DEVICE_PATH_MESSAGING_VT_100_PLUS,
  DEVICE_PATH_MESSAGING_VT_UTF8
};

//
// Serial Port Console Redirection Table
// Please modify all values in Spcr.h only.
//
EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE gSpcrInfo = {
  {
    EFI_ACPI_2_0_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_SIGNATURE,
    sizeof (EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE),
    EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_REVISION,

    //
    // Checksum will be updated at runtime
    //
    0x00,

    //
    // It is expected that these values will be programmed at runtime
    //
    {'I', 'N', 'T', 'E', 'L', ' '},

    SIGNATURE_64 ('S', 'P', 'C', 'R', 'T', 'a', 'b', 'l'),
    0x00,               // Spcr revision,
    SIGNATURE_32 ('I', 'N', 'T', 'L'),
    0x01
  },

  //
  // Beginning of SPCR specific fields
  //
  0x00,               //INTERFACE_TYPE,
  {
    0x00,               //RESERVED_BYTE,
    0x00,               //RESERVED_BYTE,
    0x00               //RESERVED_BYTE,
  },

  //
  // Base Address Block
  //
  {
    EFI_ACPI_3_0_SYSTEM_IO,
    0x08,               //BASE_ADDRESS_BIT_WIDTH,
    0x00,               //BASE_ADDRESS_BIT_OFFSET,
    0x00,               //RESERVED_BYTE,
    0x0000000000000000  //BASE_ADDRESS_ADDRESS,
  },

  0x03,               //INTERRUPT_TYPE,
  0x04,               //IRQ,
  0x04,               //GLOBAL_SYSTEM_INTERRUPT,
  0x07,               //BAUD_RATE,
  0x00,               //PARITY,
  0x01,               //STOP_BITS,
  0x00,               //FLOW_CONTROL,
  0x03,               //TERMINAL_TYPE,
  0x00,               //LANGUAGE,
  0xFFFF,             //PCI_DEVICE_ID,
  0xFFFF,             //PCI_VENDOR_ID,
  0x00,               //PCI_BUS_NUMBER,
  0x00,               //PCI_DEVICE_NUMBER,
  0x00,               //PCI_FUNCTION_NUMBER,
  0x00000000,         //PCI_FLAGS,
  0x00,               //PCI_SEGMENT,
  EFI_ACPI_RESERVED_DWORD
};


/**

  Install Boot Device Selection Protocol

  @param ImageHandle     The image handle.
  @param SystemTable     The system table.

  @retval  EFI_SUCEESS  BDS has finished initializing.
                        Return the dispatcher and recall BDS.Entry
  @retval  Other        Return status from AllocatePool() or gBS->InstallProtocolInterface

**/
EFI_STATUS
EFIAPI
SpcrInitialize (
  IN EFI_HANDLE                            ImageHandle,
  IN EFI_SYSTEM_TABLE                      *SystemTable
  )
{
  EFI_STATUS                      Status;
  EFI_EVENT                       ReadyToBootEvent;
  //
  // Install protocol interface
  //
  Status = EfiCreateEventReadyToBootEx (
            TPL_CALLBACK,
            OutOfBandACPITableConstruction,
            NULL,
            &ReadyToBootEvent
            );
  return Status;
}

/**
  Check whether the device path node is ISA Serial Node.

  @param Acpi           Device path node to be checked

  @retval TRUE          It's ISA Serial Node.
  @retval FALSE         It's NOT ISA Serial Node.

**/
BOOLEAN
HasIsaSerialNode (
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  ACPI_HID_DEVICE_PATH      *Acpi;

  for (Node = DevicePath; !IsDevicePathEnd (Node); Node = NextDevicePathNode (Node)) {
    Acpi = (ACPI_HID_DEVICE_PATH *) Node;
    if ((DevicePathType (Acpi) == ACPI_DEVICE_PATH) && (DevicePathSubType (Acpi) == ACPI_DP) &&
        (ReadUnaligned32 (&Acpi->HID) == EISA_PNP_ID (0x0501))) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
  Get Irq and resource info from isa type console device.

  @param SavedDevicePath           Device path for isa device.

**/
VOID
GetIsaTypeInfo (
  IN EFI_DEVICE_PATH_PROTOCOL *SavedDevicePath
  )
{
  EFI_STATUS               Status;
  EFI_HANDLE               Handle;
  ACPI_RESOURCE_HEADER_PTR Resources;
  EFI_SIO_PROTOCOL         *Sio;
  EFI_ACPI_IRQ_NOFLAG_DESCRIPTOR   *Irq;
  EFI_ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR *FixedLocation;

    //
    // Try to get IRP and BaseAddress info.
    //
    Status = gBS->LocateDevicePath(
                    &gEfiSioProtocolGuid,
                    &SavedDevicePath,
                    &Handle
                    );
    if (EFI_ERROR(Status)) {
      return;
    }

    Status = gBS->HandleProtocol (
                    Handle,
                    &gEfiSioProtocolGuid,
                    (VOID **) &Sio
                    );
    if (EFI_ERROR(Status)) {
      return;
    }

    Status = Sio->GetResources (Sio, &Resources);
    if (EFI_ERROR(Status)) {
      return;
    }

    while (Resources.SmallHeader->Byte != ACPI_END_TAG_DESCRIPTOR) {
      switch (Resources.SmallHeader->Byte) {
        case ACPI_IRQ_DESCRIPTOR:
        case ACPI_IRQ_NOFLAG_DESCRIPTOR:
          Irq = (EFI_ACPI_IRQ_NOFLAG_DESCRIPTOR *) Resources.SmallHeader;
          gSpcrInfo.Irq = 0;
          while (Irq->Mask > 1) {
            gSpcrInfo.Irq ++;
            Irq->Mask = Irq->Mask >> 1;
          }
          break;
        case ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR:
          FixedLocation = (EFI_ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR *)Resources.SmallHeader;
          gSpcrInfo.BaseAddress.Address = FixedLocation->BaseAddress;
          break;
        default:
          break;
      }

      if (Resources.SmallHeader->Bits.Type == 0) {
        Resources.SmallHeader = (ACPI_SMALL_RESOURCE_HEADER *) ((UINT8 *) Resources.SmallHeader
                                + Resources.SmallHeader->Bits.Length
                                + sizeof (*Resources.SmallHeader));
      } else {
        Resources.LargeHeader = (ACPI_LARGE_RESOURCE_HEADER *) ((UINT8 *) Resources.LargeHeader
                                + Resources.LargeHeader->Length
                                + sizeof (*Resources.LargeHeader));
      }
    }
}

/**
  Get Irq and resource info from Pci type console device.

  @param SavedDevicePath           Device path for PCI device.

**/
VOID
GetPciTypeInfo (
  IN EFI_DEVICE_PATH_PROTOCOL *SavedDevicePath
  )
{
  EFI_STATUS               Status;
  EFI_HANDLE               PciHandle;
  EFI_PCI_IO_PROTOCOL      *PciIo;
  UINTN                    Bus;
  UINTN                    Device;
  UINTN                    Function;
  UINTN                    SegNum;
  UINTN                    Irq;
  UINT16                   BaseAddress;
  UINT8                    count;
  UINT64                   Supports;
  UINT16                   VendorId;
  UINT16                   DeviceId;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR   *Resources;

  PciIo     = NULL;
  Resources = NULL;
  Supports  = 0x01;
  Bus       = 0;
  Device    = 0;
  Function  = 0;
  SegNum    = 0;
  Irq       = 0;
  BaseAddress = 0;
  VendorId  = 0;
  DeviceId  = 0;

  Status = gBS->LocateDevicePath (&gEfiPciIoProtocolGuid, &SavedDevicePath, (VOID **) &PciHandle);
  if (EFI_ERROR (Status)) {
    Status = EFI_NOT_FOUND;
    return;
  }

  Status = gBS->HandleProtocol(PciHandle, &gEfiPciIoProtocolGuid, (VOID **) &PciIo);
  if (EFI_ERROR (Status) || (PciIo == NULL)) {
    Status = EFI_NOT_FOUND;
    return;
  }

  PciIo->GetLocation(PciIo, &SegNum, &Bus, &Device, &Function);
  if (EFI_ERROR (Status)) {
    Status = EFI_NOT_FOUND;
    return;
  }

  if (Bus == 0x00 && Device == 0x16 && Function == 3)
  {
    for (count = 0; count < PCI_MAX_BAR_NO; count++) {
      Status  = PciIo->GetBarAttributes (PciIo, count, &Supports, (VOID **) &Resources);
      if (!EFI_ERROR(Status)) {
        if (Resources->ResType == 1) { // TYPE_IO
          BaseAddress= (UINT16)Resources->AddrRangeMin;
          break;
        }
        FreePool (Resources);
        Resources = NULL;
      }
    }
  }

  Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint8, 0x3c, 1, &Irq);
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint16, 0x00, 1, &VendorId);
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  Status = PciIo->Pci.Read(PciIo, EfiPciIoWidthUint16, 0x02, 1, &DeviceId);
  if (EFI_ERROR(Status)) {
    goto Done;
  }

  gSpcrInfo.BaseAddress.Address = BaseAddress;
  gSpcrInfo.Irq = (UINT8) Irq;

  gSpcrInfo.PciDeviceId       = DeviceId;
  gSpcrInfo.PciVendorId       = VendorId;
  gSpcrInfo.PciBusNumber      = (UINT8) Bus;
  gSpcrInfo.PciDeviceNumber   = (UINT8) Device;
  gSpcrInfo.PciFunctionNumber = (UINT8) Function;
  gSpcrInfo.PciSegment        = (UINT8) SegNum;

Done:
  if (Resources != NULL) {
    FreePool (Resources);
  }
}

/**
  Installs the Smbios Table to the System Table. This function gets called
  when the EFI_EVENT_SIGNAL_READY_TO_BOOT gets signaled

  @param  Event                The event to signal
  @param  Context              Event contex

**/
VOID
EFIAPI
OutOfBandACPITableConstruction (
  IN EFI_EVENT        Event,
  IN VOID             *Context
  )
{
  EFI_DEVICE_PATH_PROTOCOL *SavedDevicePath;
  EFI_DEVICE_PATH_PROTOCOL *TmpDevicePath;
  EFI_DEVICE_PATH_PROTOCOL *Tmp2DevicePath;
  EFI_ACPI_TABLE_PROTOCOL  *AcpiTablProtocol;
  UINTN                    TurnKey;
  EFI_STATUS               Status;
  EFI_HANDLE               Handle;
  EFI_SERIAL_IO_PROTOCOL   *SerialIo;
  UINT32                   FlowControl;
  VENDOR_DEVICE_PATH       *Vendor;
  UINT8                    Index;

  Handle          = NULL;
  SavedDevicePath = NULL;

  GetVariable2 (L"OutOfBand", &gOutOfBandGuid, (VOID **) &SavedDevicePath, NULL);
  if (SavedDevicePath == NULL) {
    SavedDevicePath = GetDefaultDeviceForSpcr();
    if (SavedDevicePath == NULL) {
      return;
    }

    gRT->SetVariable (
            L"OutOfBand",
            &gOutOfBandGuid,
            EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
            GetDevicePathSize (SavedDevicePath),
            SavedDevicePath
            );
  }

  //
  // Get TerminalType info from the last device path node.
  //
  TmpDevicePath  = SavedDevicePath;
  Tmp2DevicePath = TmpDevicePath;
  while (!IsDevicePathEnd (TmpDevicePath)) {
    Tmp2DevicePath = TmpDevicePath;
    TmpDevicePath  = NextDevicePathNode(TmpDevicePath);
  }
  Vendor = (VENDOR_DEVICE_PATH *) Tmp2DevicePath;
  for (Index=0; Index < 4; Index ++) {
    if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[Index])) {
      gSpcrInfo.TerminalType = TerminalTypeValue[Index];
      break;
    }
  }
  SetDevicePathEndNode (Tmp2DevicePath);
  TmpDevicePath = SavedDevicePath;
  //
  // Get Console attribute from the serial io protocol.
  //
  Status = gBS->LocateDevicePath(
                  &gEfiSerialIoProtocolGuid,
                  &TmpDevicePath,
                  &Handle
                  );
  if (EFI_ERROR(Status)) {
    return;
  }

  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiSerialIoProtocolGuid,
                  (VOID **) &SerialIo
                  );
  if (EFI_ERROR(Status)) {
    return;
  }

  switch (SerialIo->Mode->BaudRate) {
    case 9600:
      gSpcrInfo.BaudRate = 3;
      break;
    case 19200:
      gSpcrInfo.BaudRate = 4;
      break;
    case 57600:
      gSpcrInfo.BaudRate = 6;
      break;
    case 115200:
    default:
      gSpcrInfo.BaudRate = 7;
      break;
  }

  gSpcrInfo.FlowControl = 0;
  SerialIo->GetControl(SerialIo, &FlowControl);
  if ((FlowControl & EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE) != 0) {
    gSpcrInfo.FlowControl = UART_FLOW_CONTROL_HARDWARE;
  }

  if (HasIsaSerialNode(SavedDevicePath)) {
    GetIsaTypeInfo (SavedDevicePath);
  } else {
    GetPciTypeInfo (SavedDevicePath);
  }
  //
  // Not create before, create new Spcr ACPI table.
  //
  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID**)&AcpiTablProtocol
                  );
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Add table
  //
  Status = AcpiTablProtocol->InstallAcpiTable (
                                  AcpiTablProtocol,
                                  &gSpcrInfo,
                                  sizeof (EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE),
                                  &TurnKey
                                  );
  ASSERT_EFI_ERROR (Status);
}
