/** @file
  UDF/ECMA-167 file system driver.

  Copyright (C) 2014-2017 Paulo Alcantara <pcacjr@zytor.com>

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

#include "Udf.h"

//
// UDF filesystem driver's Global Variables.
//
EFI_DRIVER_BINDING_PROTOCOL gUdfDriverBinding = {
  UdfDriverBindingSupported,
  UdfDriverBindingStart,
  UdfDriverBindingStop,
  0x10,
  NULL,
  NULL
};

EFI_SIMPLE_FILE_SYSTEM_PROTOCOL gUdfSimpleFsTemplate = {
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
  UdfOpenVolume
};

/**
  Test to see if this driver supports ControllerHandle. Any ControllerHandle
  than contains a BlockIo and DiskIo protocol or a BlockIo2 protocol can be
  supported.

  @param[in]  This                Protocol instance pointer.
  @param[in]  ControllerHandle    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
UdfDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS            Status;
  EFI_DISK_IO_PROTOCOL  *DiskIo;

  //
  // Open DiskIo protocol on ControllerHandle
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiDiskIoProtocolGuid,
    (VOID **)&DiskIo,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_BY_DRIVER
    );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close DiskIo protocol on ControllerHandle
  //
  gBS->CloseProtocol (
    ControllerHandle,
    &gEfiDiskIoProtocolGuid,
    This->DriverBindingHandle,
    ControllerHandle
    );

  //
  // Test whether ControllerHandle supports BlockIo protocol
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiBlockIoProtocolGuid,
    NULL,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_TEST_PROTOCOL
    );

  return Status;
}

/**
  Start this driver on ControllerHandle by opening a Block IO or a Block IO2
  or both, and Disk IO protocol, reading Device Path, and creating a child
  handle with a Disk IO and device path protocol.

  @param[in]  This                 Protocol instance pointer.
  @param[in]  ControllerHandle     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
UdfDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_TPL                     OldTpl;
  EFI_STATUS                  Status;
  EFI_BLOCK_IO_PROTOCOL       *BlockIo;
  EFI_DISK_IO_PROTOCOL        *DiskIo;
  PRIVATE_UDF_SIMPLE_FS_DATA  *PrivFsData;

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  //
  // Open BlockIo protocol on ControllerHandle
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiBlockIoProtocolGuid,
    (VOID **)&BlockIo,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
    );
  ASSERT_EFI_ERROR (Status);

  //
  // Open DiskIo protocol on ControllerHandle
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiDiskIoProtocolGuid,
    (VOID **)&DiskIo,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_BY_DRIVER
    );
  ASSERT_EFI_ERROR (Status);

  //
  // Check if ControllerHandle supports an UDF file system
  //
  Status = SupportUdfFileSystem (This, ControllerHandle);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  //
  // Initialize private file system structure
  //
  PrivFsData =
    (PRIVATE_UDF_SIMPLE_FS_DATA *)
    AllocateZeroPool (sizeof (PRIVATE_UDF_SIMPLE_FS_DATA));
  if (PrivFsData == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  //
  // Create new child handle
  //
  PrivFsData->Signature = PRIVATE_UDF_SIMPLE_FS_DATA_SIGNATURE;
  PrivFsData->BlockIo   = BlockIo;
  PrivFsData->DiskIo    = DiskIo;
  PrivFsData->Handle    = ControllerHandle;

  //
  // Set up SimpleFs protocol
  //
  CopyMem ((VOID *)&PrivFsData->SimpleFs, (VOID *)&gUdfSimpleFsTemplate,
           sizeof (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL));

  //
  // Install child handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
    &PrivFsData->Handle,
    &gEfiSimpleFileSystemProtocolGuid,
    &PrivFsData->SimpleFs,
    NULL
    );

Exit:
  if (EFI_ERROR (Status)) {
    //
    // Close DiskIo protocol on ControllerHandle
    //
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiDiskIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
    //
    // Close BlockIo protocol on ControllerHandle
    //
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiBlockIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
  }

  gBS->RestoreTPL (OldTpl);

  return Status;
}

/**
  Stop this driver on ControllerHandle. Support stopping any child handles
  created by this driver.

  @param  This              Protocol instance pointer.
  @param  ControllerHandle  Handle of device to stop driver on
  @param  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                            children is zero stop the entire bus driver.
  @param  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
UdfDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL   *This,
  IN  EFI_HANDLE                    ControllerHandle,
  IN  UINTN                         NumberOfChildren,
  IN  EFI_HANDLE                    *ChildHandleBuffer
  )
{
  PRIVATE_UDF_SIMPLE_FS_DATA        *PrivFsData;
  EFI_STATUS                        Status;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL   *SimpleFs;

  //
  // Open SimpleFs protocol on ControllerHandle
  //
  Status = gBS->OpenProtocol (
    ControllerHandle,
    &gEfiSimpleFileSystemProtocolGuid,
    (VOID **)&SimpleFs,
    This->DriverBindingHandle,
    ControllerHandle,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
    );
  if (!EFI_ERROR (Status)) {
    PrivFsData = PRIVATE_UDF_SIMPLE_FS_DATA_FROM_THIS (SimpleFs);

    //
    // Uninstall child handle
    //
    Status = gBS->UninstallMultipleProtocolInterfaces (
      PrivFsData->Handle,
      &gEfiSimpleFileSystemProtocolGuid,
      &PrivFsData->SimpleFs,
      NULL
      );

    FreePool ((VOID *)PrivFsData);
  }

  if (!EFI_ERROR (Status)) {
    //
    // Close DiskIo protocol on ControllerHandle
    //
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiDiskIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
    //
    // Close BlockIo protocol on ControllerHandle
    //
    gBS->CloseProtocol (
      ControllerHandle,
      &gEfiBlockIoProtocolGuid,
      This->DriverBindingHandle,
      ControllerHandle
      );
  }

  return Status;
}

/**
  The user Entry Point for UDF file system driver. 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
InitializeUdf (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS              Status;

  Status = EfiLibInstallDriverBindingComponentName2 (
    ImageHandle,
    SystemTable,
    &gUdfDriverBinding,
    ImageHandle,
    &gUdfComponentName,
    &gUdfComponentName2
    );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
