blob: 9aedd666a8c1a600275a4fc7f7613d33c1c22711 [file]
/** @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;
}