/** @file
  Public API for Opal Core library.

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

**/

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/TimerLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/TcgStorageOpalLib.h>

#include "TcgStorageOpalLibInternal.h"

#pragma pack(1)
typedef struct {
    UINT8 HardwareReset : 1;
    UINT8 Reserved : 7;
} TCG_BLOCK_SID_CLEAR_EVENTS;
#pragma pack()

#define TRUSTED_COMMAND_TIMEOUT_NS      ((UINT64) 5 * ((UINT64)(1000000)) * 1000) // 5 seconds
#define BUFFER_SIZE                      512

/**
  The function performs a Trusted Send of a Buffer containing a TCG_COM_PACKET.

  @param[in]      Sscp                  The input Ssc Protocol.
  @param[in]      MediaId               The input Media id info used by Ssc Protocol.
  @param[in]      SecurityProtocol      Security Protocol
  @param[in]      SpSpecific            Security Protocol Specific
  @param[in]      TransferLength        Transfer Length of Buffer (in bytes) - always a multiple of 512
  @param[in]      Buffer                Address of Data to transfer
  @param[in]      BufferSize            Full Size of Buffer, including space that may be used for padding.

**/
TCG_RESULT
OpalTrustedSend(
  EFI_STORAGE_SECURITY_COMMAND_PROTOCOL  *Sscp,
  UINT32                                 MediaId,
  UINT8                                  SecurityProtocol,
  UINT16                                 SpSpecific,
  UINTN                                  TransferLength,
  VOID                                   *Buffer,
  UINTN                                  BufferSize
  )
{
  UINTN       TransferLength512;
  EFI_STATUS  Status;

  //
  // Round transferLength up to a 512-byte multiple
  //
  TransferLength512 = (TransferLength + 511) & ~(UINTN)511;

  if (TransferLength512 > BufferSize) {
    return TcgResultFailureBufferTooSmall;
  }

  ZeroMem((UINT8*)Buffer + TransferLength, TransferLength512 - TransferLength);

  Status = Sscp->SendData(
               Sscp,
               MediaId,
               TRUSTED_COMMAND_TIMEOUT_NS,
               SecurityProtocol,
               SwapBytes16(SpSpecific),
               TransferLength512,
               Buffer
               );

  return Status == EFI_SUCCESS ? TcgResultSuccess : TcgResultFailure;
}

/**

  The function performs a Trusted Receive of a Buffer containing a TCG_COM_PACKET.

  @param[in]      Sscp                  The input Ssc Protocol.
  @param[in]      MediaId               The input Media id info used by Ssc Protocol.
  @param[in]      SecurityProtocol      Security Protocol
  @param[in]      SpSpecific            Security Protocol Specific
  @param[in]      Buffer                Address of Data to transfer
  @param[in]      BufferSize            Full Size of Buffer, including space that may be used for padding.
  @param[in]      EstimateTimeCost      Estimate the time needed.

**/
TCG_RESULT
OpalTrustedRecv(
  EFI_STORAGE_SECURITY_COMMAND_PROTOCOL  *Sscp,
  UINT32                                 MediaId,
  UINT8                                  SecurityProtocol,
  UINT16                                 SpSpecific,
  VOID                                   *Buffer,
  UINTN                                  BufferSize,
  UINT32                                 EstimateTimeCost
  )
{
  UINTN           TransferLength512;
  UINT32          Tries;
  TCG_COM_PACKET  *ComPacket;
  UINT32          Length;
  UINT32          OutstandingData;
  EFI_STATUS      Status;
  UINTN           TransferSize;

  //
  // Round Buffer Size down to a 512-byte multiple
  //
  TransferLength512 = BufferSize & ~(UINTN)511;
  Tries = 0;
  ComPacket = NULL;
  Length = 0;
  OutstandingData = 0;

  if (TransferLength512 < sizeof(TCG_COM_PACKET)) {
    DEBUG ((DEBUG_INFO, "transferLength %u too small for ComPacket\n", TransferLength512));
    return TcgResultFailureBufferTooSmall;
  }

  //
  // Some devices respond with Length = 0 and OutstandingData = 1 to indicate that processing is not yet completed,
  // so we need to retry the IF-RECV to get the actual Data.
  // See TCG Core Spec v2 Table 45 IF-RECV ComPacket Field Values Summary
  // This is an arbitrary number of retries, not from the spec.
  //
  // if user input estimate time cost(second level) value bigger than 10s, base on user input value to wait.
  // Else, Use a max timeout of 10 seconds to wait, 5000 tries * 2ms = 10s
  //
  if (EstimateTimeCost > 10) {
    Tries = EstimateTimeCost * 500; // 500 = 1000 * 1000 / 2000;
  } else {
    Tries = 5000;
  }
  while ((Tries--) > 0) {
    ZeroMem( Buffer, BufferSize );
    TransferSize = 0;

    Status = Sscp->ReceiveData(
                 Sscp,
                 MediaId,
                 TRUSTED_COMMAND_TIMEOUT_NS,
                 SecurityProtocol,
                 SwapBytes16(SpSpecific),
                 TransferLength512,
                 Buffer,
                 &TransferSize
             );
    if (EFI_ERROR (Status)) {
      return TcgResultFailure;
    }

    if (SecurityProtocol != TCG_OPAL_SECURITY_PROTOCOL_1 && SecurityProtocol != TCG_OPAL_SECURITY_PROTOCOL_2) {
      return TcgResultSuccess;
    }

    if (SpSpecific == TCG_SP_SPECIFIC_PROTOCOL_LEVEL0_DISCOVERY) {
      return TcgResultSuccess;
    }

    ComPacket = (TCG_COM_PACKET*) Buffer;
    Length = SwapBytes32(ComPacket->LengthBE);
    OutstandingData = SwapBytes32( ComPacket->OutstandingDataBE );

    if (Length != 0 && OutstandingData == 0) {
      return TcgResultSuccess;
    }

    //
    // Delay for 2 ms
    //
    MicroSecondDelay (2000);
  }

  return TcgResultFailure;
}

/**
  The function performs send, recv, check comIDs, check method status action.

  @param[in]      Session           OPAL_SESSION related to this method..
  @param[in]      SendSize          Transfer Length of Buffer (in bytes) - always a multiple of 512
  @param[in]      Buffer            Address of Data to transfer
  @param[in]      BufferSize        Full Size of Buffer, including space that may be used for padding.
  @param[in]      ParseStruct       Structure used to parse received TCG response.
  @param[in]      MethodStatus      Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.
  @param[in]      EstimateTimeCost  Estimate the time need to for the method.
**/
TCG_RESULT
EFIAPI
OpalPerformMethod (
  OPAL_SESSION     *Session,
  UINT32           SendSize,
  VOID             *Buffer,
  UINT32           BufferSize,
  TCG_PARSE_STRUCT *ParseStruct,
  UINT8            *MethodStatus,
  UINT32           EstimateTimeCost
  )
{
  NULL_CHECK(Session);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(OpalTrustedSend(
                  Session->Sscp,
                  Session->MediaId,
                  TCG_OPAL_SECURITY_PROTOCOL_1,
                  Session->OpalBaseComId,
                  SendSize,
                  Buffer,
                  BufferSize
              ));

  ERROR_CHECK(OpalTrustedRecv(
                  Session->Sscp,
                  Session->MediaId,
                  TCG_OPAL_SECURITY_PROTOCOL_1,
                  Session->OpalBaseComId,
                  Buffer,
                  BufferSize,
                  EstimateTimeCost
              ));

  ERROR_CHECK(TcgInitTcgParseStruct(ParseStruct, Buffer, BufferSize));
  ERROR_CHECK(TcgCheckComIds(ParseStruct, Session->OpalBaseComId, Session->ComIdExtension));
  ERROR_CHECK(TcgGetMethodStatus(ParseStruct, MethodStatus));

  return TcgResultSuccess;
}

/**
  Trig the block sid action.

  @param[in]      Session            OPAL_SESSION related to this method..
  @param[in]      HardwareReset      Whether need to do hardware reset.

**/
TCG_RESULT
EFIAPI
OpalBlockSid(
  OPAL_SESSION                           *Session,
  BOOLEAN                                HardwareReset
  )
{
  UINT8                      Buffer[BUFFER_SIZE];
  TCG_BLOCK_SID_CLEAR_EVENTS *ClearEvents;

  NULL_CHECK(Session);

  //
  // Set Hardware Reset bit
  //
  ClearEvents = (TCG_BLOCK_SID_CLEAR_EVENTS *) &Buffer[0];

  ClearEvents->Reserved = 0;
  ClearEvents->HardwareReset = HardwareReset;

  return(OpalTrustedSend(
                  Session->Sscp,
                  Session->MediaId,
                  TCG_OPAL_SECURITY_PROTOCOL_2,
                  TCG_BLOCKSID_COMID,   // hardcode ComID 0x0005
                  1,
                  Buffer,
                  BUFFER_SIZE
              ));
}

/**

  Reverts device using Admin SP Revert method.

  @param[in]  AdminSpSession      OPAL_SESSION with OPAL_UID_ADMIN_SP as OPAL_ADMIN_SP_PSID_AUTHORITY to perform PSID revert.

**/
TCG_RESULT
EFIAPI
OpalPsidRevert(
  OPAL_SESSION              *AdminSpSession
  )
{
  //
  // Now that base comid is known, start Session
  // we'll attempt to start Session as PSID authority
  // verify PSID Authority is defined in ADMIN SP authority table... is this possible?
  //
  TCG_CREATE_STRUCT  CreateStruct;
  TCG_PARSE_STRUCT   ParseStruct;
  UINT32             Size;
  UINT8              Buffer[BUFFER_SIZE];
  UINT8              MethodStatus;

  NULL_CHECK(AdminSpSession);

  //
  // Send Revert action on Admin SP
  //
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buffer, BUFFER_SIZE));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, AdminSpSession->OpalBaseComId, AdminSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, AdminSpSession->TperSessionId, AdminSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_UID_ADMIN_SP, OPAL_ADMIN_SP_REVERT_METHOD));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send Revert Method Call
  //
  ERROR_CHECK(OpalPerformMethod(AdminSpSession, Size, Buffer, BUFFER_SIZE, &ParseStruct, &MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(MethodStatus, TcgResultFailure);

  return TcgResultSuccess;
}

/**

  Reverts device using Admin SP Revert method.

  @param[in]  AdminSpSession      OPAL_SESSION with OPAL_UID_ADMIN_SP as OPAL_ADMIN_SP_PSID_AUTHORITY to perform PSID revert.
  @param[in]  EstimateTimeCost    Estimate the time needed.

**/
TCG_RESULT
OpalPyrite2PsidRevert(
  OPAL_SESSION              *AdminSpSession,
  UINT32                    EstimateTimeCost
  )
{
  //
  // Now that base comid is known, start Session
  // we'll attempt to start Session as PSID authority
  // verify PSID Authority is defined in ADMIN SP authority table... is this possible?
  //
  TCG_CREATE_STRUCT  CreateStruct;
  TCG_PARSE_STRUCT   ParseStruct;
  UINT32             Size;
  UINT8              Buffer[BUFFER_SIZE];
  UINT8              MethodStatus;


  NULL_CHECK(AdminSpSession);

  //
  // Send Revert action on Admin SP
  //
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buffer, BUFFER_SIZE));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, AdminSpSession->OpalBaseComId, AdminSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, AdminSpSession->TperSessionId, AdminSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_UID_ADMIN_SP, OPAL_ADMIN_SP_REVERT_METHOD));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send Revert Method Call
  //
  ERROR_CHECK(OpalPerformMethod(AdminSpSession, Size, Buffer, BUFFER_SIZE, &ParseStruct, &MethodStatus, EstimateTimeCost));
  METHOD_STATUS_ERROR_CHECK(MethodStatus, TcgResultFailure);

  return TcgResultSuccess;
}

/**

  The function fills in the provided Buffer with the level 0 discovery Header
  of the device specified.

  @param[in]        Session         OPAL_SESSION data.
  @param[in]        BufferSize      Size of Buffer provided (in bytes)
  @param[in]        BuffAddress     Buffer address to fill with Level 0 Discovery response

**/
TCG_RESULT
EFIAPI
OpalRetrieveLevel0DiscoveryHeader(
  OPAL_SESSION     *Session,
  UINTN            BufferSize,
  VOID             *BuffAddress
  )
{
  return (OpalTrustedRecv(
              Session->Sscp,
              Session->MediaId,
              TCG_OPAL_SECURITY_PROTOCOL_1,   // SP
              TCG_SP_SPECIFIC_PROTOCOL_LEVEL0_DISCOVERY, // SP_Specific
              BuffAddress,
              BufferSize,
              0
            ));
}

/**

  The function fills in the provided Buffer with the supported protocol list
  of the device specified.

  @param[in]        Session         OPAL_SESSION data.
  @param[in]        BufferSize      Size of Buffer provided (in bytes)
  @param[in]        BuffAddress     Buffer address to fill with security protocol list

**/
TCG_RESULT
EFIAPI
OpalRetrieveSupportedProtocolList(
  OPAL_SESSION     *Session,
  UINTN            BufferSize,
  VOID             *BuffAddress
  )
{
  return (OpalTrustedRecv(
              Session->Sscp,
              Session->MediaId,
              TCG_SECURITY_PROTOCOL_INFO, // SP
              TCG_SP_SPECIFIC_PROTOCOL_LIST, // SP_Specific
              BuffAddress,
              BufferSize,
              0
          ));
}

/**
  Starts a session with a security provider (SP).

  If a session is started successfully, the caller must end the session with OpalEndSession when finished
  performing Opal actions.

  @param[in/out]  Session                 OPAL_SESSION to initialize.
  @param[in]      SpId                    Security provider ID to start the session with.
  @param[in]      Write                   Whether the session should be read-only (FALSE) or read/write (TRUE).
  @param[in]      HostChallengeLength     Length of the host challenge.  Length should be 0 if hostChallenge is NULL
  @param[in]      HostChallenge           Host challenge for Host Signing Authority.  If NULL, then no Host Challenge will be sent.
  @param[in]      HostSigningAuthority    Host Signing Authority used for start session.  If NULL, then no Host Signing Authority will be sent.
  @param[in/out]  MethodStatus            Status of the StartSession method; only valid if TcgResultSuccess is returned.

  @return TcgResultSuccess indicates that the function completed without any internal errors.
  The caller must inspect the MethodStatus field to determine whether the method completed successfully.

**/
TCG_RESULT
EFIAPI
OpalStartSession(
  OPAL_SESSION     *Session,
  TCG_UID          SpId,
  BOOLEAN          Write,
  UINT32           HostChallengeLength,
  const VOID       *HostChallenge,
  TCG_UID          HostSigningAuthority,
  UINT8            *MethodStatus
  )
{
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  UINT8             Buf[BUFFER_SIZE];
  UINT16            ComIdExtension;
  UINT32            HostSessionId;

  ComIdExtension = 0;
  HostSessionId  = 1;

  NULL_CHECK(Session);
  NULL_CHECK(MethodStatus);

  Session->ComIdExtension = ComIdExtension;
  Session->HostSessionId = HostSessionId;

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgCreateStartSession(
                    &CreateStruct,
                    &Size,
                    Session->OpalBaseComId,
                    ComIdExtension,
                    HostSessionId,
                    SpId,
                    Write,
                    HostChallengeLength,
                    HostChallenge,
                    HostSigningAuthority
                ));
  ERROR_CHECK(OpalPerformMethod(Session, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));
  if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
    return TcgResultSuccess; // return early if method failed - user must check MethodStatus
  }

  if (TcgParseSyncSession(&ParseStruct, Session->OpalBaseComId, ComIdExtension, HostSessionId, &Session->TperSessionId) != TcgResultSuccess) {
    OpalEndSession(Session);
    return TcgResultFailure;
  }

  return TcgResultSuccess;
}

/**
  Close a session opened with OpalStartSession.

  @param[in/out]  Session                 OPAL_SESSION to end.

**/
TCG_RESULT
EFIAPI
OpalEndSession(
  OPAL_SESSION     *Session
  )
{
  UINT8             Buffer[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  UINT32            Size;
  TCG_PARSE_STRUCT  ParseStruct;

  NULL_CHECK(Session);
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buffer, sizeof(Buffer)));
  ERROR_CHECK(TcgCreateEndSession(
                  &CreateStruct,
                  &Size,
                  Session->OpalBaseComId,
                  Session->ComIdExtension,
                  Session->HostSessionId,
                  Session->TperSessionId
                ));

  ERROR_CHECK(OpalTrustedSend(
                  Session->Sscp,
                  Session->MediaId,
                  TCG_OPAL_SECURITY_PROTOCOL_1,
                  Session->OpalBaseComId,
                  Size,
                  Buffer,
                  sizeof(Buffer)
              ));

  ERROR_CHECK(OpalTrustedRecv(
                  Session->Sscp,
                  Session->MediaId,
                  TCG_OPAL_SECURITY_PROTOCOL_1,
                  Session->OpalBaseComId,
                  Buffer,
                  sizeof(Buffer),
                  0
              ));

  ERROR_CHECK(TcgInitTcgParseStruct(&ParseStruct, Buffer, sizeof(Buffer)));
  ERROR_CHECK(TcgCheckComIds(&ParseStruct, Session->OpalBaseComId, Session->ComIdExtension));

  ERROR_CHECK(TcgGetNextEndOfSession(&ParseStruct));
  return TcgResultSuccess;
}

/**

  The function retrieves the MSID from the device specified

  @param[in]  AdminSpSession      OPAL_SESSION with OPAL_UID_ADMIN_SP as OPAL_ADMIN_SP_PSID_AUTHORITY to perform PSID revert.
  @param[in]  MsidBufferSize      Allocated Buffer Size (in bytes) for MSID allocated by caller
  @param[in]  Msid                Variable Length byte sequence representing MSID of device
  @param[in]  MsidLength          Actual Length of MSID retrieved from device

**/
TCG_RESULT
EFIAPI
OpalGetMsid(
  OPAL_SESSION    *AdminSpSession,
  UINT32          MsidBufferSize,
  UINT8           *Msid,
  UINT32          *MsidLength
  )
{
  //
  // now that base comid is known, start Session
  // we'll attempt to start Session as PSID authority
  // verify PSID Authority is defined in ADMIN SP authority table... is this possible?
  //
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  UINT8             MethodStatus;
  UINT32            Col;
  const VOID        *RecvMsid;
  UINT8             Buffer[BUFFER_SIZE];

  NULL_CHECK(AdminSpSession);
  NULL_CHECK(Msid);
  NULL_CHECK(MsidLength);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buffer, BUFFER_SIZE));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, AdminSpSession->OpalBaseComId, AdminSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, AdminSpSession->TperSessionId, AdminSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_UID_ADMIN_SP_C_PIN_MSID, TCG_UID_METHOD_GET));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgAddStartList(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_START_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_ADMIN_SP_PIN_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_END_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_ADMIN_SP_PIN_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddEndList(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send MSID Method Call
  //
  ERROR_CHECK(OpalPerformMethod(AdminSpSession, Size, Buffer, BUFFER_SIZE, &ParseStruct, &MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(MethodStatus, TcgResultFailure);

  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartName(&ParseStruct));
  ERROR_CHECK(TcgGetNextUINT32(&ParseStruct, &Col));
  ERROR_CHECK(TcgGetNextByteSequence(&ParseStruct, &RecvMsid, MsidLength));
  ERROR_CHECK(TcgGetNextEndName(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndOfData(&ParseStruct));

  if (Col != OPAL_ADMIN_SP_PIN_COL) {
    DEBUG ((DEBUG_INFO, "ERROR: got col %u, expected %u\n", Col, OPAL_ADMIN_SP_PIN_COL));
    return TcgResultFailure;
  }

  if (RecvMsid == NULL) {
    return TcgResultFailure;
  }

  if (MsidBufferSize < *MsidLength) {
    DEBUG ((DEBUG_INFO, "Buffer too small MsidBufferSize: %d MsidLength: %d\n", MsidBufferSize, *MsidLength));
    return TcgResultFailureBufferTooSmall;
  }

  //
  // copy msid into Buffer
  //
  CopyMem(Msid, RecvMsid, *MsidLength);
  return TcgResultSuccess;
}

/**

  The function retrieves the MSID from the device specified

  @param[in]  AdminSpSession              OPAL_SESSION with OPAL_UID_ADMIN_SP as OPAL_ADMIN_SP_ANYBODY_AUTHORITY
  @param[out] ActiveDataRemovalMechanism  Active Data Removal Mechanism that the device will use for Revert/RevertSP calls.

**/
TCG_RESULT
OpalPyrite2GetActiveDataRemovalMechanism (
  IN  OPAL_SESSION    *AdminSpSession,
  OUT UINT8           *ActiveDataRemovalMechanism
  )
{
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  UINT8             MethodStatus;
  UINT32            Col;
  UINT8             RecvActiveDataRemovalMechanism;
  UINT8             Buffer[BUFFER_SIZE];

  NULL_CHECK(AdminSpSession);
  NULL_CHECK(ActiveDataRemovalMechanism);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buffer, BUFFER_SIZE));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, AdminSpSession->OpalBaseComId, AdminSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, AdminSpSession->TperSessionId, AdminSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_UID_ADMIN_SP_DATA_REMOVAL_MECHANISM, TCG_UID_METHOD_GET));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgAddStartList(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_START_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_ADMIN_SP_ACTIVE_DATA_REMOVAL_MECHANISM_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_END_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_ADMIN_SP_ACTIVE_DATA_REMOVAL_MECHANISM_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddEndList(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send Get Active Data Removal Mechanism Method Call
  //
  ERROR_CHECK(OpalPerformMethod(AdminSpSession, Size, Buffer, BUFFER_SIZE, &ParseStruct, &MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(MethodStatus, TcgResultFailure);

  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartName(&ParseStruct));
  ERROR_CHECK(TcgGetNextUINT32(&ParseStruct, &Col));
  ERROR_CHECK(TcgGetNextUINT8(&ParseStruct, &RecvActiveDataRemovalMechanism));
  ERROR_CHECK(TcgGetNextEndName(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndOfData(&ParseStruct));

  if (Col != OPAL_ADMIN_SP_ACTIVE_DATA_REMOVAL_MECHANISM_COL) {
    DEBUG ((DEBUG_INFO, "ERROR: got col %u, expected %u\n", Col, OPAL_ADMIN_SP_ACTIVE_DATA_REMOVAL_MECHANISM_COL));
    return TcgResultFailure;
  }

  if (RecvActiveDataRemovalMechanism >= ResearvedMechanism) {
    return TcgResultFailure;
  }

  //
  // Copy active data removal mechanism into Buffer
  //
  CopyMem(ActiveDataRemovalMechanism, &RecvActiveDataRemovalMechanism, sizeof(RecvActiveDataRemovalMechanism));
  return TcgResultSuccess;
}

/**

  The function calls the Admin SP RevertSP method on the Locking SP.  If KeepUserData is True, then the optional parameter
  to keep the user Data is set to True, otherwise the optional parameter is not provided.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP as OPAL_LOCKING_SP_ADMIN1_AUTHORITY to revertSP
  @param[in]      KeepUserData        Specifies whether or not to keep user Data when performing RevertSP action. True = keeps user Data.
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalAdminRevert(
  OPAL_SESSION    *LockingSpSession,
  BOOLEAN         KeepUserData,
  UINT8           *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  UINT32            Size;
  TCG_PARSE_STRUCT  ParseStruct;
  TCG_RESULT        Ret;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  //
  // ReadLocked or WriteLocked must be False (per Opal spec) to guarantee revertSP can keep user Data
  //
  if (KeepUserData) {
    //
    // set readlocked and writelocked to false
    //
    Ret = OpalUpdateGlobalLockingRange(
                        LockingSpSession,
                        FALSE,
                        FALSE,
                        MethodStatus);

    if (Ret != TcgResultSuccess || *MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
      //
      // bail out
      //
      return Ret;
    }
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, TCG_UID_THIS_SP, OPAL_LOCKING_SP_REVERTSP_METHOD));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));

  if (KeepUserData) {
    //
    // optional parameter to keep Data after revert
    //
    ERROR_CHECK(TcgAddStartName(&CreateStruct));
    ERROR_CHECK(TcgAddUINT32(&CreateStruct, 0x060000));      // weird Value but that's what spec says
    ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, KeepUserData));
    ERROR_CHECK(TcgAddEndName(&CreateStruct));
  }

  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send RevertSP method call
  //
  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  //
  // Session is immediately ended by device after successful revertsp, so no need to end Session
  //
  if (*MethodStatus == TCG_METHOD_STATUS_CODE_SUCCESS) {
    //
    // Caller should take ownership again
    //
    return TcgResultSuccess;
  } else {
    //
    // End Session
    //
    METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);     // exit with success on method failure - user must inspect MethodStatus
  }

  return TcgResultSuccess;
}


/**

  The function calls the Admin SP RevertSP method on the Locking SP.  If KeepUserData is True, then the optional parameter
  to keep the user Data is set to True, otherwise the optional parameter is not provided.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP as OPAL_LOCKING_SP_ADMIN1_AUTHORITY to revertSP
  @param[in]      KeepUserData        Specifies whether or not to keep user Data when performing RevertSP action. True = keeps user Data.
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.
  @param[in]      EstimateTimeCost    Estimate the time needed.

**/
TCG_RESULT
OpalPyrite2AdminRevert(
  OPAL_SESSION    *LockingSpSession,
  BOOLEAN         KeepUserData,
  UINT8           *MethodStatus,
  UINT32          EstimateTimeCost
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  UINT32            Size;
  TCG_PARSE_STRUCT  ParseStruct;
  TCG_RESULT        Ret;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  //
  // ReadLocked or WriteLocked must be False (per Opal spec) to guarantee revertSP can keep user Data
  //
  if (KeepUserData) {
    //
    // set readlocked and writelocked to false
    //
    Ret = OpalUpdateGlobalLockingRange(
                        LockingSpSession,
                        FALSE,
                        FALSE,
                        MethodStatus);

    if (Ret != TcgResultSuccess || *MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
      //
      // bail out
      //
      return Ret;
    }
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, TCG_UID_THIS_SP, OPAL_LOCKING_SP_REVERTSP_METHOD));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));

  if (KeepUserData) {
    //
    // optional parameter to keep Data after revert
    //
    ERROR_CHECK(TcgAddStartName(&CreateStruct));
    ERROR_CHECK(TcgAddUINT32(&CreateStruct, 0x060000));      // weird Value but that's what spec says
    ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, KeepUserData));
    ERROR_CHECK(TcgAddEndName(&CreateStruct));
  }

  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send RevertSP method call
  //
  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, EstimateTimeCost));

  //
  // Session is immediately ended by device after successful revertsp, so no need to end Session
  //
  if (*MethodStatus == TCG_METHOD_STATUS_CODE_SUCCESS) {
    //
    // Caller should take ownership again
    //
    return TcgResultSuccess;
  } else {
    //
    // End Session
    //
    METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);     // exit with success on method failure - user must inspect MethodStatus
  }

  return TcgResultSuccess;
}

/**

  The function activates the Locking SP.
  Once activated, per Opal spec, the ADMIN SP SID PIN is copied over to the ADMIN1 LOCKING SP PIN.
  If the Locking SP is already enabled, then TcgResultSuccess is returned and no action occurs.

  @param[in]      AdminSpSession      OPAL_SESSION with OPAL_UID_ADMIN_SP as OPAL_ADMIN_SP_SID_AUTHORITY to activate Locking SP
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalActivateLockingSp(
  OPAL_SESSION           *AdminSpSession,
  UINT8                  *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  UINT32            Size;
  TCG_PARSE_STRUCT  ParseStruct;

  NULL_CHECK(AdminSpSession);
  NULL_CHECK(MethodStatus);

  //
  // Call Activate method on Locking SP
  //
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, AdminSpSession->OpalBaseComId, AdminSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, AdminSpSession->TperSessionId, AdminSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_UID_LOCKING_SP, OPAL_ADMIN_SP_ACTIVATE_METHOD));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  //
  // Send Activate method call
  //
  ERROR_CHECK(OpalPerformMethod(AdminSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess); // exit with success on method failure - user must inspect MethodStatus

  return TcgResultSuccess;
}

/**

  The function sets the PIN column of the specified cpinRowUid (authority) with the newPin Value.

  @param[in/out]  Session                 OPAL_SESSION to set password
  @param[in]      CpinRowUid              UID of row (authority) to update PIN column
  @param[in]      NewPin                  New Pin to set for cpinRowUid specified
  @param[in]      NewPinLength            Length in bytes of newPin
  @param[in/out]  MethodStatus            Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalSetPassword(
  OPAL_SESSION   *Session,
  TCG_UID        CpinRowUid,
  const VOID     *NewPin,
  UINT32         NewPinLength,
  UINT8          *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;

  NULL_CHECK(Session);
  NULL_CHECK(NewPin);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgCreateSetCPin(
                         &CreateStruct,
                         &Size,
                         Session->OpalBaseComId,
                         Session->ComIdExtension,
                         Session->TperSessionId,
                         Session->HostSessionId,
                         CpinRowUid,
                         NewPin,
                         NewPinLength
                         ));

  ERROR_CHECK(OpalPerformMethod(Session, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));
  // exit with success on method failure - user must inspect MethodStatus
  METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);

  return TcgResultSuccess;
}

/**

  The function sets the Enabled column to TRUE for the authorityUid provided and updates the PIN column for the cpinRowUid provided
  using the newPin provided.  AuthorityUid and cpinRowUid should describe the same authority.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP as OPAL_LOCKING_SP_ADMIN1_AUTHORITY to update
  @param[in]      CpinRowUid          Row UID of C_PIN table of Locking SP to update PIN
  @param[in]      AuthorityUid        UID of Locking SP authority to update Pin column with
  @param[in]      NewPin              New Password used to set Pin column
  @param[in]      NewPinLength        Length in bytes of new password
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalSetLockingSpAuthorityEnabledAndPin(
  OPAL_SESSION    *LockingSpSession,
  TCG_UID         CpinRowUid,
  TCG_UID         AuthorityUid,
  const VOID      *NewPin,
  UINT32          NewPinLength,
  UINT8           *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  TCG_UID           ActiveKey;
  TCG_RESULT        Ret;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(NewPin);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgSetAuthorityEnabled(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  AuthorityUid,
                  TRUE));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
    DEBUG ((DEBUG_INFO, "Send Set Authority error\n"));
    return TcgResultFailure;
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));

  ERROR_CHECK(TcgCreateSetCPin(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  CpinRowUid,
                  NewPin,
                  NewPinLength));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  //
  // allow user1 to set global range to unlocked/locked by modifying ACE_Locking_GlobalRange_SetRdLocked/SetWrLocked
  //
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgCreateSetAce(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  OPAL_LOCKING_SP_ACE_LOCKING_GLOBALRANGE_SET_RDLOCKED,
                  OPAL_LOCKING_SP_USER1_AUTHORITY,
                  TCG_ACE_EXPRESSION_OR,
                  OPAL_LOCKING_SP_ADMINS_AUTHORITY
              ));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
    DEBUG ((DEBUG_INFO, "Update ACE for RDLOCKED failed\n"));
    return TcgResultFailure;
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgCreateSetAce(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  OPAL_LOCKING_SP_ACE_LOCKING_GLOBALRANGE_SET_WRLOCKED,
                  OPAL_LOCKING_SP_USER1_AUTHORITY,
                  TCG_ACE_EXPRESSION_OR,
                  OPAL_LOCKING_SP_ADMINS_AUTHORITY
              ));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
    DEBUG ((DEBUG_INFO, "Update ACE for WRLOCKED failed\n"));
    return TcgResultFailure;
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(OpalCreateRetrieveGlobalLockingRangeActiveKey(LockingSpSession, &CreateStruct, &Size));
  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  //
  // For Pyrite type SSC, it not supports Active Key.
  // So here add check logic before enable it.
  //
  Ret = OpalParseRetrieveGlobalLockingRangeActiveKey(&ParseStruct, &ActiveKey);
  if (Ret == TcgResultSuccess) {
    ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
    ERROR_CHECK(TcgCreateSetAce(
                    &CreateStruct,
                    &Size,
                    LockingSpSession->OpalBaseComId,
                    LockingSpSession->ComIdExtension,
                    LockingSpSession->TperSessionId,
                    LockingSpSession->HostSessionId,
                    (ActiveKey == OPAL_LOCKING_SP_K_AES_256_GLOBALRANGE_KEY) ? OPAL_LOCKING_SP_ACE_K_AES_256_GLOBALRANGE_GENKEY : OPAL_LOCKING_SP_ACE_K_AES_128_GLOBALRANGE_GENKEY,
                    OPAL_LOCKING_SP_USER1_AUTHORITY,
                    TCG_ACE_EXPRESSION_OR,
                    OPAL_LOCKING_SP_ADMINS_AUTHORITY
                ));

    ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

    if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
      DEBUG ((DEBUG_INFO, "Update ACE for GLOBALRANGE_GENKEY failed\n"));
      //
      // Disable user1 if all permissions are not granted.
      //
      return TcgResultFailure;
    }
  }

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgCreateSetAce(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  OPAL_LOCKING_SP_ACE_LOCKING_GLOBALRANGE_GET_ALL,
                  OPAL_LOCKING_SP_USER1_AUTHORITY,
                  TCG_ACE_EXPRESSION_OR,
                  OPAL_LOCKING_SP_ADMINS_AUTHORITY
              ));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  if (*MethodStatus != TCG_METHOD_STATUS_CODE_SUCCESS) {
    DEBUG ((DEBUG_INFO, "Update ACE for OPAL_LOCKING_SP_ACE_LOCKING_GLOBALRANGE_GET_ALL failed\n"));
    return TcgResultFailure;
  }

  return TcgResultSuccess;
}

/**

  The function sets the Enabled column to FALSE for the USER1 authority.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP as OPAL_LOCKING_SP_ADMIN1_AUTHORITY to disable User1
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalDisableUser(
  OPAL_SESSION     *LockingSpSession,
  UINT8            *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgSetAuthorityEnabled(
                  &CreateStruct,
                  &Size,
                  LockingSpSession->OpalBaseComId,
                  LockingSpSession->ComIdExtension,
                  LockingSpSession->TperSessionId,
                  LockingSpSession->HostSessionId,
                  OPAL_LOCKING_SP_USER1_AUTHORITY,
                  FALSE));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  return TcgResultSuccess;
}

/**

  The function retrieves the active key of the global locking range
  and calls the GenKey method on the active key retrieved.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP to generate key
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalGlobalLockingRangeGenKey(
  OPAL_SESSION   *LockingSpSession,
  UINT8          *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  TCG_UID           ActiveKey;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  //
  // retrieve the activekey in order to know which globalrange key to generate
  //
  ERROR_CHECK(OpalCreateRetrieveGlobalLockingRangeActiveKey(LockingSpSession, &CreateStruct, &Size));
  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);

  ERROR_CHECK(OpalParseRetrieveGlobalLockingRangeActiveKey(&ParseStruct, &ActiveKey));

  //
  // call genkey on ActiveKey UID
  //
  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, ActiveKey, TCG_UID_METHOD_GEN_KEY));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));

  return TcgResultSuccess;
}

/**

  The function updates the ReadLocked and WriteLocked columns of the Global Locking Range.
  This function is required for a user1 authority, since a user1 authority shall only have access to ReadLocked and WriteLocked columns
  (not ReadLockEnabled and WriteLockEnabled columns).

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP to generate key
  @param[in]      ReadLocked          Value to set ReadLocked column for Global Locking Range
  @param[in]      WriteLocked         Value to set WriteLocked column for Global Locking Range
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalUpdateGlobalLockingRange(
  OPAL_SESSION             *LockingSpSession,
  BOOLEAN                  ReadLocked,
  BOOLEAN                  WriteLocked,
  UINT8                    *MethodStatus
  )
{
  UINT8             Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));

  //
  // set global locking range values
  //
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, OPAL_LOCKING_SP_LOCKING_GLOBALRANGE, TCG_UID_METHOD_SET));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x01));                       // "Values"
  ERROR_CHECK(TcgAddStartList(&CreateStruct));

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x07));                       // "ReadLocked"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, ReadLocked));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x08));                       // "WriteLocked"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, WriteLocked));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddEndList(&CreateStruct));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);

  return TcgResultSuccess;
}

/**

  The function updates the RangeStart, RangeLength, ReadLockedEnabled, WriteLockedEnabled, ReadLocked and WriteLocked columns
  of the specified Locking Range.  This function requires admin authority of a locking SP session.

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP to generate key
  @param[in]      LockingRangeUid     Locking range UID to set values
  @param[in]      RangeStart          Value to set RangeStart column for Locking Range
  @param[in]      RangeLength         Value to set RangeLength column for Locking Range
  @param[in]      ReadLockEnabled     Value to set readLockEnabled column for Locking Range
  @param[in]      WriteLockEnabled    Value to set writeLockEnabled column for Locking Range
  @param[in]      ReadLocked          Value to set ReadLocked column for Locking Range
  @param[in]      WriteLocked         Value to set WriteLocked column for Locking Range
  @param[in/out]  MethodStatus        Method status of last action performed.  If action succeeded, it should be TCG_METHOD_STATUS_CODE_SUCCESS.

**/
TCG_RESULT
EFIAPI
OpalSetLockingRange(
  OPAL_SESSION      *LockingSpSession,
  TCG_UID           LockingRangeUid,
  UINT64            RangeStart,
  UINT64            RangeLength,
  BOOLEAN           ReadLockEnabled,
  BOOLEAN           WriteLockEnabled,
  BOOLEAN           ReadLocked,
  BOOLEAN           WriteLocked,
  UINT8             *MethodStatus
  )
{
  UINT8           Buf[BUFFER_SIZE];
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32          Size;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(MethodStatus);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));

  //
  // set locking range values
  //
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, LockingRangeUid, TCG_UID_METHOD_SET));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x01));                        // "Values"
  ERROR_CHECK(TcgAddStartList(&CreateStruct));

  //
  // range start and range Length only apply to non-global locking ranges
  //
  if (LockingRangeUid != OPAL_LOCKING_SP_LOCKING_GLOBALRANGE) {
    ERROR_CHECK(TcgAddStartName(&CreateStruct));
    ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x03));                       // "RangeStart"
    ERROR_CHECK(TcgAddUINT64(&CreateStruct, RangeStart));
    ERROR_CHECK(TcgAddEndName(&CreateStruct));

    ERROR_CHECK(TcgAddStartName(&CreateStruct));
    ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x04));                       // "RangeLength"
    ERROR_CHECK(TcgAddUINT64(&CreateStruct, RangeLength));
    ERROR_CHECK(TcgAddEndName(&CreateStruct));
  }

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x05));                       // "ReadLockEnabled"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, ReadLockEnabled));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x06));                       // "WriteLockEnabled"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, WriteLockEnabled));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x07));                       // "ReadLocked"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, ReadLocked));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, 0x08));                       // "WriteLocked"
  ERROR_CHECK(TcgAddBOOLEAN(&CreateStruct, WriteLocked));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));

  ERROR_CHECK(TcgAddEndList(&CreateStruct));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, MethodStatus, 0));
  // Exit with success on method failure - user must inspect MethodStatus
  METHOD_STATUS_ERROR_CHECK(*MethodStatus, TcgResultSuccess);

  return TcgResultSuccess;
}

/**

  The function populates the CreateStruct with a payload that will retrieve the global locking range active key.
  It is intended to be called with a session that is already started with a valid credential.
  The function does not send the payload.

  @param[in]      Session        OPAL_SESSION to populate command for, needs ComId
  @param[in/out]  CreateStruct   Structure to populate with encoded TCG command
  @param[in/out]  Size           Size in bytes of the command created.

**/
TCG_RESULT
EFIAPI
OpalCreateRetrieveGlobalLockingRangeActiveKey(
  const OPAL_SESSION    *Session,
  TCG_CREATE_STRUCT     *CreateStruct,
  UINT32                *Size
  )
{
  NULL_CHECK(Session);
  NULL_CHECK(CreateStruct);
  NULL_CHECK(Size);

  // Retrieve the activekey in order to know which globalrange key to generate
  ERROR_CHECK(TcgStartComPacket(CreateStruct, Session->OpalBaseComId, Session->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(CreateStruct, Session->TperSessionId, Session->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(CreateStruct, OPAL_LOCKING_SP_LOCKING_GLOBALRANGE, TCG_UID_METHOD_GET));
  ERROR_CHECK(TcgStartParameters(CreateStruct));
  ERROR_CHECK(TcgAddStartList(CreateStruct));
  ERROR_CHECK(TcgAddStartName(CreateStruct));
  ERROR_CHECK(TcgAddUINT8(CreateStruct, TCG_CELL_BLOCK_START_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(CreateStruct, 0x0A));         // ActiveKey
  ERROR_CHECK(TcgAddEndName(CreateStruct));
  ERROR_CHECK(TcgAddStartName(CreateStruct));
  ERROR_CHECK(TcgAddUINT8(CreateStruct, TCG_CELL_BLOCK_END_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(CreateStruct, 0x0A));
  ERROR_CHECK(TcgAddEndName(CreateStruct));
  ERROR_CHECK(TcgAddEndList(CreateStruct));
  ERROR_CHECK(TcgEndParameters(CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(CreateStruct));
  ERROR_CHECK(TcgEndPacket(CreateStruct));
  ERROR_CHECK(TcgEndComPacket(CreateStruct, Size));

  return TcgResultSuccess;
}

/**

  The function acquires the activeKey specified for the Global Locking Range from the ParseStruct.

  @param[in]      ParseStruct    Structure that contains the device's response with the activekey
  @param[in/out]  ActiveKey      The UID of the active key retrieved

**/
TCG_RESULT
EFIAPI
OpalParseRetrieveGlobalLockingRangeActiveKey(
  TCG_PARSE_STRUCT  *ParseStruct,
  TCG_UID           *ActiveKey
  )
{
  UINT32 ColumnName;

  NULL_CHECK(ParseStruct);
  NULL_CHECK(ActiveKey);

  // parse response
  ERROR_CHECK(TcgGetNextStartList(ParseStruct));
  ERROR_CHECK(TcgGetNextStartList(ParseStruct));
  ERROR_CHECK(TcgGetNextStartName(ParseStruct));
  ERROR_CHECK(TcgGetNextUINT32(ParseStruct, &ColumnName));
  ERROR_CHECK(TcgGetNextTcgUid(ParseStruct, ActiveKey));
  ERROR_CHECK(TcgGetNextEndName(ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(ParseStruct));
  ERROR_CHECK(TcgGetNextEndOfData(ParseStruct));

  if (ColumnName != 0x0A) {
    DEBUG ((DEBUG_INFO, "Unexpected column name %u (exp 0x0A)\n", ColumnName));
    return TcgResultFailure;
  }

  if (*ActiveKey != OPAL_LOCKING_SP_K_AES_256_GLOBALRANGE_KEY && *ActiveKey != OPAL_LOCKING_SP_K_AES_128_GLOBALRANGE_KEY) {
    DEBUG ((DEBUG_INFO, "Unexpected gen key %u (exp %u or %u)\n", *ActiveKey, OPAL_LOCKING_SP_K_AES_256_GLOBALRANGE_KEY, OPAL_LOCKING_SP_K_AES_128_GLOBALRANGE_KEY));
    return TcgResultFailure;
  }

  return TcgResultSuccess;
}

/**

  The function retrieves the TryLimit column for the specified rowUid (authority).

  @param[in]      LockingSpSession    OPAL_SESSION with OPAL_UID_LOCKING_SP to retrieve try limit
  @param[in]      RowUid              Row UID of the Locking SP C_PIN table to retrieve TryLimit column
  @param[in/out]  TryLimit            Value from TryLimit column

**/
TCG_RESULT
EFIAPI
OpalGetTryLimit(
  OPAL_SESSION   *LockingSpSession,
  TCG_UID        RowUid,
  UINT32         *TryLimit
  )
{
  TCG_CREATE_STRUCT CreateStruct;
  TCG_PARSE_STRUCT  ParseStruct;
  UINT32            Size;
  UINT8             MethodStatus;
  UINT8             Buf[BUFFER_SIZE];
  UINT32            Col;

  NULL_CHECK(LockingSpSession);
  NULL_CHECK(TryLimit);

  ERROR_CHECK(TcgInitTcgCreateStruct(&CreateStruct, Buf, sizeof(Buf)));
  ERROR_CHECK(TcgStartComPacket(&CreateStruct, LockingSpSession->OpalBaseComId, LockingSpSession->ComIdExtension));
  ERROR_CHECK(TcgStartPacket(&CreateStruct, LockingSpSession->TperSessionId, LockingSpSession->HostSessionId, 0x0, 0x0, 0x0));
  ERROR_CHECK(TcgStartSubPacket(&CreateStruct, 0x0));
  ERROR_CHECK(TcgStartMethodCall(&CreateStruct, RowUid, TCG_UID_METHOD_GET));
  ERROR_CHECK(TcgStartParameters(&CreateStruct));
  ERROR_CHECK(TcgAddStartList(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_START_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_LOCKING_SP_C_PIN_TRYLIMIT_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddStartName(&CreateStruct));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, TCG_CELL_BLOCK_END_COLUMN_NAME));
  ERROR_CHECK(TcgAddUINT8(&CreateStruct, OPAL_LOCKING_SP_C_PIN_TRYLIMIT_COL));
  ERROR_CHECK(TcgAddEndName(&CreateStruct));
  ERROR_CHECK(TcgAddEndList(&CreateStruct));
  ERROR_CHECK(TcgEndParameters(&CreateStruct));
  ERROR_CHECK(TcgEndMethodCall(&CreateStruct));
  ERROR_CHECK(TcgEndSubPacket(&CreateStruct));
  ERROR_CHECK(TcgEndPacket(&CreateStruct));
  ERROR_CHECK(TcgEndComPacket(&CreateStruct, &Size));

  ERROR_CHECK(OpalPerformMethod(LockingSpSession, Size, Buf, sizeof(Buf), &ParseStruct, &MethodStatus, 0));
  METHOD_STATUS_ERROR_CHECK(MethodStatus, TcgResultFailure);

  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartList(&ParseStruct));
  ERROR_CHECK(TcgGetNextStartName(&ParseStruct));
  ERROR_CHECK(TcgGetNextUINT32(&ParseStruct, &Col));
  ERROR_CHECK(TcgGetNextUINT32(&ParseStruct, TryLimit));
  ERROR_CHECK(TcgGetNextEndName(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndList(&ParseStruct));
  ERROR_CHECK(TcgGetNextEndOfData(&ParseStruct));

  if (Col != OPAL_LOCKING_SP_C_PIN_TRYLIMIT_COL) {
    DEBUG ((DEBUG_INFO, "ERROR: got col %u, expected %u\n", Col, OPAL_LOCKING_SP_C_PIN_TRYLIMIT_COL));
    return TcgResultFailure;
  }

  return TcgResultSuccess;
}

/**

  Get the support attribute info.

  @param[in]      Session             OPAL_SESSION with OPAL_UID_LOCKING_SP to retrieve info.
  @param[out]     SupportedAttributes Return the support attribute info.
  @param[out]     OpalBaseComId       Return the base com id info.

**/
TCG_RESULT
EFIAPI
OpalGetSupportedAttributesInfo(
  IN  OPAL_SESSION                 *Session,
  OUT OPAL_DISK_SUPPORT_ATTRIBUTE  *SupportedAttributes,
  OUT UINT16                       *OpalBaseComId
  )
{
  UINT8                              Buffer[BUFFER_SIZE];
  TCG_SUPPORTED_SECURITY_PROTOCOLS   *SupportedProtocols;
  TCG_LEVEL0_DISCOVERY_HEADER        *DiscoveryHeader;
  OPAL_LEVEL0_FEATURE_DESCRIPTOR     *Feat;
  OPAL_LEVEL0_FEATURE_DESCRIPTOR     *Feat2;
  UINTN                              Size;
  UINTN                              Size2;

  NULL_CHECK(Session);
  NULL_CHECK(SupportedAttributes);
  NULL_CHECK(OpalBaseComId);

  ZeroMem(Buffer, BUFFER_SIZE);
  ZeroMem(SupportedAttributes, sizeof(OPAL_DISK_SUPPORT_ATTRIBUTE));
  ASSERT(sizeof(Buffer) >= sizeof(TCG_SUPPORTED_SECURITY_PROTOCOLS));

  //
  // Retrieve supported protocols verify security protocol 1 is supported
  //
  SupportedProtocols = (TCG_SUPPORTED_SECURITY_PROTOCOLS*) Buffer;

  //
  // Get list of supported protocols
  //
  if (OpalRetrieveSupportedProtocolList (Session, sizeof(TCG_SUPPORTED_SECURITY_PROTOCOLS), SupportedProtocols) == TcgResultFailure) {
    DEBUG ((DEBUG_INFO, "OpalRetrieveSupportedProtocolList failed\n"));
    return TcgResultFailure;
  }

  SupportedAttributes->Sp1 = TcgIsProtocolSupported (SupportedProtocols, TCG_OPAL_SECURITY_PROTOCOL_1);
  SupportedAttributes->Sp2 = TcgIsProtocolSupported (SupportedProtocols, TCG_OPAL_SECURITY_PROTOCOL_2);
  SupportedAttributes->SpIeee1667 = TcgIsProtocolSupported (SupportedProtocols, TCG_SECURITY_PROTOCOL_IEEE_1667);

  DEBUG ((DEBUG_INFO, "Supported Protocols: Sp1 %d Sp2: %d SpIeee1667 %d \n",
              SupportedAttributes->Sp1,
              SupportedAttributes->Sp2,
              SupportedAttributes->SpIeee1667
              ));

  //
  // Perform level 0 discovery and assign desired feature info to Opal Disk structure
  //
  ZeroMem (Buffer, BUFFER_SIZE);
  if (OpalRetrieveLevel0DiscoveryHeader (Session, BUFFER_SIZE, Buffer) == TcgResultFailure) {
    DEBUG ((DEBUG_INFO, "OpalRetrieveLevel0DiscoveryHeader failed\n"));
    return TcgResultFailure;
  }

  //
  // Check for required feature descriptors
  //
  DiscoveryHeader = (TCG_LEVEL0_DISCOVERY_HEADER*) Buffer;

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_OPAL_SSC_V2_0_0, &Size);
  SupportedAttributes->OpalSsc2 = (Feat != NULL);

  *OpalBaseComId = TCG_RESERVED_COMID;

  //
  // Check Opal SCC V2 has valid settings for SID C_PIN on revert
  //
  if (SupportedAttributes->OpalSsc2 && Size >= sizeof (OPAL_SSCV2_FEATURE_DESCRIPTOR)) {
    //
    // Want opposite polarity b/c Value is greater than a bit, but we only care about non-zero vs zero
    //
    SupportedAttributes->InitCpinIndicator = (Feat->OpalSscV2.InitialCPINSIDPIN == 0);
    SupportedAttributes->CpinUponRevert = (Feat->OpalSscV2.CPINSIDPINRevertBehavior == 0);
    DEBUG ((DEBUG_INFO, "Opal SSC V2 InitCpinIndicator %d  CpinUponRevert %d \n",
             SupportedAttributes->InitCpinIndicator,
             SupportedAttributes->CpinUponRevert
            ));
    *OpalBaseComId = SwapBytes16 (Feat->OpalSscV2.BaseComdIdBE);
  }

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_OPAL_SSC_LITE, &Size);
  SupportedAttributes->OpalSscLite = (Feat != NULL);

  if (Feat != NULL && Size >= sizeof (OPAL_SSCLITE_FEATURE_DESCRIPTOR)) {
    if (*OpalBaseComId == TCG_RESERVED_COMID) {
      //
      // Pin values used always match up with ComId used
      //
      *OpalBaseComId = SwapBytes16 (Feat->OpalSscLite.BaseComdIdBE);
      SupportedAttributes->InitCpinIndicator = (Feat->OpalSscV2.InitialCPINSIDPIN == 0);
      SupportedAttributes->CpinUponRevert = (Feat->OpalSscV2.CPINSIDPINRevertBehavior == 0);
      DEBUG ((DEBUG_INFO, "Opal SSC Lite InitCpinIndicator %d  CpinUponRevert %d \n",
               SupportedAttributes->InitCpinIndicator,
               SupportedAttributes->CpinUponRevert
              ));
    }
  }

  //
  // For some pyrite 2.0 device, it contains both pyrite 1.0 and 2.0 feature data.
  // so here try to get data from pyrite 2.0 feature data first.
  //
  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_PYRITE_SSC, &Size);
  Size2 = 0;
  Feat2 = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_PYRITE_SSC_V2_0_0, &Size2);
  if (Feat2 != NULL && Size2 >= sizeof (PYRITE_SSCV2_FEATURE_DESCRIPTOR)) {
    SupportedAttributes->PyriteSscV2 = TRUE;
    if (*OpalBaseComId == TCG_RESERVED_COMID) {
      *OpalBaseComId = SwapBytes16 (Feat2->PyriteSscV2.BaseComdIdBE);
      SupportedAttributes->InitCpinIndicator = (Feat2->PyriteSscV2.InitialCPINSIDPIN == 0);
      SupportedAttributes->CpinUponRevert = (Feat2->PyriteSscV2.CPINSIDPINRevertBehavior == 0);
      DEBUG ((DEBUG_INFO, "Pyrite SSC V2 InitCpinIndicator %d  CpinUponRevert %d \n",
               SupportedAttributes->InitCpinIndicator,
               SupportedAttributes->CpinUponRevert
              ));
    }
  } else {
    SupportedAttributes->PyriteSsc = (Feat != NULL);
    if (Feat != NULL && Size >= sizeof (PYRITE_SSC_FEATURE_DESCRIPTOR)) {
      if (*OpalBaseComId == TCG_RESERVED_COMID) {
        *OpalBaseComId = SwapBytes16 (Feat->PyriteSsc.BaseComdIdBE);
        SupportedAttributes->InitCpinIndicator = (Feat->PyriteSsc.InitialCPINSIDPIN == 0);
        SupportedAttributes->CpinUponRevert = (Feat->PyriteSsc.CPINSIDPINRevertBehavior == 0);
        DEBUG ((DEBUG_INFO, "Pyrite SSC InitCpinIndicator %d  CpinUponRevert %d \n",
                 SupportedAttributes->InitCpinIndicator,
                 SupportedAttributes->CpinUponRevert
                ));
      }
    }
  }

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_OPAL_SSC_V1_0_0, &Size);
  SupportedAttributes->OpalSsc1 = (Feat != NULL);
  if (Feat != NULL && Size >= sizeof (OPAL_SSCV1_FEATURE_DESCRIPTOR)) {
    if (*OpalBaseComId == TCG_RESERVED_COMID) {
      *OpalBaseComId = SwapBytes16 (Feat->OpalSscV1.BaseComdIdBE);
    }
  }

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_LOCKING, &Size);
  if (Feat != NULL && Size >= sizeof (TCG_LOCKING_FEATURE_DESCRIPTOR)) {
    SupportedAttributes->MediaEncryption = Feat->Locking.MediaEncryption;
    DEBUG ((DEBUG_INFO, "SupportedAttributes->MediaEncryption 0x%X \n", SupportedAttributes->MediaEncryption));
  }

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_BLOCK_SID, &Size);
  if (Feat != NULL && Size >= sizeof (TCG_BLOCK_SID_FEATURE_DESCRIPTOR)) {
    SupportedAttributes->BlockSid = TRUE;
    DEBUG ((DEBUG_INFO, "BlockSid Supported!!! Current Status is 0x%X \n", Feat->BlockSid.SIDBlockedState));
  } else {
    DEBUG ((DEBUG_INFO, "BlockSid Unsupported!!!"));
  }

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_DATA_REMOVAL, &Size);
  if (Feat != NULL && Size >= sizeof (DATA_REMOVAL_FEATURE_DESCRIPTOR)) {
    SupportedAttributes->DataRemoval = TRUE;
    DEBUG ((DEBUG_INFO, "DataRemoval Feature Supported!\n"));
    DEBUG ((DEBUG_INFO, "Operation Processing = 0x%x\n", Feat->DataRemoval.OperationProcessing));
    DEBUG ((DEBUG_INFO, "RemovalMechanism = 0x%x\n", Feat->DataRemoval.RemovalMechanism));
    DEBUG ((DEBUG_INFO, "BIT0 :: Format = 0x%x, Time = 0x%x\n", Feat->DataRemoval.FormatBit0, SwapBytes16 (Feat->DataRemoval.TimeBit0)));
    DEBUG ((DEBUG_INFO, "BIT1 :: Format = 0x%x, Time = 0x%x\n", Feat->DataRemoval.FormatBit1, SwapBytes16 (Feat->DataRemoval.TimeBit1)));
    DEBUG ((DEBUG_INFO, "BIT2 :: Format = 0x%x, Time = 0x%x\n", Feat->DataRemoval.FormatBit2, SwapBytes16 (Feat->DataRemoval.TimeBit2)));
    DEBUG ((DEBUG_INFO, "BIT3 :: Format = 0x%x, Time = 0x%x\n", Feat->DataRemoval.FormatBit3, SwapBytes16 (Feat->DataRemoval.TimeBit3)));
    DEBUG ((DEBUG_INFO, "BIT4 :: Format = 0x%x, Time = 0x%x\n", Feat->DataRemoval.FormatBit4, SwapBytes16 (Feat->DataRemoval.TimeBit4)));
  }

  DEBUG ((DEBUG_INFO, "Base COMID 0x%04X \n", *OpalBaseComId));

  return TcgResultSuccess;
}

/**

  Get the support attribute info.

  @param[in]      Session             OPAL_SESSION with OPAL_UID_LOCKING_SP to retrieve info.
  @param[in/out]  LockingFeature      Return the Locking info.

**/
TCG_RESULT
EFIAPI
OpalGetLockingInfo(
  OPAL_SESSION                     *Session,
  TCG_LOCKING_FEATURE_DESCRIPTOR   *LockingFeature
  )
{
  UINT8                              Buffer[BUFFER_SIZE];
  TCG_LEVEL0_DISCOVERY_HEADER        *DiscoveryHeader;
  OPAL_LEVEL0_FEATURE_DESCRIPTOR     *Feat;
  UINTN                              Size;

  NULL_CHECK(Session);
  NULL_CHECK(LockingFeature);

  ZeroMem(Buffer, BUFFER_SIZE);
  ASSERT(sizeof(Buffer) >= sizeof(TCG_SUPPORTED_SECURITY_PROTOCOLS));

  if (OpalRetrieveLevel0DiscoveryHeader (Session, BUFFER_SIZE, Buffer) == TcgResultFailure) {
    DEBUG ((DEBUG_INFO, "OpalRetrieveLevel0DiscoveryHeader failed\n"));
    return TcgResultFailure;
  }
  DiscoveryHeader = (TCG_LEVEL0_DISCOVERY_HEADER*) Buffer;

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, TCG_FEATURE_LOCKING, &Size);
  if (Feat != NULL && Size >= sizeof (TCG_LOCKING_FEATURE_DESCRIPTOR)) {
    CopyMem (LockingFeature, &Feat->Locking, sizeof (TCG_LOCKING_FEATURE_DESCRIPTOR));
  }

  return TcgResultSuccess;
}

/**

  Get the descriptor for the specific feature code.

  @param[in]      Session             OPAL_SESSION with OPAL_UID_LOCKING_SP to retrieve info.
  @param[in]      FeatureCode         The feature code user request.
  @param[in, out] DataSize            The data size.
  @param[out]     Data                The data buffer used to save the feature descriptor.

**/
TCG_RESULT
OpalGetFeatureDescriptor (
  IN     OPAL_SESSION              *Session,
  IN     UINT16                    FeatureCode,
  IN OUT UINTN                     *DataSize,
  OUT    VOID                      *Data
  )
{
  UINT8                              Buffer[BUFFER_SIZE];
  TCG_LEVEL0_DISCOVERY_HEADER        *DiscoveryHeader;
  OPAL_LEVEL0_FEATURE_DESCRIPTOR     *Feat;
  UINTN                              Size;

  NULL_CHECK(Session);
  NULL_CHECK(DataSize);
  NULL_CHECK(Data);

  ZeroMem(Buffer, BUFFER_SIZE);
  ASSERT(sizeof(Buffer) >= sizeof(TCG_SUPPORTED_SECURITY_PROTOCOLS));

  if (OpalRetrieveLevel0DiscoveryHeader (Session, BUFFER_SIZE, Buffer) == TcgResultFailure) {
    DEBUG ((DEBUG_INFO, "OpalRetrieveLevel0DiscoveryHeader failed\n"));
    return TcgResultFailure;
  }
  DiscoveryHeader = (TCG_LEVEL0_DISCOVERY_HEADER*) Buffer;

  Size = 0;
  Feat = (OPAL_LEVEL0_FEATURE_DESCRIPTOR*) TcgGetFeature (DiscoveryHeader, FeatureCode, &Size);
  if (Feat != NULL) {
    if (Size > *DataSize) {
      *DataSize = Size;
      return TcgResultFailureBufferTooSmall;
    }

    *DataSize = Size;
    CopyMem (Data, Feat, Size);
  }

  return TcgResultSuccess;
}

/**

  The function determines whether or not all of the requirements for the Opal Feature (not full specification)
  are met by the specified device.

  @param[in]      SupportedAttributes     Opal device attribute.

**/
BOOLEAN
EFIAPI
OpalFeatureSupported(
  OPAL_DISK_SUPPORT_ATTRIBUTE      *SupportedAttributes
  )
{
  NULL_CHECK(SupportedAttributes);

  if (SupportedAttributes->Sp1 == 0) {
    return FALSE;
  }

  if (SupportedAttributes->OpalSscLite == 0 &&
      SupportedAttributes->OpalSsc1 == 0 &&
      SupportedAttributes->OpalSsc2 == 0 &&
      SupportedAttributes->PyriteSsc == 0 &&
      SupportedAttributes->PyriteSscV2 == 0
     ) {
    return FALSE;
  }

  return TRUE;
}

/**

  The function returns whether or not the device is Opal Enabled.
  TRUE means that the device is partially or fully locked.
  This will perform a Level 0 Discovery and parse the locking feature descriptor

  @param[in]      SupportedAttributes     Opal device attribute.
  @param[in]      LockingFeature          Opal device locking status.


**/
BOOLEAN
EFIAPI
OpalFeatureEnabled(
  OPAL_DISK_SUPPORT_ATTRIBUTE      *SupportedAttributes,
  TCG_LOCKING_FEATURE_DESCRIPTOR   *LockingFeature
  )
{
  NULL_CHECK(SupportedAttributes);
  NULL_CHECK(LockingFeature);

  if (!OpalFeatureSupported (SupportedAttributes)) {
    return FALSE;
  }

  if (LockingFeature->LockingSupported && LockingFeature->LockingEnabled) {
    return TRUE;
  }

  return FALSE;
}

/**

  The function returns whether or not the device is Opal Locked.
  TRUE means that the device is partially or fully locked.
  This will perform a Level 0 Discovery and parse the locking feature descriptor

  @param[in]      SupportedAttributes     Opal device attribute.
  @param[in]      LockingFeature          Opal device locking status.

**/
BOOLEAN
OpalDeviceLocked(
  OPAL_DISK_SUPPORT_ATTRIBUTE      *SupportedAttributes,
  TCG_LOCKING_FEATURE_DESCRIPTOR   *LockingFeature
  )
{
  NULL_CHECK(SupportedAttributes);
  NULL_CHECK(LockingFeature);

  if (!OpalFeatureEnabled (SupportedAttributes, LockingFeature)) {
    return FALSE;
  }

  return LockingFeature->Locked;
}

