/*++

Copyright (c) 2006, Intel Corporation                                                         
All rights reserved. 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.             

Module Name:

  locate.c

Abstract:

  Locate handle functions    

Revision History

--*/

#include <DxeMain.h>

//
// ProtocolRequest - Last LocateHandle request ID
//
UINTN mEfiLocateHandleRequest = 0;

//
// Internal prototypes
//

typedef struct {
  EFI_GUID        *Protocol;
  VOID            *SearchKey;
  LIST_ENTRY      *Position;
  PROTOCOL_ENTRY  *ProtEntry;
} LOCATE_POSITION;

typedef 
IHANDLE *
(* CORE_GET_NEXT) (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  );

STATIC
IHANDLE *
CoreGetNextLocateAllHandles (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  );

STATIC
IHANDLE *
CoreGetNextLocateByRegisterNotify (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  );

STATIC
IHANDLE *
CoreGetNextLocateByProtocol (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  );

//
//
//



 
EFI_STATUS
EFIAPI
CoreLocateHandle (
  IN EFI_LOCATE_SEARCH_TYPE   SearchType,
  IN EFI_GUID                 *Protocol   OPTIONAL,
  IN VOID                     *SearchKey  OPTIONAL,
  IN OUT UINTN                *BufferSize,
  OUT EFI_HANDLE              *Buffer
  )
/*++

Routine Description:

  Locates the requested handle(s) and returns them in Buffer.

Arguments:

  SearchType  - The type of search to perform to locate the handles

  Protocol    - The protocol to search for
  
  SearchKey   - Dependant on SearchType

  BufferSize  - On input the size of Buffer.  On output the 
                size of data returned.  

  Buffer      - The buffer to return the results in


Returns:

  EFI_BUFFER_TOO_SMALL      - Buffer too small, required buffer size is returned in BufferSize.

  EFI_INVALID_PARAMETER     - Invalid parameter
  
  EFI_SUCCESS               - Successfully found the requested handle(s) and returns them in Buffer.
  
--*/
{
  EFI_STATUS          Status;
  LOCATE_POSITION     Position;
  PROTOCOL_NOTIFY     *ProtNotify;
  CORE_GET_NEXT       GetNext;
  UINTN               ResultSize;
  IHANDLE             *Handle;
  IHANDLE             **ResultBuffer;
  VOID                *Interface;
 
  if (BufferSize == NULL) {
    Status = EFI_INVALID_PARAMETER;
  }
  
  if ((*BufferSize > 0) && (Buffer == NULL)) {
    return EFI_INVALID_PARAMETER;
  }
  
  GetNext = NULL;
  //
  // Set initial position
  //

  Position.Protocol  = Protocol;
  Position.SearchKey = SearchKey;
  Position.Position  = &gHandleList;

  ResultSize = 0;
  ResultBuffer = (IHANDLE **) Buffer;
  Status = EFI_SUCCESS;

  //
  // Lock the protocol database
  //
  
  CoreAcquireProtocolLock ();

  //
  // Get the search function based on type
  //
  switch (SearchType) {
  case AllHandles:      
    GetNext = CoreGetNextLocateAllHandles;       
    break;

  case ByRegisterNotify:    
    //
    // Must have SearchKey for locate ByRegisterNotify
    //
    if (SearchKey == NULL) {
      Status = EFI_INVALID_PARAMETER;
      break;
    }
    GetNext = CoreGetNextLocateByRegisterNotify;   
    break;

  case ByProtocol:      
    GetNext = CoreGetNextLocateByProtocol;
    if (Protocol == NULL) {
      Status = EFI_INVALID_PARAMETER;
      break;
    }
    //
    // Look up the protocol entry and set the head pointer
    //
    Position.ProtEntry = CoreFindProtocolEntry (Protocol, FALSE);
    if (Position.ProtEntry == NULL) {
      Status = EFI_NOT_FOUND;
      break;
    }
    Position.Position = &Position.ProtEntry->Protocols;
    break;

  default:
    Status = EFI_INVALID_PARAMETER;
    break;
  }

  if (EFI_ERROR(Status)) {
    CoreReleaseProtocolLock ();
    return Status;
  }

  //
  // Enumerate out the matching handles
  //
  mEfiLocateHandleRequest += 1;
  for (; ;) {
    //
    // Get the next handle.  If no more handles, stop
    //
    Handle = GetNext (&Position, &Interface);
    if (NULL == Handle) {
      break;
    }

    //
    // Increase the resulting buffer size, and if this handle
    // fits return it
    //
    ResultSize += sizeof(Handle);
    if (ResultSize <= *BufferSize) {
        *ResultBuffer = Handle;
        ResultBuffer += 1;
    }
  }

  //
  // If the result is a zero length buffer, then there were no
  // matching handles
  //
  if (ResultSize == 0) {
    Status = EFI_NOT_FOUND;
  } else {
    //
    // Return the resulting buffer size.  If it's larger than what
    // was passed, then set the error code
    //
    if (ResultSize > *BufferSize) {
      Status = EFI_BUFFER_TOO_SMALL;
    } 
    
    *BufferSize = ResultSize;

    if (SearchType == ByRegisterNotify && !EFI_ERROR(Status)) {
      //
      // If this is a search by register notify and a handle was
      // returned, update the register notification position
      // 
      ProtNotify = SearchKey;
      ProtNotify->Position = ProtNotify->Position->ForwardLink;
    }
  }

  CoreReleaseProtocolLock ();
  return Status;
}


STATIC
IHANDLE *
CoreGetNextLocateAllHandles (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  )
/*++

Routine Description:

  Routine to get the next Handle, when you are searching for all handles.

Arguments:

  Position  - Information about which Handle to seach for.

  Interface - Return the interface structure for the matching protocol.
  
Returns:
  IHANDLE - An IHANDLE is returned if the next Position is not the end of the
            list. A NULL_HANDLE is returned if it's the end of the list.
  
--*/
{
  IHANDLE     *Handle;

  //
  // Next handle
  //
  Position->Position = Position->Position->ForwardLink;

  //
  // If not at the end of the list, get the handle
  //
  Handle      = NULL_HANDLE;
  *Interface  = NULL;
  if (Position->Position != &gHandleList) {
    Handle = CR (Position->Position, IHANDLE, AllHandles, EFI_HANDLE_SIGNATURE);
  }

  return Handle;
}


STATIC
IHANDLE *
CoreGetNextLocateByRegisterNotify (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  )
/*++

Routine Description:

  Routine to get the next Handle, when you are searching for register protocol 
  notifies.

Arguments:

  Position  - Information about which Handle to seach for.

  Interface - Return the interface structure for the matching protocol.
  
Returns:
  IHANDLE - An IHANDLE is returned if the next Position is not the end of the
            list. A NULL_HANDLE is returned if it's the end of the list.
  
--*/
{
  IHANDLE             *Handle;
  PROTOCOL_NOTIFY     *ProtNotify;
  PROTOCOL_INTERFACE  *Prot;
  LIST_ENTRY          *Link;    

  Handle      = NULL_HANDLE;
  *Interface  = NULL;
  ProtNotify = Position->SearchKey;

  //
  // If this is the first request, get the next handle
  //
  if (ProtNotify != NULL) {
    ASSERT(ProtNotify->Signature == PROTOCOL_NOTIFY_SIGNATURE);
    Position->SearchKey = NULL;

    //
    // If not at the end of the list, get the next handle
    //
    Link = ProtNotify->Position->ForwardLink;
    if (Link != &ProtNotify->Protocol->Protocols) {
      Prot = CR (Link, PROTOCOL_INTERFACE, ByProtocol, PROTOCOL_INTERFACE_SIGNATURE);
      Handle = (IHANDLE *) Prot->Handle;
      *Interface = Prot->Interface;
    }  
  }

  return Handle;
}


STATIC
IHANDLE *
CoreGetNextLocateByProtocol (
  IN OUT LOCATE_POSITION    *Position,
  OUT VOID                  **Interface
  )
/*++

Routine Description:

  Routine to get the next Handle, when you are searching for a given protocol.

Arguments:

  Position  - Information about which Handle to seach for.

  Interface - Return the interface structure for the matching protocol.
  
Returns:
  IHANDLE - An IHANDLE is returned if the next Position is not the end of the
            list. A NULL_HANDLE is returned if it's the end of the list.
  
--*/
{
  IHANDLE             *Handle;
  LIST_ENTRY          *Link;
  PROTOCOL_INTERFACE  *Prot;
 
  Handle      = NULL_HANDLE;
  *Interface  = NULL;
  for (; ;) {
    //
    // Next entry
    //
    Link = Position->Position->ForwardLink;
    Position->Position = Link;

    //
    // If not at the end, return the handle
    //
    if (Link == &Position->ProtEntry->Protocols) {
      Handle = NULL_HANDLE;
      break;
    }

    //
    // Get the handle
    //
    Prot = CR(Link, PROTOCOL_INTERFACE, ByProtocol, PROTOCOL_INTERFACE_SIGNATURE);
    Handle = (IHANDLE *) Prot->Handle;
    *Interface = Prot->Interface;

    //
    // If this handle has not been returned this request, then 
    // return it now
    //
    if (Handle->LocateRequest != mEfiLocateHandleRequest) {
      Handle->LocateRequest = mEfiLocateHandleRequest;
      break;
    }
  }

  return Handle;
}



EFI_STATUS
EFIAPI
CoreLocateDevicePath (
  IN EFI_GUID                       *Protocol,
  IN OUT EFI_DEVICE_PATH_PROTOCOL   **DevicePath,
  OUT EFI_HANDLE                    *Device
  )
/*++

Routine Description:

  Locates the handle to a device on the device path that best matches the specified protocol.

Arguments:

  Protocol    - The protocol to search for.
  DevicePath  - On input, a pointer to a pointer to the device path. On output, the device
                  path pointer is modified to point to the remaining part of the devicepath.
  Device      - A pointer to the returned device handle.              

Returns:

  EFI_SUCCESS           - The resulting handle was returned.
  EFI_NOT_FOUND         - No handles matched the search.
  EFI_INVALID_PARAMETER - One of the parameters has an invalid value.

--*/
{
  INTN                        SourceSize;
  INTN                        Size;
  INTN                        BestMatch;
  UINTN                       HandleCount;
  UINTN                       Index;
  EFI_STATUS                  Status;
  EFI_HANDLE                  *Handles;
  EFI_HANDLE                  Handle;
  EFI_DEVICE_PATH_PROTOCOL    *SourcePath;
  EFI_DEVICE_PATH_PROTOCOL    *TmpDevicePath;
  
  if (Protocol == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  if ((DevicePath == NULL) || (*DevicePath == NULL)) {
    return EFI_INVALID_PARAMETER;
  }
  
  if (Device == NULL) {
    return  EFI_INVALID_PARAMETER;
  }
  
  *Device = NULL_HANDLE;
  SourcePath = *DevicePath;
  SourceSize = CoreDevicePathSize (SourcePath) - sizeof(EFI_DEVICE_PATH_PROTOCOL);
  
  //
  // The source path can only have 1 instance
  //
  if (CoreIsDevicePathMultiInstance (SourcePath)) {
    DEBUG((EFI_D_ERROR, "LocateDevicePath: Device path has too many instances\n"));
    return EFI_INVALID_PARAMETER;
  }

  //
  // Get a list of all handles that support the requested protocol
  //
  Status = CoreLocateHandleBuffer (ByProtocol, Protocol, NULL, &HandleCount, &Handles);
  if (EFI_ERROR (Status) || HandleCount == 0) {
    return EFI_NOT_FOUND;
  }

  BestMatch = -1;
  for(Index = 0; Index < HandleCount; Index += 1) {
    Handle = Handles[Index];
    Status = CoreHandleProtocol (Handle, &gEfiDevicePathProtocolGuid, (VOID **)&TmpDevicePath);
    if (EFI_ERROR (Status)) {
      //
      // If this handle doesn't support device path, then skip it
      //
      continue;
    }

    //
    // Check if DevicePath is first part of SourcePath
    //
    Size = CoreDevicePathSize (TmpDevicePath) - sizeof(EFI_DEVICE_PATH_PROTOCOL);
    if ((Size <= SourceSize) && CompareMem (SourcePath, TmpDevicePath, Size) == 0) {
      //
      // If the size is equal to the best match, then we
      // have a duplice device path for 2 different device
      // handles
      //
      ASSERT (Size != BestMatch);
      
      //
      // We've got a match, see if it's the best match so far
      //
      if (Size > BestMatch) {
        BestMatch = Size;
        *Device = Handle;
      }
    }
  }

  CoreFreePool (Handles);
   
  //
  // If there wasn't any match, then no parts of the device path was found.  
  // Which is strange since there is likely a "root level" device path in the system.
  //
  if (BestMatch == -1) {
    return EFI_NOT_FOUND;
  }

  //
  // Return the remaining part of the device path
  //
  *DevicePath = (EFI_DEVICE_PATH_PROTOCOL *) (((UINT8 *) SourcePath) + BestMatch);
  return EFI_SUCCESS;
}


 
EFI_STATUS
EFIAPI
CoreLocateProtocol (
  IN  EFI_GUID  *Protocol,
  IN  VOID      *Registration OPTIONAL,
  OUT VOID      **Interface
  )
/*++

Routine Description:

  Return the first Protocol Interface that matches the Protocol GUID. If
  Registration is pasased in return a Protocol Instance that was just add
  to the system. If Retistration is NULL return the first Protocol Interface
  you find.

Arguments:

  Protocol     - The protocol to search for
  
  Registration - Optional Registration Key returned from RegisterProtocolNotify() 

  Interface    - Return the Protocol interface (instance).

Returns:

  EFI_SUCCESS - If a valid Interface is returned
  
  EFI_INVALID_PARAMETER       - Invalid parameter
  
  EFI_NOT_FOUND               - Protocol interface not found

--*/
{
  EFI_STATUS              Status;
  LOCATE_POSITION         Position;
  PROTOCOL_NOTIFY         *ProtNotify;
  IHANDLE                 *Handle;

  if (Interface == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  
  if (Protocol == NULL) {
    return EFI_NOT_FOUND;
  }
  
  *Interface = NULL;
  Status = EFI_SUCCESS;

  //
  // Set initial position
  //
  Position.Protocol  = Protocol;
  Position.SearchKey = Registration;
  Position.Position  = &gHandleList;
  
  //
  // Lock the protocol database
  //
  CoreAcquireProtocolLock ();

  mEfiLocateHandleRequest += 1;

  if (NULL == Registration) {
    //
    // Look up the protocol entry and set the head pointer
    //
    Position.ProtEntry = CoreFindProtocolEntry (Protocol, FALSE);
    if (Position.ProtEntry == NULL) {
      Status = EFI_NOT_FOUND;
      goto Done;
    }
    Position.Position = &Position.ProtEntry->Protocols;

    Handle = CoreGetNextLocateByProtocol (&Position, Interface);
  } else {
    Handle = CoreGetNextLocateByRegisterNotify (&Position, Interface);   
  }

  if (NULL == Handle) {
    Status = EFI_NOT_FOUND;
  } else if (NULL != Registration) {
    //
    // If this is a search by register notify and a handle was
    // returned, update the register notification position
    // 
    ProtNotify = Registration;
    ProtNotify->Position = ProtNotify->Position->ForwardLink;
  }

Done:
  CoreReleaseProtocolLock ();
  return Status;
}


 
EFI_STATUS
EFIAPI
CoreLocateHandleBuffer (
  IN EFI_LOCATE_SEARCH_TYPE       SearchType,
  IN EFI_GUID                     *Protocol OPTIONAL,
  IN VOID                         *SearchKey OPTIONAL,
  IN OUT UINTN                    *NumberHandles,
  OUT EFI_HANDLE                  **Buffer
  )
/*++

Routine Description:

  Function returns an array of handles that support the requested protocol 
  in a buffer allocated from pool. This is a version of CoreLocateHandle()
  that allocates a buffer for the caller.

Arguments:

  SearchType           - Specifies which handle(s) are to be returned.
  Protocol             - Provides the protocol to search by.   
                         This parameter is only valid for SearchType ByProtocol.
  SearchKey            - Supplies the search key depending on the SearchType.
  NumberHandles      - The number of handles returned in Buffer.
  Buffer               - A pointer to the buffer to return the requested array of 
                         handles that support Protocol.

Returns:
  
  EFI_SUCCESS          - The result array of handles was returned.
  EFI_NOT_FOUND        - No handles match the search. 
  EFI_OUT_OF_RESOURCES - There is not enough pool memory to store the matching results.
  EFI_INVALID_PARAMETER   - Invalid parameter

--*/
{
  EFI_STATUS          Status;
  UINTN               BufferSize;

  if (NumberHandles == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  BufferSize = 0;
  *NumberHandles = 0;
  *Buffer = NULL;
  Status = CoreLocateHandle (
             SearchType,
             Protocol,
             SearchKey,
             &BufferSize,
             *Buffer
             );
  //
  // LocateHandleBuffer() returns incorrect status code if SearchType is
  // invalid.
  //
  // Add code to correctly handle expected errors from CoreLocateHandle().
  //
  if (EFI_ERROR(Status) && Status != EFI_BUFFER_TOO_SMALL) {
    if (Status != EFI_INVALID_PARAMETER) {
      Status = EFI_NOT_FOUND;
    }
    return Status;
  }

  *Buffer = CoreAllocateBootServicesPool (BufferSize);
  if (*Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = CoreLocateHandle (
             SearchType,
             Protocol,
             SearchKey,
             &BufferSize,
             *Buffer
             );

  *NumberHandles = BufferSize/sizeof(EFI_HANDLE);
  if (EFI_ERROR(Status)) {
    *NumberHandles = 0;
  }

  return Status;
}