/** @file
  Serial driver for PCI or SIO UARTS.

Copyright (c) 2006 - 2016, Intel Corporation. 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 "Serial.h"

//
// ISA Serial Driver Global Variables
//

EFI_DRIVER_BINDING_PROTOCOL gSerialControllerDriver = {
  SerialControllerDriverSupported,
  SerialControllerDriverStart,
  SerialControllerDriverStop,
  0xa,
  NULL,
  NULL
};

CONTROLLER_DEVICE_PATH mControllerDevicePathTemplate = {
  {
    HARDWARE_DEVICE_PATH,
    HW_CONTROLLER_DP,
    {
      (UINT8) (sizeof (CONTROLLER_DEVICE_PATH)),
      (UINT8) ((sizeof (CONTROLLER_DEVICE_PATH)) >> 8)
    }
  },
  0
};

SERIAL_DEV  gSerialDevTemplate = {
  SERIAL_DEV_SIGNATURE,
  NULL,
  {
    SERIAL_IO_INTERFACE_REVISION,
    SerialReset,
    SerialSetAttributes,
    SerialSetControl,
    SerialGetControl,
    SerialWrite,
    SerialRead,
    NULL
  },                                       // SerialIo
  {
    SERIAL_PORT_SUPPORT_CONTROL_MASK,
    SERIAL_PORT_DEFAULT_TIMEOUT,
    0,
    16,
    0,
    0,
    0
  },                                       // SerialMode
  NULL,                                    // DevicePath
  NULL,                                    // ParentDevicePath
  {
    {
      MESSAGING_DEVICE_PATH,
      MSG_UART_DP,
      {
        (UINT8) (sizeof (UART_DEVICE_PATH)),
        (UINT8) ((sizeof (UART_DEVICE_PATH)) >> 8)
      }
    },
    0, 0, 0, 0, 0
  },                                       // UartDevicePath
  0,                                       // BaseAddress
  FALSE,                                   // MmioAccess
  1,                                       // RegisterStride
  0,                                       // ClockRate
  16,                                      // ReceiveFifoDepth
  { 0, 0 },                                // Receive;
  16,                                      // TransmitFifoDepth
  { 0, 0 },                                // Transmit;
  FALSE,                                   // SoftwareLoopbackEnable;
  FALSE,                                   // HardwareFlowControl;
  NULL,                                    // *ControllerNameTable;
  FALSE,                                   // ContainsControllerNode;
  0,                                       // Instance;
  NULL                                     // *PciDeviceInfo;
};

/**
  Check the device path node whether it's the Flow Control node or not.

  @param[in] FlowControl    The device path node to be checked.
  
  @retval TRUE              It's the Flow Control node.
  @retval FALSE             It's not.

**/
BOOLEAN
IsUartFlowControlDevicePathNode (
  IN UART_FLOW_CONTROL_DEVICE_PATH *FlowControl
  )
{
  return (BOOLEAN) (
           (DevicePathType (FlowControl) == MESSAGING_DEVICE_PATH) &&
           (DevicePathSubType (FlowControl) == MSG_VENDOR_DP) &&
           (CompareGuid (&FlowControl->Guid, &gEfiUartDevicePathGuid))
           );
}

/**
  The user Entry Point for module PciSioSerial. The user code starts with this function.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.  
  @param[in] SystemTable    A pointer to the EFI System Table.
  
  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
InitializePciSioSerial (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS              Status;

  //
  // Install driver model protocol(s).
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gSerialControllerDriver,
             ImageHandle,
             &gPciSioSerialComponentName,
             &gPciSioSerialComponentName2
             );
  ASSERT_EFI_ERROR (Status);

  //
  // Initialize UART default setting in gSerialDevTempate
  //
  gSerialDevTemplate.SerialMode.BaudRate = PcdGet64 (PcdUartDefaultBaudRate);
  gSerialDevTemplate.SerialMode.DataBits = PcdGet8 (PcdUartDefaultDataBits);
  gSerialDevTemplate.SerialMode.Parity   = PcdGet8 (PcdUartDefaultParity);
  gSerialDevTemplate.SerialMode.StopBits = PcdGet8 (PcdUartDefaultStopBits);
  gSerialDevTemplate.UartDevicePath.BaudRate = PcdGet64 (PcdUartDefaultBaudRate);
  gSerialDevTemplate.UartDevicePath.DataBits = PcdGet8 (PcdUartDefaultDataBits);
  gSerialDevTemplate.UartDevicePath.Parity   = PcdGet8 (PcdUartDefaultParity);
  gSerialDevTemplate.UartDevicePath.StopBits = PcdGet8 (PcdUartDefaultStopBits);
  gSerialDevTemplate.ClockRate = PcdGet32 (PcdSerialClockRate);

  return Status;
}

/**
  Return whether the controller is a SIO serial controller.

  @param  Controller   The controller handle.

  @retval EFI_SUCCESS  The controller is a SIO serial controller.
  @retval others       The controller is not a SIO serial controller.
**/
EFI_STATUS
IsSioSerialController (
  EFI_HANDLE               Controller
  )
{
  EFI_STATUS               Status;
  EFI_SIO_PROTOCOL         *Sio;
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  ACPI_HID_DEVICE_PATH     *Acpi;

  //
  // Open the IO Abstraction(s) needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiSioProtocolGuid,
                  (VOID **) &Sio,
                  gSerialControllerDriver.DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (!EFI_ERROR (Status)) {
    //
    // Close the I/O Abstraction(s) used to perform the supported test
    //
    gBS->CloseProtocol (
           Controller,
           &gEfiSioProtocolGuid,
           gSerialControllerDriver.DriverBindingHandle,
           Controller
           );

    Status = gBS->OpenProtocol (
      Controller,
      &gEfiDevicePathProtocolGuid,
      (VOID **) &DevicePath,
      gSerialControllerDriver.DriverBindingHandle,
      Controller,
      EFI_OPEN_PROTOCOL_BY_DRIVER
      );
    ASSERT (Status != EFI_ALREADY_STARTED);

    if (!EFI_ERROR (Status)) {
      do {
        Acpi = (ACPI_HID_DEVICE_PATH *) DevicePath;
        DevicePath = NextDevicePathNode (DevicePath);
      } while (!IsDevicePathEnd (DevicePath));

      if (DevicePathType (Acpi) != ACPI_DEVICE_PATH ||
          (DevicePathSubType (Acpi) != ACPI_DP && DevicePathSubType (Acpi) != ACPI_EXTENDED_DP) ||
          Acpi->HID != EISA_PNP_ID (0x501)
          ) {
        Status = EFI_UNSUPPORTED;
      }
    }

    //
    // Close protocol, don't use device path protocol in the Support() function
    //
    gBS->CloseProtocol (
      Controller,
      &gEfiDevicePathProtocolGuid,
      gSerialControllerDriver.DriverBindingHandle,
      Controller
      );
  }
  return Status;
}

/**
  Return whether the controller is a PCI serial controller.

  @param  Controller   The controller handle.

  @retval EFI_SUCCESS  The controller is a PCI serial controller.
  @retval others       The controller is not a PCI serial controller.
**/
EFI_STATUS
IsPciSerialController (
  EFI_HANDLE               Controller
  )
{
  EFI_STATUS               Status;
  EFI_PCI_IO_PROTOCOL      *PciIo;
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  PCI_TYPE00               Pci;
  PCI_SERIAL_PARAMETER     *PciSerialParameter;

  //
  // Open the IO Abstraction(s) needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
    Controller,
    &gEfiPciIoProtocolGuid,
    (VOID **) &PciIo,
    gSerialControllerDriver.DriverBindingHandle,
    Controller,
    EFI_OPEN_PROTOCOL_BY_DRIVER
    );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (!EFI_ERROR (Status)) {
    Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0, sizeof (Pci), &Pci);
    if (!EFI_ERROR (Status)) {
      if (!IS_PCI_16550_SERIAL (&Pci)) {
        for (PciSerialParameter = (PCI_SERIAL_PARAMETER *) PcdGetPtr (PcdPciSerialParameters)
             ; PciSerialParameter->VendorId != 0xFFFF
             ; PciSerialParameter++
             ) {
          if ((Pci.Hdr.VendorId == PciSerialParameter->VendorId) &&
              (Pci.Hdr.DeviceId == PciSerialParameter->DeviceId)
              ) {
            break;
          }
        }
        if (PciSerialParameter->VendorId == 0xFFFF) {
          Status = EFI_UNSUPPORTED;
        } else {
          Status = EFI_SUCCESS;
        }
      }
    }

    //
    // Close the I/O Abstraction(s) used to perform the supported test
    //
    gBS->CloseProtocol (
      Controller,
      &gEfiPciIoProtocolGuid,
      gSerialControllerDriver.DriverBindingHandle,
      Controller
      );
  }
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Open the EFI Device Path protocol needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
    Controller,
    &gEfiDevicePathProtocolGuid,
    (VOID **) &DevicePath,
    gSerialControllerDriver.DriverBindingHandle,
    Controller,
    EFI_OPEN_PROTOCOL_BY_DRIVER
    );
  ASSERT (Status != EFI_ALREADY_STARTED);

  //
  // Close protocol, don't use device path protocol in the Support() function
  //
  gBS->CloseProtocol (
    Controller,
    &gEfiDevicePathProtocolGuid,
    gSerialControllerDriver.DriverBindingHandle,
    Controller
    );

  return Status;
}

/**
  Check to see if this driver supports the given controller

  @param  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
  @param  Controller           The handle of the controller to test.
  @param  RemainingDevicePath  A pointer to the remaining portion of a device path.

  @return EFI_SUCCESS          This driver can support the given controller

**/
EFI_STATUS
EFIAPI
SerialControllerDriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )

{
  EFI_STATUS                                Status;
  UART_DEVICE_PATH                          *Uart;
  UART_FLOW_CONTROL_DEVICE_PATH             *FlowControl;

  //
  // Test RemainingDevicePath
  //
  if ((RemainingDevicePath != NULL) && !IsDevicePathEnd (RemainingDevicePath)) {
    Status = EFI_UNSUPPORTED;

    Uart = SkipControllerDevicePathNode (RemainingDevicePath, NULL, NULL);
    if (DevicePathType (Uart) != MESSAGING_DEVICE_PATH ||
        DevicePathSubType (Uart) != MSG_UART_DP ||
        DevicePathNodeLength (Uart) != sizeof (UART_DEVICE_PATH)
        ) {
      return EFI_UNSUPPORTED;
    }

    //
    // Do a rough check because Clock Rate is unknown until DriverBindingStart()
    //
    if (!VerifyUartParameters (0, Uart->BaudRate, Uart->DataBits, Uart->Parity, Uart->StopBits, NULL, NULL)) {
      return EFI_UNSUPPORTED;
    }

    FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (Uart);
    if (IsUartFlowControlDevicePathNode (FlowControl)) {
      //
      // If the second node is Flow Control Node,
      //   return error when it request other than hardware flow control.
      //
      if ((ReadUnaligned32 (&FlowControl->FlowControlMap) & ~UART_FLOW_CONTROL_HARDWARE) != 0) {
        return EFI_UNSUPPORTED;
      }
    }
  }

  Status = IsSioSerialController (Controller);
  if (EFI_ERROR (Status)) {
    Status = IsPciSerialController (Controller);
  }
  return Status;  
}

/**
  Create the child serial device instance.

  @param Controller           The parent controller handle.
  @param Uart                 Pointer to the UART device path node in RemainingDevicePath,
                              or NULL if RemainingDevicePath is NULL.
  @param ParentDevicePath     Pointer to the parent device path.
  @param CreateControllerNode TRUE to create the controller node.
  @param Instance             Instance number of the serial device.
                              The value will be set to the controller node
                              if CreateControllerNode is TRUE.
  @param ParentIo             A union type pointer to either Sio or PciIo.
  @param PciSerialParameter   The PCI serial parameter to be used by current serial device.
                              NULL for SIO serial device.
  @param PciDeviceInfo        The PCI device info for the current serial device.
                              NULL for SIO serial device.

  @retval EFI_SUCCESS         The serial device was created successfully.
  @retval others              The serial device wasn't created.
**/
EFI_STATUS
CreateSerialDevice (
  IN EFI_HANDLE                     Controller,
  IN UART_DEVICE_PATH               *Uart,
  IN EFI_DEVICE_PATH_PROTOCOL       *ParentDevicePath,
  IN BOOLEAN                        CreateControllerNode,
  IN UINT32                         Instance,
  IN PARENT_IO_PROTOCOL_PTR         ParentIo,
  IN PCI_SERIAL_PARAMETER           *PciSerialParameter, OPTIONAL
  IN PCI_DEVICE_INFO                *PciDeviceInfo       OPTIONAL
  )
{
  EFI_STATUS                                 Status;
  SERIAL_DEV                                 *SerialDevice;
  UINT8                                      BarIndex;
  UINT64                                     Offset;
  UART_FLOW_CONTROL_DEVICE_PATH              *FlowControl;
  UINT32                                     FlowControlMap;
  ACPI_RESOURCE_HEADER_PTR                   Resources;
  EFI_ACPI_IO_PORT_DESCRIPTOR                *Io;
  EFI_ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR *FixedIo;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR          *AddressSpace;
  EFI_DEVICE_PATH_PROTOCOL                   *TempDevicePath;

  BarIndex = 0;
  Offset = 0;
  FlowControl = NULL;
  FlowControlMap = 0;

  //
  // Initialize the serial device instance
  //
  SerialDevice = AllocateCopyPool (sizeof (SERIAL_DEV), &gSerialDevTemplate);
  ASSERT (SerialDevice != NULL);

  SerialDevice->SerialIo.Mode    = &(SerialDevice->SerialMode);
  SerialDevice->ParentDevicePath = ParentDevicePath;
  SerialDevice->PciDeviceInfo    = PciDeviceInfo;
  SerialDevice->Instance         = Instance;

  if (Uart != NULL) {
    CopyMem (&SerialDevice->UartDevicePath, Uart, sizeof (UART_DEVICE_PATH));
    FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (Uart);
    if (IsUartFlowControlDevicePathNode (FlowControl)) {
      FlowControlMap = ReadUnaligned32 (&FlowControl->FlowControlMap);
    } else {
      FlowControl = NULL;
    }
  }

  //
  // For PCI serial device, use the information from PCD
  //
  if (PciSerialParameter != NULL) {
    BarIndex = (PciSerialParameter->BarIndex == MAX_UINT8) ? 0 : PciSerialParameter->BarIndex;
    Offset = PciSerialParameter->Offset;
    if (PciSerialParameter->RegisterStride != 0) {
      SerialDevice->RegisterStride = PciSerialParameter->RegisterStride;
    }
    if (PciSerialParameter->ClockRate != 0) {
      SerialDevice->ClockRate = PciSerialParameter->ClockRate;
    }
    if (PciSerialParameter->ReceiveFifoDepth != 0) {
      SerialDevice->ReceiveFifoDepth = PciSerialParameter->ReceiveFifoDepth;
    }
    if (PciSerialParameter->TransmitFifoDepth != 0) {
      SerialDevice->TransmitFifoDepth = PciSerialParameter->TransmitFifoDepth;
    }
  }

  //
  // Pass NULL ActualBaudRate to VerifyUartParameters to disallow baudrate degrade.
  // DriverBindingStart() shouldn't create a handle with different UART device path.
  //
  if (!VerifyUartParameters (SerialDevice->ClockRate, SerialDevice->UartDevicePath.BaudRate, SerialDevice->UartDevicePath.DataBits,
                            SerialDevice->UartDevicePath.Parity, SerialDevice->UartDevicePath.StopBits, NULL, NULL
                            )) {
    Status = EFI_INVALID_PARAMETER;
    goto CreateError;
  }

  if (PciSerialParameter == NULL) {
    Status = ParentIo.Sio->GetResources (ParentIo.Sio, &Resources);
  } else {
    Status = ParentIo.PciIo->GetBarAttributes (ParentIo.PciIo, BarIndex, NULL, (VOID **) &Resources);
  }

  if (!EFI_ERROR (Status)) {
    //
    // Get the base address information from ACPI resource descriptor.
    // ACPI_IO_PORT_DESCRIPTOR and ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR are returned from Sio;
    // ACPI_ADDRESS_SPACE_DESCRIPTOR is returned from PciIo.
    //
    while ((Resources.SmallHeader->Byte != ACPI_END_TAG_DESCRIPTOR) && (SerialDevice->BaseAddress == 0)) {
      switch (Resources.SmallHeader->Byte) {
      case ACPI_IO_PORT_DESCRIPTOR:
        Io = (EFI_ACPI_IO_PORT_DESCRIPTOR *) Resources.SmallHeader;
        if (Io->Length != 0) {
          SerialDevice->BaseAddress = Io->BaseAddressMin;
        }
        break;

      case ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR:
        FixedIo = (EFI_ACPI_FIXED_LOCATION_IO_PORT_DESCRIPTOR *) Resources.SmallHeader;
        if (FixedIo->Length != 0) {
          SerialDevice->BaseAddress = FixedIo->BaseAddress;
        }
        break;

      case ACPI_ADDRESS_SPACE_DESCRIPTOR:
        AddressSpace = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) Resources.SmallHeader;
        if (AddressSpace->AddrLen != 0) {
          if (AddressSpace->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM) {
            SerialDevice->MmioAccess = TRUE;
          }
          SerialDevice->BaseAddress = AddressSpace->AddrRangeMin + Offset;
        }
        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));
      }
    }
  }

  if (SerialDevice->BaseAddress == 0) {
    Status = EFI_INVALID_PARAMETER;
    goto CreateError;
  }

  SerialDevice->HardwareFlowControl = (BOOLEAN) (FlowControlMap == UART_FLOW_CONTROL_HARDWARE);

  //
  // Report status code the serial present
  //
  REPORT_STATUS_CODE_WITH_DEVICE_PATH (
    EFI_PROGRESS_CODE,
    EFI_P_PC_PRESENCE_DETECT | EFI_PERIPHERAL_SERIAL_PORT,
    SerialDevice->ParentDevicePath
    );

  if (!SerialPresent (SerialDevice)) {
    Status = EFI_DEVICE_ERROR;
    REPORT_STATUS_CODE_WITH_DEVICE_PATH (
      EFI_ERROR_CODE,
      EFI_P_EC_NOT_DETECTED | EFI_PERIPHERAL_SERIAL_PORT,
      SerialDevice->ParentDevicePath
      );
    goto CreateError;
  }

  //
  // 1. Append Controller device path node.
  //
  if (CreateControllerNode) {
    mControllerDevicePathTemplate.ControllerNumber = SerialDevice->Instance;
    SerialDevice->DevicePath = AppendDevicePathNode (
                                 SerialDevice->ParentDevicePath,
                                 (EFI_DEVICE_PATH_PROTOCOL *) &mControllerDevicePathTemplate
                                 );
    SerialDevice->ContainsControllerNode = TRUE;
  }

  //
  // 2. Append UART device path node.
  //    The Uart setings are zero here.
  //    SetAttribute() will update them to match the default setings.
  //
  TempDevicePath = SerialDevice->DevicePath;
  if (TempDevicePath != NULL) {
    SerialDevice->DevicePath = AppendDevicePathNode (
                                 TempDevicePath,
                                 (EFI_DEVICE_PATH_PROTOCOL *) &SerialDevice->UartDevicePath
                                 );
    FreePool (TempDevicePath);
  } else {
    SerialDevice->DevicePath = AppendDevicePathNode (
                                 SerialDevice->ParentDevicePath,
                                 (EFI_DEVICE_PATH_PROTOCOL *) &SerialDevice->UartDevicePath
                                 );
  }
  //
  // 3. Append the Flow Control device path node.
  //    Only produce the Flow Control node when remaining device path has it
  //
  if (FlowControl != NULL) {
    TempDevicePath = SerialDevice->DevicePath;
    if (TempDevicePath != NULL) {
      SerialDevice->DevicePath = AppendDevicePathNode (
                                   TempDevicePath,
                                   (EFI_DEVICE_PATH_PROTOCOL *) FlowControl
                                   );
      FreePool (TempDevicePath);
    }
  }
  ASSERT (SerialDevice->DevicePath != NULL);

  //
  // Fill in Serial I/O Mode structure based on either the RemainingDevicePath or defaults.
  //
  SerialDevice->SerialMode.BaudRate = SerialDevice->UartDevicePath.BaudRate;
  SerialDevice->SerialMode.DataBits = SerialDevice->UartDevicePath.DataBits;
  SerialDevice->SerialMode.Parity   = SerialDevice->UartDevicePath.Parity;
  SerialDevice->SerialMode.StopBits = SerialDevice->UartDevicePath.StopBits;

  //
  // Issue a reset to initialize the COM port
  //
  Status = SerialDevice->SerialIo.Reset (&SerialDevice->SerialIo);
  if (EFI_ERROR (Status)) {
    REPORT_STATUS_CODE_WITH_DEVICE_PATH (
      EFI_ERROR_CODE,
      EFI_P_EC_CONTROLLER_ERROR | EFI_PERIPHERAL_SERIAL_PORT,
      SerialDevice->DevicePath
      );
    goto CreateError;
  }

  AddName (SerialDevice, Instance);
  //
  // Install protocol interfaces for the serial device.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &SerialDevice->Handle,
                  &gEfiDevicePathProtocolGuid, SerialDevice->DevicePath,
                  &gEfiSerialIoProtocolGuid, &SerialDevice->SerialIo,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto CreateError;
  }
  //
  // Open For Child Device
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  PciSerialParameter != NULL ? &gEfiPciIoProtocolGuid : &gEfiSioProtocolGuid,
                  (VOID **) &ParentIo,
                  gSerialControllerDriver.DriverBindingHandle,
                  SerialDevice->Handle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );

  if (EFI_ERROR (Status)) {
    gBS->UninstallMultipleProtocolInterfaces (
           &SerialDevice->Handle,
           &gEfiDevicePathProtocolGuid, SerialDevice->DevicePath,
           &gEfiSerialIoProtocolGuid, &SerialDevice->SerialIo,
           NULL
           );
  }

CreateError:
  if (EFI_ERROR (Status)) {
    if (SerialDevice->DevicePath != NULL) {
      FreePool (SerialDevice->DevicePath);
    }
    if (SerialDevice->ControllerNameTable != NULL) {
      FreeUnicodeStringTable (SerialDevice->ControllerNameTable);
    }
    FreePool (SerialDevice);
  }
  return Status;
}

/**
  Returns an array of pointers containing all the child serial device pointers.

  @param Controller      The parent controller handle.
  @param IoProtocolGuid  The protocol GUID, either equals to gEfiSioProtocolGuid
                         or equals to gEfiPciIoProtocolGuid.
  @param Count           Count of the serial devices.

  @return  An array of pointers containing all the child serial device pointers.
**/
SERIAL_DEV **
GetChildSerialDevices (
  IN EFI_HANDLE                       Controller,
  IN EFI_GUID                         *IoProtocolGuid,
  OUT UINTN                           *Count
  )
{
  EFI_STATUS                                 Status;
  UINTN                                      Index;
  EFI_OPEN_PROTOCOL_INFORMATION_ENTRY        *OpenInfoBuffer;
  UINTN                                      EntryCount;
  SERIAL_DEV                                 **SerialDevices;
  EFI_SERIAL_IO_PROTOCOL                     *SerialIo;
  BOOLEAN                                    OpenByDriver;

  *Count = 0;
  //
  // If the SerialIo instance specified by RemainingDevicePath is already created,
  // update the attributes/control.
  //
  Status = gBS->OpenProtocolInformation (
    Controller,
    IoProtocolGuid,
    &OpenInfoBuffer,
    &EntryCount
    );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  SerialDevices = AllocatePool (EntryCount * sizeof (SERIAL_DEV *));
  ASSERT (SerialDevices != NULL);

  *Count = 0;
  OpenByDriver = FALSE;
  for (Index = 0; Index < EntryCount; Index++) {
    if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) != 0) {
      Status = gBS->OpenProtocol (
        OpenInfoBuffer[Index].ControllerHandle,
        &gEfiSerialIoProtocolGuid,
        (VOID **) &SerialIo,
        gSerialControllerDriver.DriverBindingHandle,
        Controller,
        EFI_OPEN_PROTOCOL_GET_PROTOCOL
        );
      if (!EFI_ERROR (Status)) {
        SerialDevices[(*Count)++] = SERIAL_DEV_FROM_THIS (SerialIo);
      }
    }


    if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) != 0) {
      ASSERT (OpenInfoBuffer[Index].AgentHandle == gSerialControllerDriver.DriverBindingHandle);
      OpenByDriver = TRUE;
    }
  }
  if (OpenInfoBuffer != NULL) {
    FreePool (OpenInfoBuffer);
  }

  ASSERT ((*Count == 0) || (OpenByDriver));

  return SerialDevices;
}

/**
  Start to management the controller passed in

  @param  This                 A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
  @param  Controller           The handle of the controller to test.
  @param  RemainingDevicePath  A pointer to the remaining portion of a device path.

  @return EFI_SUCCESS   Driver is started successfully
**/
EFI_STATUS
EFIAPI
SerialControllerDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                                 Status;
  UINTN                                      Index;
  EFI_DEVICE_PATH_PROTOCOL                   *ParentDevicePath;
  EFI_DEVICE_PATH_PROTOCOL                   *Node;
  EFI_SERIAL_IO_PROTOCOL                     *SerialIo;
  UINT32                                     ControllerNumber;
  UART_DEVICE_PATH                           *Uart;
  UART_FLOW_CONTROL_DEVICE_PATH              *FlowControl;
  UINT32                                     Control;
  PARENT_IO_PROTOCOL_PTR                     ParentIo;
  ACPI_HID_DEVICE_PATH                       *Acpi;
  EFI_GUID                                   *IoProtocolGuid;
  PCI_SERIAL_PARAMETER                       *PciSerialParameter;
  PCI_SERIAL_PARAMETER                       DefaultPciSerialParameter;
  PCI_TYPE00                                 Pci;
  UINT32                                     PciSerialCount;
  SERIAL_DEV                                 **SerialDevices;
  UINTN                                      SerialDeviceCount;
  PCI_DEVICE_INFO                            *PciDeviceInfo;
  UINT64                                     Supports;
  BOOLEAN                                    ContainsControllerNode;

  //
  // Get the Parent Device Path
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    return Status;
  }
  //
  // Report status code enable the serial
  //
  REPORT_STATUS_CODE_WITH_DEVICE_PATH (
    EFI_PROGRESS_CODE,
    EFI_P_PC_ENABLE | EFI_PERIPHERAL_SERIAL_PORT,
    ParentDevicePath
    );

  //
  // Grab the IO abstraction we need to get any work done
  //
  IoProtocolGuid = &gEfiSioProtocolGuid;
  Status = gBS->OpenProtocol (
                  Controller,
                  IoProtocolGuid,
                  (VOID **) &ParentIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    IoProtocolGuid = &gEfiPciIoProtocolGuid;
    Status = gBS->OpenProtocol (
                    Controller,
                    IoProtocolGuid,
                    (VOID **) &ParentIo,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_BY_DRIVER
                    );
  }
  ASSERT (!EFI_ERROR (Status) || Status == EFI_ALREADY_STARTED);

  //
  // Do nothing for END device path node
  //
  if ((RemainingDevicePath != NULL) && IsDevicePathEnd (RemainingDevicePath)) {
    return EFI_SUCCESS;
  }

  ControllerNumber = 0;
  ContainsControllerNode = FALSE;
  SerialDevices = GetChildSerialDevices (Controller, IoProtocolGuid, &SerialDeviceCount);

  if (SerialDeviceCount != 0) {
    if (RemainingDevicePath == NULL) {
      //
      // If the SerialIo instance is already created, NULL as RemainingDevicePath is treated
      // as to create the same SerialIo instance.
      //
      return EFI_SUCCESS;
    } else {
      //
      // Update the attributes/control of the SerialIo instance specified by RemainingDevicePath.
      //
      Uart = (UART_DEVICE_PATH *) SkipControllerDevicePathNode (RemainingDevicePath, &ContainsControllerNode, &ControllerNumber);
      for (Index = 0; Index < SerialDeviceCount; Index++) {
        ASSERT ((SerialDevices != NULL) && (SerialDevices[Index] != NULL));
        if ((!SerialDevices[Index]->ContainsControllerNode && !ContainsControllerNode) ||
            (SerialDevices[Index]->ContainsControllerNode && ContainsControllerNode && SerialDevices[Index]->Instance == ControllerNumber)
            ) {
          SerialIo = &SerialDevices[Index]->SerialIo;
          Status = EFI_INVALID_PARAMETER;
          //
          // Pass NULL ActualBaudRate to VerifyUartParameters to disallow baudrate degrade.
          // DriverBindingStart() shouldn't create a handle with different UART device path.
          //
          if (VerifyUartParameters (SerialDevices[Index]->ClockRate, Uart->BaudRate, Uart->DataBits,
                                    (EFI_PARITY_TYPE) Uart->Parity, (EFI_STOP_BITS_TYPE) Uart->StopBits, NULL, NULL)) {
            Status = SerialIo->SetAttributes (
                                 SerialIo,
                                 Uart->BaudRate,
                                 SerialIo->Mode->ReceiveFifoDepth,
                                 SerialIo->Mode->Timeout,
                                 (EFI_PARITY_TYPE) Uart->Parity,
                                 Uart->DataBits,
                                 (EFI_STOP_BITS_TYPE) Uart->StopBits
                                 );
          }
          FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (Uart);
          if (!EFI_ERROR (Status) && IsUartFlowControlDevicePathNode (FlowControl)) {
            Status = SerialIo->GetControl (SerialIo, &Control);
            if (!EFI_ERROR (Status)) {
              if (ReadUnaligned32 (&FlowControl->FlowControlMap) == UART_FLOW_CONTROL_HARDWARE) {
                Control |= EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE;
              } else {
                Control &= ~EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE;
              }
              //
              // Clear the bits that are not allowed to pass to SetControl
              //
              Control &= (EFI_SERIAL_REQUEST_TO_SEND | EFI_SERIAL_DATA_TERMINAL_READY |
                          EFI_SERIAL_HARDWARE_LOOPBACK_ENABLE | EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE |
                          EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE);
              Status = SerialIo->SetControl (SerialIo, Control);
            }
          }
          break;
        }
      }
      if (Index != SerialDeviceCount) {
        //
        // Directly return if the SerialIo instance specified by RemainingDevicePath is found and updated.
        // Otherwise continue to create the instance specified by RemainingDevicePath.
        //
        if (SerialDevices != NULL) {
          FreePool (SerialDevices);
        }
        return Status;
      }
    }
  }

  if (RemainingDevicePath != NULL) {
    Uart = (UART_DEVICE_PATH *) SkipControllerDevicePathNode (RemainingDevicePath, &ContainsControllerNode, &ControllerNumber);
  } else {
    Uart = NULL;
  }

  PciDeviceInfo = NULL;
  if (IoProtocolGuid == &gEfiSioProtocolGuid) {
    Status = EFI_NOT_FOUND;
    if (RemainingDevicePath == NULL || !ContainsControllerNode) {
      Node = ParentDevicePath;
      do {
        Acpi = (ACPI_HID_DEVICE_PATH *) Node;
        Node = NextDevicePathNode (Node);
      } while (!IsDevicePathEnd (Node));
      Status = CreateSerialDevice (Controller, Uart, ParentDevicePath, FALSE, Acpi->UID, ParentIo, NULL, NULL);
      DEBUG ((EFI_D_INFO, "PciSioSerial: Create SIO child serial device - %r\n", Status));
    }
  } else {
    Status = ParentIo.PciIo->Pci.Read (ParentIo.PciIo, EfiPciIoWidthUint8, 0, sizeof (Pci), &Pci);
    if (!EFI_ERROR (Status)) {
      //
      // PcdPciSerialParameters takes the higher priority.
      //
      PciSerialCount = 0;
      for (PciSerialParameter = PcdGetPtr (PcdPciSerialParameters); PciSerialParameter->VendorId != 0xFFFF; PciSerialParameter++) {
        if ((PciSerialParameter->VendorId == Pci.Hdr.VendorId) &&
            (PciSerialParameter->DeviceId == Pci.Hdr.DeviceId)
            ) {
          PciSerialCount++;
        }
      }

      if (SerialDeviceCount == 0) {
        //
        // Enable the IO & MEM decoding when creating the first child.
        // Restore the PCI attributes when all children is destroyed (PciDeviceInfo->ChildCount == 0).
        //
        PciDeviceInfo = AllocatePool (sizeof (PCI_DEVICE_INFO));
        ASSERT (PciDeviceInfo != NULL);
        PciDeviceInfo->ChildCount = 0;
        PciDeviceInfo->PciIo = ParentIo.PciIo;
        Status = ParentIo.PciIo->Attributes (
          ParentIo.PciIo,
          EfiPciIoAttributeOperationGet,
          0,
          &PciDeviceInfo->PciAttributes
          );

        if (!EFI_ERROR (Status)) {
          Status = ParentIo.PciIo->Attributes (
            ParentIo.PciIo,
            EfiPciIoAttributeOperationSupported,
            0,
            &Supports
            );
          if (!EFI_ERROR (Status)) {
            Supports &= (UINT64)(EFI_PCI_IO_ATTRIBUTE_IO | EFI_PCI_IO_ATTRIBUTE_MEMORY);
            Status = ParentIo.PciIo->Attributes (
              ParentIo.PciIo,
              EfiPciIoAttributeOperationEnable,
              Supports,
              NULL
              );
          }
        }
      } else {
        //
        // Re-use the PciDeviceInfo stored in existing children.
        //
        ASSERT ((SerialDevices != NULL) && (SerialDevices[0] != NULL));
        PciDeviceInfo = SerialDevices[0]->PciDeviceInfo;
        ASSERT (PciDeviceInfo != NULL);
      }

      Status = EFI_NOT_FOUND;
      if (PciSerialCount <= 1) {
        //
        // PCI serial device contains only one UART
        //
        if (RemainingDevicePath == NULL || !ContainsControllerNode) {
          //
          // This PCI serial device is matched by class code in Supported()
          //
          if (PciSerialCount == 0) {
            DefaultPciSerialParameter.VendorId = Pci.Hdr.VendorId;
            DefaultPciSerialParameter.DeviceId = Pci.Hdr.DeviceId;
            DefaultPciSerialParameter.BarIndex = 0;
            DefaultPciSerialParameter.Offset = 0;
            DefaultPciSerialParameter.RegisterStride = 0;
            DefaultPciSerialParameter.ClockRate = 0;
            PciSerialParameter = &DefaultPciSerialParameter;
          } else if (PciSerialCount == 1) {
            PciSerialParameter = PcdGetPtr (PcdPciSerialParameters);
          }

          Status = CreateSerialDevice (Controller, Uart, ParentDevicePath, FALSE, 0, ParentIo, PciSerialParameter, PciDeviceInfo);
          DEBUG ((EFI_D_INFO, "PciSioSerial: Create PCI child serial device (single) - %r\n", Status));
          if (!EFI_ERROR (Status)) {
            PciDeviceInfo->ChildCount++;
          }
        }
      } else {
        //
        // PCI serial device contains multiple UARTs
        //
        if (RemainingDevicePath == NULL || ContainsControllerNode) {
          PciSerialCount = 0;
          for (PciSerialParameter = PcdGetPtr (PcdPciSerialParameters); PciSerialParameter->VendorId != 0xFFFF; PciSerialParameter++) {
            if ((PciSerialParameter->VendorId == Pci.Hdr.VendorId) &&
                (PciSerialParameter->DeviceId == Pci.Hdr.DeviceId) &&
                ((RemainingDevicePath == NULL) || (ControllerNumber == PciSerialCount))
                ) {
              //
              // Create controller node when PCI serial device contains multiple UARTs
              //
              Status = CreateSerialDevice (Controller, Uart, ParentDevicePath, TRUE, PciSerialCount, ParentIo, PciSerialParameter, PciDeviceInfo);
              PciSerialCount++;
              DEBUG ((EFI_D_INFO, "PciSioSerial: Create PCI child serial device (multiple) - %r\n", Status));
              if (!EFI_ERROR (Status)) {
                PciDeviceInfo->ChildCount++;
              }
            }
          }
        }
      }
    }
  }

  if (SerialDevices != NULL) {
    FreePool (SerialDevices);
  }

  //
  // For multiple PCI serial devices, set Status to SUCCESS if one child is created successfully
  //
  if ((PciDeviceInfo != NULL) && (PciDeviceInfo->ChildCount != 0)) {
    Status = EFI_SUCCESS;
  }

  if (EFI_ERROR (Status) && (SerialDeviceCount == 0)) {
    if (PciDeviceInfo != NULL) {
      Status = ParentIo.PciIo->Attributes (
        ParentIo.PciIo,
        EfiPciIoAttributeOperationSet,
        PciDeviceInfo->PciAttributes,
        NULL
        );
      ASSERT_EFI_ERROR (Status);
      FreePool (PciDeviceInfo);
    }
    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    gBS->CloseProtocol (
           Controller,
           IoProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
  }

  return Status;
}

/**
  Disconnect this driver with the controller, uninstall related protocol instance

  @param  This                  A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance.
  @param  Controller            The handle of the controller to test.
  @param  NumberOfChildren      Number of child device.
  @param  ChildHandleBuffer     A pointer to the remaining portion of a device path.

  @retval EFI_SUCCESS           Operation successfully
  @retval EFI_DEVICE_ERROR      Cannot stop the driver successfully

**/
EFI_STATUS
EFIAPI
SerialControllerDriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     Controller,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )

{
  EFI_STATUS                          Status;
  UINTN                               Index;
  BOOLEAN                             AllChildrenStopped;
  EFI_SERIAL_IO_PROTOCOL              *SerialIo;
  SERIAL_DEV                          *SerialDevice;
  VOID                                *IoProtocol;
  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  PCI_DEVICE_INFO                     *PciDeviceInfo;

  PciDeviceInfo = NULL;

  Status = gBS->HandleProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &DevicePath
                  );

  //
  // Report the status code disable the serial
  //
  REPORT_STATUS_CODE_WITH_DEVICE_PATH (
    EFI_PROGRESS_CODE,
    EFI_P_PC_DISABLE | EFI_PERIPHERAL_SERIAL_PORT,
    DevicePath
    );

  if (NumberOfChildren == 0) {
    //
    // Close the bus driver
    //
    Status = gBS->OpenProtocol (
                    Controller,
                    &gEfiPciIoProtocolGuid,
                    &IoProtocol,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                    );
    gBS->CloseProtocol (
           Controller,
           !EFI_ERROR (Status) ? &gEfiPciIoProtocolGuid : &gEfiSioProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );

    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {

    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiSerialIoProtocolGuid,
                    (VOID **) &SerialIo,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (!EFI_ERROR (Status)) {

      SerialDevice = SERIAL_DEV_FROM_THIS (SerialIo);
      ASSERT ((PciDeviceInfo == NULL) || (PciDeviceInfo == SerialDevice->PciDeviceInfo));
      PciDeviceInfo = SerialDevice->PciDeviceInfo;

      Status = gBS->CloseProtocol (
                      Controller,
                      PciDeviceInfo != NULL ? &gEfiPciIoProtocolGuid : &gEfiSioProtocolGuid,
                      This->DriverBindingHandle,
                      ChildHandleBuffer[Index]
                      );

      Status = gBS->UninstallMultipleProtocolInterfaces (
                      ChildHandleBuffer[Index],
                      &gEfiDevicePathProtocolGuid, SerialDevice->DevicePath,
                      &gEfiSerialIoProtocolGuid,   &SerialDevice->SerialIo,
                      NULL
                      );
      if (EFI_ERROR (Status)) {
        gBS->OpenProtocol (
               Controller,
               PciDeviceInfo != NULL ? &gEfiPciIoProtocolGuid : &gEfiSioProtocolGuid,
               &IoProtocol,
               This->DriverBindingHandle,
               ChildHandleBuffer[Index],
               EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
               );
      } else {
        FreePool (SerialDevice->DevicePath);
        FreeUnicodeStringTable (SerialDevice->ControllerNameTable);
        FreePool (SerialDevice);

        if (PciDeviceInfo != NULL) {
          ASSERT (PciDeviceInfo->ChildCount != 0);
          PciDeviceInfo->ChildCount--;
        }
      }
    }

    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  } else {
    //
    // If all children are destroyed, restore the PCI attributes.
    //
    if ((PciDeviceInfo != NULL) && (PciDeviceInfo->ChildCount == 0)) {
      ASSERT (PciDeviceInfo->PciIo != NULL);
      Status = PciDeviceInfo->PciIo->Attributes (
        PciDeviceInfo->PciIo,
        EfiPciIoAttributeOperationSet,
        PciDeviceInfo->PciAttributes,
        NULL
        );
      ASSERT_EFI_ERROR (Status);
      FreePool (PciDeviceInfo);
    }
    return EFI_SUCCESS;
  }
}
