/*++

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:

  BsDataHubStatusCode.c

Abstract:

  This implements a status code listener that logs status codes into the data
  hub.  This is only active during non-runtime DXE.

--*/
#include "BsDataHubStatusCode.h"

//
// Globals only work at BootService Time. NOT at Runtime!
//
static EFI_DATA_HUB_PROTOCOL  *mDataHub;
static LIST_ENTRY             mRecordBuffer;
static INTN                   mRecordNum;
static EFI_EVENT              mLogDataHubEvent;
static EFI_LOCK               mStatusCodeReportLock;
static BOOLEAN                mEventHandlerActive   = FALSE;

STATUS_CODE_RECORD_LIST *
GetRecordBuffer (
  VOID
  )
/*++

Routine Description:

  Returned buffer of length BYTES_PER_RECORD

Arguments:

  None

Returns:

  Entry in mRecordBuffer or NULL if non available

--*/
{
  STATUS_CODE_RECORD_LIST *Buffer;

  gBS->AllocatePool (EfiBootServicesData, sizeof (STATUS_CODE_RECORD_LIST), (VOID **) &Buffer);
  if (Buffer == NULL) {
    return NULL;
  }

  ZeroMem (Buffer, sizeof (STATUS_CODE_RECORD_LIST));
  Buffer->Signature = BS_DATA_HUB_STATUS_CODE_SIGNATURE;

  return Buffer;
}

DATA_HUB_STATUS_CODE_DATA_RECORD *
AquireEmptyRecordBuffer (
  VOID
  )
/*++

Routine Description:

  Allocate a mRecordBuffer entry in the form of a pointer.

Arguments:

  None

Returns:

  Pointer to new buffer. NULL if none exist.

--*/
{
  STATUS_CODE_RECORD_LIST *DataBuffer;

  if (mRecordNum < MAX_RECORD_NUM) {
    DataBuffer = GetRecordBuffer ();
    if (DataBuffer != NULL) {
      EfiAcquireLock (&mStatusCodeReportLock);
      InsertTailList (&mRecordBuffer, &DataBuffer->Link);
      mRecordNum++;
      EfiReleaseLock (&mStatusCodeReportLock);
      return (DATA_HUB_STATUS_CODE_DATA_RECORD *) DataBuffer->RecordBuffer;
    }
  }

  return NULL;
}

EFI_STATUS
ReleaseRecordBuffer (
  IN  STATUS_CODE_RECORD_LIST  *RecordBuffer
  )
/*++

Routine Description:

  Release a mRecordBuffer entry allocated by AquireEmptyRecordBuffer ().

Arguments:

  RecordBuffer          - Data to free

Returns:

  EFI_SUCCESS           - If DataRecord is valid
  EFI_UNSUPPORTED       - The record list has empty

--*/
{
  ASSERT (RecordBuffer != NULL);
  if (mRecordNum <= 0) {
    return EFI_UNSUPPORTED;
  }

  EfiAcquireLock (&mStatusCodeReportLock);
  RemoveEntryList (&RecordBuffer->Link);
  mRecordNum--;
  EfiReleaseLock (&mStatusCodeReportLock);
  gBS->FreePool (RecordBuffer);
  return EFI_SUCCESS;
}

EFI_STATUS
BsDataHubReportStatusCode (
  IN EFI_STATUS_CODE_TYPE     CodeType,
  IN EFI_STATUS_CODE_VALUE    Value,
  IN UINT32                   Instance,
  IN EFI_GUID                 * CallerId,
  IN EFI_STATUS_CODE_DATA     * Data OPTIONAL
  )
/*++

Routine Description:

  Boot service report status code listener.  This function logs the status code
  into the data hub.

Arguments:

  Same as ReportStatusCode (See Tiano Runtime Specification)

Returns:

  None

--*/
{
  DATA_HUB_STATUS_CODE_DATA_RECORD  *DataHub;
  UINT32                            ErrorLevel;
  VA_LIST                           Marker;
  CHAR8                             *Format;
  UINTN                             Index;
  CHAR16                            FormatBuffer[BYTES_PER_RECORD];

  if (EfiAtRuntime ()) {
    //
    // For now all we do is post code at runtime
    //
    return EFI_SUCCESS;
  }
  //
  // If we had an error while in our event handler, then do nothing so
  // that we don't get in an endless loop.
  //
  if (mEventHandlerActive) {
    return EFI_SUCCESS;
  }

  DataHub = (DATA_HUB_STATUS_CODE_DATA_RECORD *) AquireEmptyRecordBuffer ();
  if (DataHub == NULL) {
    //
    // There are no empty record buffer in private buffers
    //
    return EFI_OUT_OF_RESOURCES;
  }
  //
  // Construct Data Hub Extended Data
  //
  DataHub->CodeType = CodeType;
  DataHub->Value    = Value;
  DataHub->Instance = Instance;

  if (CallerId != NULL) {
    CopyMem (&DataHub->CallerId, CallerId, sizeof (EFI_GUID));
  } else {
    ZeroMem (&DataHub->CallerId, sizeof (EFI_GUID));
  }

  if (Data == NULL) {
    ZeroMem (&DataHub->Data, sizeof (EFI_STATUS_CODE_DATA));
  } else {
    //
    // Copy generic Header
    //
    CopyMem (&DataHub->Data, Data, sizeof (EFI_STATUS_CODE_DATA));

    if (ReportStatusCodeExtractDebugInfo (Data, &ErrorLevel, &Marker, &Format)) {
      //
      // Convert Ascii Format string to Unicode.
      //
      for (Index = 0; Format[Index] != '\0' && Index < (BYTES_PER_RECORD - 1); Index += 1) {
        FormatBuffer[Index] = (CHAR16) Format[Index];
      }

      FormatBuffer[Index] = L'\0';

      //
      // Put processed string into the buffer
      //
      Index = UnicodeVSPrint (
                (CHAR16 *) (DataHub + 1),
                BYTES_PER_RECORD - (sizeof (DATA_HUB_STATUS_CODE_DATA_RECORD)),
                FormatBuffer,
                Marker
                );

      //
      // DATA_HUB_STATUS_CODE_DATA_RECORD followed by VSPrint String Buffer
      //
      DataHub->Data.Size = (UINT16) (Index * sizeof (CHAR16));

    } else {
      //
      // Default behavior is to copy optional data
      //
      if (Data->Size > (BYTES_PER_RECORD - sizeof (DATA_HUB_STATUS_CODE_DATA_RECORD))) {
        DataHub->Data.Size = (UINT16) (BYTES_PER_RECORD - sizeof (DATA_HUB_STATUS_CODE_DATA_RECORD));
      }

      CopyMem (DataHub + 1, Data + 1, DataHub->Data.Size);
    }
  }

  gBS->SignalEvent (mLogDataHubEvent);

  return EFI_SUCCESS;
}

VOID
EFIAPI
LogDataHubEventHandler (
  IN  EFI_EVENT     Event,
  IN  VOID          *Context
  )
/*++

Routine Description:

  The Event handler which will be notified to log data in Data Hub.

Arguments:

  Event     -   Instance of the EFI_EVENT to signal whenever data is
                available to be logged in the system.
  Context   -   Context of the event.

Returns:

  None.

--*/
{
  EFI_STATUS                        Status;
  DATA_HUB_STATUS_CODE_DATA_RECORD  *DataRecord;
  UINTN                             Size;
  UINT64                            DataRecordClass;
  LIST_ENTRY                        *Link;
  STATUS_CODE_RECORD_LIST           *BufferEntry;

  //
  // Set our global flag so we don't recurse if we get an error here.
  //
  mEventHandlerActive = TRUE;

  //
  // Log DataRecord in Data Hub.
  // If there are multiple DataRecords, Log all of them.
  //
  for (Link = mRecordBuffer.ForwardLink; Link != &mRecordBuffer;) {
    BufferEntry = CR (Link, STATUS_CODE_RECORD_LIST, Link, BS_DATA_HUB_STATUS_CODE_SIGNATURE);
    DataRecord  = (DATA_HUB_STATUS_CODE_DATA_RECORD *) (BufferEntry->RecordBuffer);
    Link        = Link->ForwardLink;

    //
    // Add in the size of the header we added.
    //
    Size = sizeof (DATA_HUB_STATUS_CODE_DATA_RECORD) + DataRecord->Data.Size;

    if ((DataRecord->CodeType & EFI_STATUS_CODE_TYPE_MASK) == EFI_PROGRESS_CODE) {
      DataRecordClass = EFI_DATA_RECORD_CLASS_PROGRESS_CODE;
    } else if ((DataRecord->CodeType & EFI_STATUS_CODE_TYPE_MASK) == EFI_ERROR_CODE) {
      DataRecordClass = EFI_DATA_RECORD_CLASS_ERROR;
    } else if ((DataRecord->CodeType & EFI_STATUS_CODE_TYPE_MASK) == EFI_DEBUG_CODE) {
      DataRecordClass = EFI_DATA_RECORD_CLASS_DEBUG;
    } else {
      //
      // Should never get here.
      //
      DataRecordClass = EFI_DATA_RECORD_CLASS_DEBUG |
        EFI_DATA_RECORD_CLASS_ERROR |
        EFI_DATA_RECORD_CLASS_DATA |
        EFI_DATA_RECORD_CLASS_PROGRESS_CODE;
    }

    if (((DataRecord->Instance & EFI_D_ERROR) != 0) &&
        (((DataRecord->Instance & EFI_D_POOL) != 0) || ((DataRecord->Instance & EFI_D_PAGE) != 0))
        ) {
      //
      // If memory error, do not call LogData ().
      //
      DebugPrint ((UINTN)-1, "Memory Error\n");
      Status = EFI_OUT_OF_RESOURCES;
    } else {
      //
      // Log DataRecord in Data Hub
      //
      Status = mDataHub->LogData (
                          mDataHub,
                          &gEfiStatusCodeGuid,
                          &gEfiStatusCodeRuntimeProtocolGuid,
                          DataRecordClass,
                          DataRecord,
                          (UINT32) Size
                          );
    }

    ReleaseRecordBuffer (BufferEntry);
  }

  mEventHandlerActive = FALSE;

  return ;
}

VOID
BsDataHubStatusCodeInitialize (
  VOID
  )
/*++

Routine Description:

  Install a data hub listener.

Arguments:

  (Standard EFI Image entry - EFI_IMAGE_ENTRY_POINT)

Returns:

  EFI_SUCCESS - Logging Hub protocol installed
  Other       - No protocol installed, unload driver.

--*/
{
  EFI_STATUS  Status;

  Status = gBS->LocateProtocol (&gEfiDataHubProtocolGuid, NULL, (VOID **) &mDataHub);
  //
  // Should never fail due to dependency grammer
  //
  ASSERT_EFI_ERROR (Status);

  //
  // Initialize FIFO
  //
  InitializeListHead (&mRecordBuffer);
  mRecordNum = 0;

  EfiInitializeLock (&mStatusCodeReportLock, EFI_TPL_HIGH_LEVEL);

  //
  // Create a Notify Event to log data in Data Hub
  //
  Status = gBS->CreateEvent (
                  EFI_EVENT_NOTIFY_SIGNAL,
                  EFI_TPL_CALLBACK,
                  LogDataHubEventHandler,
                  NULL,
                  &mLogDataHubEvent
                  );

}
