/** @file
  EFI Driver following Driver Binding Protocol.

  Copyright (c) 2010 - 2020 Intel Corporation. All rights reserved. <BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "SioDriver.h"


//
// This driver is for ACPI(PNP0A03,0)/PCI(0x1f,0)
//
//
//  Sio Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL mSioDriver = {
  SioDriverSupported,
  SioDriverStart,
  SioDriverStop,
  1,
  NULL,
  NULL
};

//
// The list of the created SIO_DEV
//
LIST_ENTRY                  mSioDevPool = INITIALIZE_LIST_HEAD_VARIABLE (mSioDevPool);

//
// Template structure to create SIO_DEV
//
SIO_DEV                     mSioDevTemplate = {
  SIO_DEV_SIGNATURE,        // Signature
  NULL,                     // PciHandle
  {
    0x00000000,             // HID
    0x00000000              // UID
  },
  NULL,                     // Handle
  {                         // Sio Instance
    SioRegisterAccess,
    SioGetResources,
    SioSetResources,
    SioPossibleResources,
    SioModify
  },
  NULL,                     // DevicePath
  {
    NULL,                   // ForwardLink
    NULL,                   // BackLink
  }
};

//
// Template ACPI_HID_DEVICE_PATH structure to create device path
//
ACPI_HID_DEVICE_PATH        mAcpiNodeTemplate = {
  {
    ACPI_DEVICE_PATH,       // Type
    ACPI_DP,                // SubType
    {
      sizeof (ACPI_HID_DEVICE_PATH),  // Length[0]
      0                               // Length[1]
    }
  },
  0x00000000,               // HID
  0x00000000                // UID
};


/**
  The user Entry Point for module Lpc47m17x. 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
SioDriverEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  if (EFI_ERROR (SioInit())) {
    return EFI_UNSUPPORTED;
  } else {

    //
    // Install protocols
    //
    return EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &mSioDriver,
             ImageHandle,
             &mSioComponentName,
             &mSioComponentName2
             );
  }
}


/**
  Compares a PCI to ISA bridge device segment, bus, device and function to the
  PcdSuperIoPciIsaBridgeDevice values.

  @param[in]  CurrentDevice       The device to be compared with the PcdSuperIoPciIsaBridgeDevice information
  @retval     TRUE                This device matches PcdSuperIoPciIsaBridgeDevice values
  @retval     FALSE               This device does not match the PcdSuperIoPciIsaBridgeDevice values
**/
BOOLEAN
EFIAPI
SioDeviceEnabled (
  IN SIO_PCI_ISA_BRIDGE_DEVICE_INFO *CurrentDevice
  )
{
  SIO_PCI_ISA_BRIDGE_DEVICE_INFO *Device;
  Device = (SIO_PCI_ISA_BRIDGE_DEVICE_INFO *) FixedPcdGetPtr (PcdSuperIoPciIsaBridgeDevice);
  if(CompareMem (Device, CurrentDevice, sizeof (SIO_PCI_ISA_BRIDGE_DEVICE_INFO)) == 0) {
    return TRUE;
  }
  return FALSE;
}

/**
  Test to see if this driver supports Controller Handle.

  @param[in]  This                Protocol instance pointer.
  @param[in]  Controller          Handle of device to test
  @param[in]  RemainingDevicePath Optional parameter use to pick a specific child
                                  device to start.

  @retval     EFI_SUCCESS         This driver supports this device
  @retval     EFI_ALREADY_STARTED This driver is already running on this device
  @retval     other               This driver does not support this device
**/
EFI_STATUS
EFIAPI
SioDriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                Status;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath;
  ACPI_HID_DEVICE_PATH      *AcpiNode;
  PCI_TYPE00                Pci;
  UINTN                     Index;
  EFI_SIO_ACPI_DEVICE_ID    *Devices;
  UINTN                     Count;
  UINTN                     SegmentNumber;
  UINTN                     BusNumber;
  UINTN                     DeviceNumber;
  UINTN                     FunctionNumber;
  SIO_PCI_ISA_BRIDGE_DEVICE_INFO  SioDevice;

  //
  // If RemainingDevicePath is not NULL, it should verify that the first device
  // path node in RemainingDevicePath is an ACPI Device path node which is a
  // legal Device Path Node for this bus driver's children.
  //
  if (RemainingDevicePath != NULL) {
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      if ((RemainingDevicePath->Type != ACPI_DEVICE_PATH) ||
          (((RemainingDevicePath->SubType != ACPI_DP) || (DevicePathNodeLength (RemainingDevicePath) != sizeof (ACPI_HID_DEVICE_PATH))) &&
          ((RemainingDevicePath->SubType != ACPI_EXTENDED_DP) || (DevicePathNodeLength (RemainingDevicePath) != sizeof (ACPI_EXTENDED_HID_DEVICE_PATH))))
          ) {
        return EFI_UNSUPPORTED;
      }

      DeviceGetList (&Devices, &Count);
      if (Devices == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }
      AcpiNode = (ACPI_HID_DEVICE_PATH *) RemainingDevicePath;
      for (Index = 0; Index < Count; Index++) {
        if ((AcpiNode->HID == Devices[Index].HID) &&
            (AcpiNode->UID == Devices[Index].UID)) {
          break;
        }
      }
      FreePool (Devices);
      if (Index == Count) {
        return EFI_UNSUPPORTED;
      }
    }
  }

  //
  // See if the parent device path can be opened BY_DRIVER
  //
  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;
  }

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

  //
  // Get PciIo protocol instance
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (!EFI_ERROR (Status)) {
    Status = PciIo->Pci.Read (
                          PciIo,
                          EfiPciIoWidthUint32,
                          0,
                          sizeof (Pci) / sizeof (UINT32),
                          &Pci
                          );
    ASSERT_EFI_ERROR (Status);

    Status = EFI_UNSUPPORTED;
    if ((Pci.Hdr.Command & (EFI_PCI_COMMAND_IO_SPACE | EFI_PCI_COMMAND_MEMORY_SPACE))
                        == (EFI_PCI_COMMAND_IO_SPACE | EFI_PCI_COMMAND_MEMORY_SPACE)
       ) {
      if (Pci.Hdr.ClassCode[2] == PCI_CLASS_BRIDGE) {
        //
        // See if this is a standard PCI to ISA Bridge from the Base Code and Class Code
        //
        if (Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA) {
          Status = EFI_SUCCESS;
        }

        //
        // See if this is an Intel PCI to ISA Bridge in Positive Decode Mode
        //
        if ((Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA_PDECODE) &&
            (Pci.Hdr.VendorId == 0x8086)) {
          //
          // See if this is on Function #0 to avoid false positive on
          // PCI_CLASS_BRIDGE_OTHER that has the same value as
          // PCI_CLASS_BRIDGE_ISA_PDECODE
          //
          Status = PciIo->GetLocation (
                            PciIo,
                            &SegmentNumber,
                            &BusNumber,
                            &DeviceNumber,
                            &FunctionNumber
                            );
          if (!EFI_ERROR (Status) && (FunctionNumber == 0)) {
            Status = EFI_SUCCESS;
          } else {
            Status = EFI_UNSUPPORTED;
          }
        }
        if(!EFI_ERROR (Status)) {
          Status = PciIo->GetLocation (
                            PciIo,
                            &SegmentNumber,
                            &BusNumber,
                            &DeviceNumber,
                            &FunctionNumber
                            );
          if(!EFI_ERROR (Status)) {
            SioDevice.Segment = (UINT8) SegmentNumber;
            SioDevice.Bus = (UINT8) BusNumber;
            SioDevice.Device = (UINT8) DeviceNumber;
            SioDevice.Funtion = (UINT8) FunctionNumber;
            if(!SioDeviceEnabled (&SioDevice)) {
              Status = EFI_UNSUPPORTED;
            }
          }
        }
      }
    }

    gBS->CloseProtocol (
           Controller,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
  }
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  return EFI_SUCCESS;
}


/**
  Destroy the SIO controller handle.

  @param[in]  ChildHandle     The SIO controller handle.

  @retval     EFI_SUCCESS     The SIO controller handle is destroyed successfully.
**/
EFI_STATUS
SioDestroyDevice (
  IN EFI_HANDLE                ChildHandle
  )
{
  EFI_STATUS                Status;
  SIO_DEV                   *SioDev;
  EFI_SIO_PROTOCOL          *Sio;
  EFI_PCI_IO_PROTOCOL       *PciIo;

  Status = gBS->HandleProtocol (
                  ChildHandle,
                  &gEfiSioProtocolGuid,
                  (VOID **) &Sio
                  );
  ASSERT_EFI_ERROR (Status);

  SioDev = SIO_DEV_FROM_THIS (Sio);

  Status = gBS->CloseProtocol (
                  SioDev->PciHandle,
                  &gEfiPciIoProtocolGuid,
                  mSioDriver.DriverBindingHandle,
                  ChildHandle
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->UninstallMultipleProtocolInterfaces (
                  ChildHandle,
                  &gEfiDevicePathProtocolGuid,
                  SioDev->DevicePath,
                  &gEfiSioProtocolGuid,
                  &SioDev->Sio,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    gBS->OpenProtocol (
           SioDev->PciHandle,
           &gEfiPciIoProtocolGuid,
           (VOID **) &PciIo,
           mSioDriver.DriverBindingHandle,
           ChildHandle,
           EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
           );
    return Status;
  }

  RemoveEntryList (&SioDev->Link);
  FreePool (SioDev->DevicePath);
  FreePool (SioDev);
  return EFI_SUCCESS;
}


/**
  Create the SIO controller handle.

  @param[in]  Controller       The parent PCI controller handle.
  @param[in]  Device           Pointer to EFI_SIO_ACPI_DEVICE_ID.
  @param[in]  ParentDevicePath The device path of the parent controller.
  @param[out] PciIo            The PciIo instance of the parent controller.
**/
VOID
SioCreateDevice (
  IN  EFI_HANDLE                Controller,
  IN  EFI_SIO_ACPI_DEVICE_ID    *Device,
  IN  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath,
  OUT EFI_PCI_IO_PROTOCOL       *PciIo
  )
{
  EFI_STATUS                Status;
  SIO_DEV                   *SioDev;

  DeviceEnable (Device);
  SioDev = AllocateCopyPool (sizeof (SIO_DEV), &mSioDevTemplate);
  ASSERT (SioDev != NULL);
  if (SioDev == NULL) {
    return;
  }
  InsertHeadList (&mSioDevPool, &SioDev->Link);

  SioDev->PciHandle       = Controller;

  CopyMem (&SioDev->Device, Device, sizeof (*Device));

  mAcpiNodeTemplate.HID = Device->HID;
  mAcpiNodeTemplate.UID = Device->UID;
  SioDev->DevicePath = AppendDevicePathNode (ParentDevicePath, (EFI_DEVICE_PATH_PROTOCOL *) &mAcpiNodeTemplate);
  ASSERT (SioDev->DevicePath != NULL);

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &SioDev->Handle,
                  &gEfiSioProtocolGuid,        &SioDev->Sio,
                  &gEfiDevicePathProtocolGuid, SioDev->DevicePath,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  mSioDriver.DriverBindingHandle,
                  SioDev->Handle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  ASSERT_EFI_ERROR (Status);
}


/**
  Start this driver on ControllerHandle.

  @param[in]  This                 Protocol instance pointer.
  @param[in]  Controller           Handle of device to bind driver to
  @param[in]  RemainingDevicePath  Optional parameter use to pick a specific child
                                   device to start.

  @retval     EFI_SUCCESS          This driver is added to ControllerHandle
  @retval     EFI_ALREADY_STARTED  This driver is already running on ControllerHandle
  @retval     other                This driver does not support this device
**/
EFI_STATUS
EFIAPI
SioDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                          Status;
  EFI_PCI_IO_PROTOCOL                 *PciIo;
  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL            *ParentDevicePath;
  EFI_SIO_ACPI_DEVICE_ID              *Devices;
  SIO_DEV                             *SioDev;
  UINTN                               Count;
  UINTN                               Index;
  ACPI_HID_DEVICE_PATH                *AcpiNode;
  BOOLEAN                             *HasCreated;
  BOOLEAN                             *RequestCreate;
  LIST_ENTRY                          *Node;

  HasCreated    = NULL;
  RequestCreate = NULL;
  //
  // Get the ISA bridge's 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;
  }

  //
  // Get Pci IO
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    return Status;
  }

  if ((RemainingDevicePath != NULL) && IsDevicePathEnd (RemainingDevicePath)) {
    return EFI_SUCCESS;
  }

  DeviceGetList (&Devices, &Count);
  if (Devices == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }
  HasCreated    = AllocatePool (sizeof (BOOLEAN) * Count);
  ASSERT (HasCreated != NULL);
  if (HasCreated == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }
  RequestCreate = AllocatePool (sizeof (BOOLEAN) * Count);
  ASSERT (RequestCreate != NULL);
  if (RequestCreate == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit_Start;
  }

  //
  // Assume no children has been created.
  // Assume the SIO interface hasn't been initialized.
  //
  ZeroMem (HasCreated, sizeof (BOOLEAN) * Count);

  if (Status == EFI_ALREADY_STARTED) {
    for (Node = GetFirstNode (&mSioDevPool);
         !IsNull (&mSioDevPool, Node);
         Node = GetNextNode (&mSioDevPool, Node)
        ) {
      SioDev = CR (Node, SIO_DEV, Link, SIO_DEV_SIGNATURE);
      Status = gBS->HandleProtocol (
                      SioDev->PciHandle,
                      &gEfiDevicePathProtocolGuid,
                      (VOID **) &DevicePath
                      );
      ASSERT_EFI_ERROR (Status);

      //
      // See if they are under the same PCI to ISA Bridge
      //
      if (CompareMem (DevicePath, ParentDevicePath, GetDevicePathSize (DevicePath)) == 0) {
        for (Index = 0; Index < Count; Index++) {
          if (CompareMem (&SioDev->Device, &Devices[Index], sizeof (EFI_SIO_ACPI_DEVICE_ID)) == 0) {
            HasCreated[Index] = TRUE;
            break;
          }
        }
      }
    }
  }

  AcpiNode = (ACPI_HID_DEVICE_PATH *) RemainingDevicePath;
  for (Index = 0; Index < Count; Index++) {
    if ((AcpiNode == NULL) ||
        ((AcpiNode->HID == Devices[Index].HID) && (AcpiNode->UID == Devices[Index].UID))
       ) {
      RequestCreate[Index] = TRUE;
    } else {
      RequestCreate[Index] = FALSE;
    }
  }

  for (Index = 0; Index < Count; Index++) {
    if (RequestCreate[Index] && !HasCreated[Index]) {
      SioCreateDevice (Controller, &Devices[Index], ParentDevicePath, PciIo);
    }
  }
Exit_Start:
  if (Devices != NULL) {
    FreePool (Devices);
  }
  if (HasCreated != NULL) {
    FreePool (HasCreated);
  }
  if (RequestCreate != NULL) {
    FreePool (RequestCreate);
  }

  return Status;
}


/**
  Stop this driver on ControllerHandle.

  @param[in]  This              Protocol instance pointer.
  @param[in]  Controller        Handle of device to stop driver on
  @param[in]  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                                children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer List of Child Handles to Stop.

  @retval     EFI_SUCCESS       This driver is removed ControllerHandle
  @retval     other             This driver was not removed from this device
**/
EFI_STATUS
EFIAPI
SioDriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     Controller,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )
{
  EFI_STATUS  Status;
  UINTN       Index;
  BOOLEAN     AllChildrenStopped;

  if (NumberOfChildren == 0) {
    gBS->CloseProtocol (
           Controller,
           &gEfiDevicePathProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    gBS->CloseProtocol (
           Controller,
           &gEfiPciIoProtocolGuid,
           This->DriverBindingHandle,
           Controller
           );
    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;
  for (Index = 0; Index < NumberOfChildren; Index++) {
    Status = SioDestroyDevice (ChildHandleBuffer[Index]);
    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (AllChildrenStopped) {
    return EFI_SUCCESS;
  } else {
    return EFI_DEVICE_ERROR;
  }
}
