/** @file

  Serial instance of Manageability Transport Library

  Copyright (c) 2024, ARM Limited. All rights reserved.<BR>
  Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Uefi.h>
#include <IndustryStandard/Mctp.h>
#include <Library/SerialPortLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseLib.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/ManageabilityTransportHelperLib.h>
#include <Library/ManageabilityTransportMctpLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/TimerLib.h>

#include "ManageabilityTransportSerial.h"

extern MANAGEABILITY_TRANSPORT_SERIAL_HARDWARE_INFO  mSerialHardwareInfo;
extern MANAGEABILITY_TRANSPORT_SERIAL                *mSingleSessionToken;

IPMI_SERIAL_SPECIAL_CHAR  mIpmiSerialSpecialChar[] = {
  { BASIC_MODE_START,     BASIC_MODE_START_ENCODED_BYTE     }, /* start */
  { BASIC_MODE_STOP,      BASIC_MODE_STOP_ENCODED_BYTE      }, /* stop */
  { BASIC_MODE_HANDSHAKE, BASIC_MODE_HANDSHAKE_ENCODED_BYTE }, /* packet handshake */
  { BASIC_MODE_ESCAPE,    BASIC_MODE_ESCAPE_ENCODED_BYTE    }, /* data escape */
  { BASIC_MODE_ESC_CHAR,  BASIC_MODE_ESC_CHAR_ENCODED_BYTE  }  /* escape */
};

UINT8  mNumberOfEscapeCharacters = ARRAY_SIZE (mIpmiSerialSpecialChar);

/**
  Return escaped character for the given one

  @param[in]      Character           Character data.

  @retval         Character           Return escaped character if it is.
                                      Otherwise, return the given one.
**/
static
UINT8
IpmiSerialGetEscapedCharacter (
  UINT8  Character
  )
{
  UINT8  Index;

  for (Index = 0; Index < mNumberOfEscapeCharacters; Index++) {
    if (mIpmiSerialSpecialChar[Index].Character == Character) {
      return mIpmiSerialSpecialChar[Index].Escape;
    }
  }

  return Character;
}

/**
  Return unescaped character for the given one

  @param[in]      Character           Character data.

  @retval         Character           Return unescaped character if it is.
                                      Otherwise, return the given one.
**/
static
UINT8
IpmiSerialGetUnescapedCharacter (
  UINT8  Character
  )
{
  UINT8  Index;

  for (Index = 0; Index < mNumberOfEscapeCharacters; Index++) {
    if (mIpmiSerialSpecialChar[Index].Escape == Character) {
      return mIpmiSerialSpecialChar[Index].Character;
    }
  }

  return Character;
}

/**
  Parse IPMI Serial incoming data in basic mode format to IPMI message

  @param[in]        Buffer                Data buffer with data byte escaped.
  @param[in]        BufferSize            Data buffer size.
  @param[out]       ResponseData          Data buffer without data byte escaped.
  @param[in, out]   ResponseDataSize      Data buffer size.

  @retval           EFI_SUCCESS           The command byte stream was successfully
                                          submit to the device and a response was
                                          successfully received.
  @retval           EFI_PROTOCOL_ERROR    The command byte stream cannot encode
                                          successfully.
  @retval           EFI_OUT_OF_RESOURCES  The resource allocation is out of resource or
                                          data size error.
**/
EFI_STATUS
IpmiSerialParseIncomingBuffer (
  IN        UINT8   *Buffer,
  IN        UINT32  BufferSize,
  OUT       UINT8   *ResponseData,
  IN OUT    UINT32  *ResponseDataSize
  )
{
  UINT8   *ResponsePtr;
  UINT8   Character;
  UINT8   UnescapedCharacter;
  UINT32  Count;
  UINT32  Index;
  UINT8   CtxState;
  UINT8   CtxEscape;

  if ((BufferSize == 0) || (Buffer == NULL)) {
    DEBUG ((DEBUG_ERROR, "Buffer Data insufficient. \n"));
    return EFI_OUT_OF_RESOURCES;
  }

  if ((ResponseDataSize == NULL) ||
      (BufferSize > *ResponseDataSize) ||
      (ResponseData == NULL))
  {
    DEBUG ((DEBUG_ERROR, "Response Data insufficient. \n"));
    return EFI_OUT_OF_RESOURCES;
  }

  CtxState    = MSG_IDLE;
  CtxEscape   = 0;
  ResponsePtr = ResponseData;
  Count       = 0;
  for (Index = 0; Index < BufferSize; Index++) {
    Character = Buffer[Index];
    if ((Character == BASIC_MODE_START) && (CtxState == MSG_IDLE)) {
      // START
      CtxState  = MSG_IN_PROGRESS;
      CtxEscape = 0;
    } else if (CtxState != MSG_IN_PROGRESS) {
      continue;
    } else if (CtxEscape != 0) {
      UnescapedCharacter = IpmiSerialGetUnescapedCharacter (Character);

      if (UnescapedCharacter == Character) {
        // error, then reset
        CtxState = MSG_IDLE;
        continue;
      }

      *ResponsePtr++ = UnescapedCharacter;
      Count++;
      CtxEscape = 0;
    } else if (Character == BASIC_MODE_ESCAPE) {
      CtxEscape = 1;
      continue;
    } else if (Character == BASIC_MODE_STOP) {
      // STOP
      CtxState          = MSG_IDLE;
      *ResponseDataSize = Count;
      return EFI_SUCCESS;
    } else if (Character == BASIC_MODE_HANDSHAKE) {
      // just skip it
      continue;
    } else if (CtxState == MSG_IN_PROGRESS) {
      *ResponsePtr++ = Character;
      Count++;
    }
  }

  return EFI_PROTOCOL_ERROR;
}

/**
  This function writes/sends data to the Serial port.
  Basic mode message fields in IPMI spec 2.0 Figure 14.

  @param[in]      TransmitHeader        Serial packet header.
  @param[in]      TransmitHeaderSize    Serial packet header size in byte.
  @param[in]      TransmitTrailer       Serial packet trailer.
  @param[in]      TransmitTrailerSize   Serial packet trailer size in byte.
  @param[in]      RequestData           Command Request Data, could be NULL.
                                        RequestDataSize must be zero, if RequestData
                                        is NULL.
  @param[in]      RequestDataSize       Size of Command Request Data.

  @retval         EFI_SUCCESS           The command byte stream was successfully
                                        submit to the device and a response was
                                        successfully received.
  @retval         EFI_NOT_FOUND         The command was not successfully sent to the
                                        device or a response was not successfully
                                        received from the device.
  @retval         EFI_NOT_READY         Ipmi Device is not ready for Ipmi command
                                        access.
  @retval         EFI_DEVICE_ERROR      Ipmi Device hardware error.
  @retval         EFI_TIMEOUT           The command time out.
  @retval         EFI_UNSUPPORTED       The command was not successfully sent to
                                        the device.
  @retval         EFI_OUT_OF_RESOURCES  The resource allocation is out of resource or
                                        data size error.
**/
EFI_STATUS
SerialTransportWrite (
  IN  MANAGEABILITY_TRANSPORT_HEADER   TransmitHeader,
  IN  UINT16                           TransmitHeaderSize,
  IN  MANAGEABILITY_TRANSPORT_TRAILER  TransmitTrailer OPTIONAL,
  IN  UINT16                           TransmitTrailerSize,
  IN  UINT8                            *RequestData OPTIONAL,
  IN  UINT32                           RequestDataSize
  )
{
  EFI_STATUS          Status;
  IPMI_SERIAL_HEADER  *HeaderPtr;
  UINT8               *BufferPtr;
  UINT8               *Buffer;
  UINT32              BufferLength;
  UINT8               *Request;
  UINT32              RequestLength;
  UINT8               RetryCount;
  UINT32              Index;
  UINT8               Character;
  UINT8               EscapedCharacterCount;
  UINT8               NetFunction;

  // Validation on RequestData and RequestDataSize.
  if (((RequestData == NULL) && (RequestDataSize != 0)) ||
      ((RequestData != NULL) && (RequestDataSize == 0))
      )
  {
    DEBUG ((DEBUG_ERROR, "%a: Mismatched values of RequestData or RequestDataSize.\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  // Validation on TransmitHeader and TransmitHeaderSize.
  if (((TransmitHeader == NULL) && (TransmitHeaderSize != 0)) ||
      ((TransmitHeader != NULL) && (TransmitHeaderSize == 0))
      )
  {
    DEBUG ((DEBUG_ERROR, "%a: Mismatched values of TransmitHeader or TransmitHeaderSize.\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  // Validation on TransmitHeader and TransmitHeaderSize.
  if (((TransmitTrailer == NULL) && (TransmitTrailerSize != 0)) ||
      ((TransmitTrailer != NULL) && (TransmitTrailerSize == 0))
      )
  {
    DEBUG ((DEBUG_ERROR, "%a: Mismatched values of TransmitTrailer or TransmitTrailerSize.\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  NetFunction = ((MANAGEABILITY_IPMI_TRANSPORT_HEADER *)TransmitHeader)->NetFn;
  ASSERT (NetFunction <= MANAGEABILITY_IPMI_NET_FUNC_MAX);

  BufferLength = RequestDataSize + IPMI_SERIAL_MIN_REQUEST_LENGTH;
  Buffer       = AllocateZeroPool (BufferLength);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  HeaderPtr = (IPMI_SERIAL_HEADER *)Buffer;

  // Fill IPMI Serial basic format data bytes
  HeaderPtr->ResponderAddress  = IPMI_SERIAL_RS_ADDRESS;
  HeaderPtr->ResponderNetFnLun = (UINT8)((NetFunction << 2) | (IPMI_SERIAL_RS_LUN & IPMI_MAX_LUN));
  HeaderPtr->CheckSum          = CalculateCheckSum8 (&Buffer[0], IPMI_SERIAL_CONNECTION_HEADER_LENGTH - 1);
  HeaderPtr->RequesterAddress  = IPMI_SERIAL_RQ_ADDRESS;
  HeaderPtr->RequesterSeqLun   = IPMI_SERIAL_RQ_LUN;
  HeaderPtr->Command           = ((MANAGEABILITY_IPMI_TRANSPORT_HEADER *)TransmitHeader)->Command;

  // Calculate data checksum
  if ((RequestData != NULL) && (RequestDataSize > 0)) {
    CopyMem (HeaderPtr->Data, RequestData, RequestDataSize);
    Buffer[BufferLength - 1] = CalculateCheckSum8 (
                                 &Buffer[IPMI_SERIAL_CONNECTION_HEADER_LENGTH],
                                 RequestDataSize + IPMI_SERIAL_REQUEST_DATA_HEADER_LENGTH
                                 );
  } else {
    Buffer[BufferLength - 1] = CalculateCheckSum8 (&Buffer[IPMI_SERIAL_CONNECTION_HEADER_LENGTH], IPMI_SERIAL_REQUEST_DATA_HEADER_LENGTH);
  }

  // Calculate escaped character count
  EscapedCharacterCount = 0;
  for (Index = 0; Index < BufferLength; Index++) {
    if (IpmiSerialGetEscapedCharacter (Buffer[Index]) != Buffer[Index]) {
      EscapedCharacterCount++;
    }
  }

  // Allocate memory buffer include escaped characters
  RequestLength = BufferLength + EscapedCharacterCount + 2; // start + stop byte
  Request       = AllocateZeroPool (RequestLength);
  if (Request == NULL) {
    DEBUG ((DEBUG_ERROR, "Out Of Resource \n"));
    return EFI_OUT_OF_RESOURCES;
  }

  BufferPtr = Request;

  // start character
  *BufferPtr++ = BASIC_MODE_START;

  // Fill the request data with data byte escaped
  for (Index = 0; Index < BufferLength; Index++) {
    Character = IpmiSerialGetEscapedCharacter (Buffer[Index]);
    if (Character != Buffer[Index]) {
      *BufferPtr++ = BASIC_MODE_ESCAPE;
    }

    *BufferPtr++ = Character;
  }

  // stop character
  *BufferPtr++ = BASIC_MODE_STOP;

  //
  // Write Request
  //
  RetryCount = 0;
  while (TRUE) {
    Status = SerialPortWrite (Request, RequestLength);
    if (Status == RequestLength) {
      break;
    }

    if (++RetryCount > IPMI_SERIAL_RETRY_COUNT) {
      DEBUG ((DEBUG_ERROR, "%a: Write Request error %r\n", __func__, Status));
      break;
    }

    MicroSecondDelay (IPMI_SERIAL_REQUEST_RETRY_INTERVAL);
  }

  FreePool (Buffer);
  FreePool (Request);

  return EFI_SUCCESS;
}

/**
  Read Serial response from BMC.

  @param[out]        ResponseData      Command Response Data.
  @param[in, out]    ResponseDataSize  Size of Command Response Data.

  @retval EFI_SUCCESS                  The command byte stream was successfully submit to the device
                                       and a response was successfully received.
  @retval EFI_PROTOCOL_ERROR           The command byte stream cannot encode
                                       successfully.
**/
EFI_STATUS
SerialReadResponse (
  OUT    UINT8   *ResponseData,
  IN OUT UINT32  *ResponseDataSize
  )
{
  EFI_STATUS  Status;
  UINT8       Character;
  UINT8       Buffer[IPMI_SERIAL_MAXIMUM_PACKET_SIZE_IN_BYTES];
  UINT32      BufferSize;
  UINT8       ReadBuffer[IPMI_SERIAL_MAXIMUM_PACKET_SIZE_IN_BYTES];
  UINT32      ReadBytes;
  UINT32      RetryCount;

  ASSERT (ResponseData != NULL && ResponseDataSize != NULL);

  //
  // Keep polling until retryCount timeout
  //
  RetryCount = 0;
  ReadBytes  = 0;
  while (ReadBytes < IPMI_SERIAL_MAXIMUM_PACKET_SIZE_IN_BYTES) {
    if (++RetryCount > IPMI_SERIAL_RETRY_COUNT) {
      break;
    }

    while (SerialPortPoll ()) {
      SerialPortRead (&Character, sizeof (Character));
      ReadBuffer[ReadBytes++] = Character;
    }

    MicroSecondDelay (IPMI_SERIAL_RESPONSE_RETRY_INTERVAL);
  }

  // Parse IPMI Serial format request data
  BufferSize = sizeof (Buffer);
  Status     = IpmiSerialParseIncomingBuffer (
                 ReadBuffer,
                 ReadBytes,
                 Buffer,
                 &BufferSize
                 );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Parse Response Error\n"));
    return Status;
  }

  // Header checksum verify
  if (CalculateCheckSum8 (Buffer, IPMI_SERIAL_CONNECTION_HEADER_LENGTH) != 0) {
    DEBUG ((DEBUG_ERROR, "Bad checksum - header\n"));
    return EFI_PROTOCOL_ERROR;
  }

  // Data checksum verify
  if (CalculateCheckSum8 (
        &Buffer[IPMI_SERIAL_CONNECTION_HEADER_LENGTH],
        BufferSize - IPMI_SERIAL_CONNECTION_HEADER_LENGTH
        ) != 0)
  {
    DEBUG ((DEBUG_ERROR, "Bad checksum - data byte\n"));
    return EFI_PROTOCOL_ERROR;
  }

  *ResponseDataSize = BufferSize - (sizeof (IPMI_SERIAL_HEADER) + 1); // Header (6) + Checksum (1)
  CopyMem (ResponseData, &Buffer[sizeof (IPMI_SERIAL_HEADER)], *ResponseDataSize);

  return Status;
}

/**
  This function sends/receives data from Serial port.

  @param [out       DataBytes             Buffer to hold the read Data.
  @param [in, out]  Length                Number of Bytes read from Serial port.
  @retval           EFI_SUCCESS           The command byte stream was
                                          successfully submit to the device and
                                          a response was successfully received.
  @retval           EFI_NOT_FOUND         The command was not successfully sent
                                          to the device or a response was not
                                          successfully received from the
                                          device.
  @retval           EFI_NOT_READY         Ipmi Device is not ready for Ipmi
                                          command access.
  @retval           EFI_OUT_OF_RESOURCES  The resource allocation is out of
                                          resource or data size error.
  @retval           EFI_PROTOCOL_ERROR    The command byte stream cannot encode
                                          successfully.
**/
EFI_STATUS
SerialTransportRead (
  OUT    UINT8   *DataByte,
  IN OUT UINT32  *Length
  )
{
  EFI_STATUS  Status;

  if ((DataByte == NULL) || (*Length == 0)) {
    DEBUG ((DEBUG_ERROR, "%a: Either DataByte is NULL or Length is 0.\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  Status = SerialReadResponse (DataByte, Length);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Read Response error %r\n", __func__, Status));
    *Length = 0;
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  This function checks the Serial response data according to
  manageability protocol.

  @param[in]      ResponseData        Pointer to response data.
  @param[in]      ResponseDataSize    Size of response data.
  @param[out]     AdditionalStatus    Pointer to receive the additional status.

  @retval         EFI_SUCCESS         Serial response header is checked without error
  @retval         EFI_DEVICE_ERROR    Serial response header has problem.
**/
EFI_STATUS
SerialCheckResponseData (
  IN UINT8                                       *ResponseData,
  IN UINT32                                      ResponseDataSize,
  OUT MANAGEABILITY_TRANSPORT_ADDITIONAL_STATUS  *AdditionalStatus
  )
{
  EFI_STATUS  Status;
  CHAR16      *CompletionCodeStr;

  Status            = EFI_SUCCESS;
  *AdditionalStatus = MANAGEABILITY_TRANSPORT_ADDITIONAL_STATUS_NO_ERRORS;
  if (CompareGuid (&gManageabilityProtocolIpmiGuid, mSingleSessionToken->Token.ManageabilityProtocolSpecification)) {
    //
    // For IPMI Serial
    // Check and print Completion Code
    //
    Status = IpmiHelperCheckCompletionCode (*ResponseData, &CompletionCodeStr, AdditionalStatus);
    if (!EFI_ERROR (Status)) {
      DEBUG ((DEBUG_MANAGEABILITY_INFO, "Cc: %02x %s.\n", *((UINT8 *)ResponseData), CompletionCodeStr));
    } else if (Status == EFI_NOT_FOUND) {
      DEBUG ((DEBUG_ERROR, "Cc: %02x not defined in IpmiCompletionCodeMapping or invalid.\n", *((UINT8 *)ResponseData)));
    }
  }

  return Status;
}

/**
  This service communicates with BMC using Serial protocol.

  @param[in]      TransmitHeader        Serial packet header.
  @param[in]      TransmitHeaderSize    Serial packet header size in byte.
  @param[in]      TransmitTrailer       Serial packet trailer.
  @param[in]      TransmitTrailerSize   Serial packet trailer size in byte.
  @param[in]      RequestData           Command Request Data.
  @param[in]      RequestDataSize       Size of Command Request Data.
  @param[out]     ResponseData          Command Response Data. The completion
                                        code is the first byte of response
                                        data.
  @param[in, out] ResponseDataSize      Size of Command Response Data.
  @param[out]     AdditionalStatus       Additional status of this transaction.

  @retval         EFI_SUCCESS           The command byte stream was
                                        successfully submit to the device and a
                                        response was successfully received.
  @retval         EFI_NOT_FOUND         The command was not successfully sent
                                        to the device or a response was not
                                        successfully received from the device.
  @retval         EFI_NOT_READY         Ipmi Device is not ready for Ipmi
                                        command access.
  @retval         EFI_DEVICE_ERROR      Ipmi Device hardware error.
  @retval         EFI_TIMEOUT           The command time out.
  @retval         EFI_UNSUPPORTED       The command was not successfully sent to
                                        the device.
  @retval         EFI_OUT_OF_RESOURCES  The resource allocation is out of
                                        resource or data size error.
**/
EFI_STATUS
EFIAPI
SerialTransportSendCommand (
  IN  MANAGEABILITY_TRANSPORT_HEADER              TransmitHeader OPTIONAL,
  IN  UINT16                                      TransmitHeaderSize,
  IN  MANAGEABILITY_TRANSPORT_TRAILER             TransmitTrailer OPTIONAL,
  IN  UINT16                                      TransmitTrailerSize,
  IN  UINT8                                       *RequestData OPTIONAL,
  IN  UINT32                                      RequestDataSize,
  OUT UINT8                                       *ResponseData OPTIONAL,
  IN  OUT UINT32                                  *ResponseDataSize OPTIONAL,
  OUT  MANAGEABILITY_TRANSPORT_ADDITIONAL_STATUS  *AdditionalStatus
  )
{
  EFI_STATUS  Status;

  if ((RequestData != NULL) && (RequestDataSize == 0)) {
    DEBUG ((DEBUG_ERROR, "%a: Mismatched values of RequestData and RequestDataSize\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  if ((ResponseData != NULL) && ((ResponseDataSize != NULL) && (*ResponseDataSize == 0))) {
    DEBUG ((DEBUG_ERROR, "%a: Mismatched values of ResponseData and ResponseDataSize\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  if (AdditionalStatus == NULL) {
    DEBUG ((DEBUG_ERROR, "%a: AdditionalStatus is NULL.\n", __func__));
    return EFI_INVALID_PARAMETER;
  }

  // Print out the request payloads.
  if ((TransmitHeader != NULL) && (TransmitHeaderSize != 0)) {
    HelperManageabilityDebugPrint ((VOID *)TransmitHeader, (UINT32)TransmitHeaderSize, "Serial Transmit Header:\n");
  }

  if (RequestData != NULL) {
    HelperManageabilityDebugPrint ((VOID *)RequestData, RequestDataSize, "Serial Request Data:\n");
  }

  if ((TransmitTrailer != NULL) && (TransmitTrailerSize != 0)) {
    HelperManageabilityDebugPrint ((VOID *)TransmitTrailer, (UINT32)TransmitTrailerSize, "Serial Transmit Trailer:\n");
  }

  if ((TransmitHeader != NULL) || (RequestData != NULL)) {
    Status = SerialTransportWrite (
               TransmitHeader,
               TransmitHeaderSize,
               TransmitTrailer,
               TransmitTrailerSize,
               RequestData,
               RequestDataSize
               );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Serial Write Failed with Status(%r)\n", Status));
      return Status;
    }
  }

  if ((ResponseData != NULL) && (ResponseDataSize != NULL) && (*ResponseDataSize != 0)) {
    Status = SerialTransportRead (ResponseData, ResponseDataSize);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Serial response read Failed with Status(%r)\n", Status));
    }

    // Print out the response payloads.
    if (*ResponseDataSize != 0) {
      HelperManageabilityDebugPrint ((VOID *)ResponseData, (UINT32)*ResponseDataSize, "Serial Response Data:\n");
      Status = SerialCheckResponseData (ResponseData, *ResponseDataSize, AdditionalStatus);
    } else {
      DEBUG ((DEBUG_ERROR, "No response, can't determine Completion Code.\n"));
    }
  } else {
    *ResponseDataSize = 0;
  }

  return Status;
}
