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

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#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;
  }
}
