blob: 97d30c802df4f880d79cac4ac519441f21c54a73 [file] [log] [blame]
/** @file
Install Serial IO Protocol that layers on top of a Debug Communication Library instance.
Copyright (c) 2012 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "DxeDebugAgentLib.h"
//
// Serial I/O Protocol Interface definitions.
//
/**
Reset serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@retval EFI_SUCCESS Reset successfully.
**/
EFI_STATUS
EFIAPI
SerialReset (
IN EFI_SERIAL_IO_PROTOCOL *This
);
/**
Set new attributes to a serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in] BaudRate The baudrate of the serial device.
@param[in] ReceiveFifoDepth The depth of receive FIFO buffer.
@param[in] Timeout The request timeout for a single char.
@param[in] Parity The type of parity used in serial device.
@param[in] DataBits Number of databits used in serial device.
@param[in] StopBits Number of stopbits used in serial device.
@retval EFI_SUCCESS The new attributes were set.
@retval EFI_INVALID_PARAMETER One or more attributes have an unsupported value.
@retval EFI_DEVICE_ERROR The serial device is not functioning correctly (no return).
**/
EFI_STATUS
EFIAPI
SerialSetAttributes (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN UINT64 BaudRate,
IN UINT32 ReceiveFifoDepth,
IN UINT32 Timeout,
IN EFI_PARITY_TYPE Parity,
IN UINT8 DataBits,
IN EFI_STOP_BITS_TYPE StopBits
);
/**
Set Control Bits.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in] Control Control bits that can be settable.
@retval EFI_SUCCESS New Control bits were set successfully.
@retval EFI_UNSUPPORTED The Control bits wanted to set are not supported.
**/
EFI_STATUS
EFIAPI
SerialSetControl (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN UINT32 Control
);
/**
Get ControlBits.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[out] Control Control signals of the serial device.
@retval EFI_SUCCESS Get Control signals successfully.
**/
EFI_STATUS
EFIAPI
SerialGetControl (
IN EFI_SERIAL_IO_PROTOCOL *This,
OUT UINT32 *Control
);
/**
Write the specified number of bytes to serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in, out] BufferSize On input the size of Buffer, on output the amount of
data actually written.
@param[in] Buffer The buffer of data to write.
@retval EFI_SUCCESS The data were written successfully.
@retval EFI_DEVICE_ERROR The device reported an error.
@retval EFI_TIMEOUT The write operation was stopped due to timeout.
**/
EFI_STATUS
EFIAPI
SerialWrite (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
);
/**
Read the specified number of bytes from serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in, out] BufferSize On input the size of Buffer, on output the amount of
data returned in buffer.
@param[out] Buffer The buffer to return the data into.
@retval EFI_SUCCESS The data were read successfully.
@retval EFI_DEVICE_ERROR The device reported an error.
@retval EFI_TIMEOUT The read operation was stopped due to timeout.
**/
EFI_STATUS
EFIAPI
SerialRead (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
);
//
// Serial Driver Defaults
//
#define SERIAL_PORT_DEFAULT_RECEIVE_FIFO_DEPTH 1
#define SERIAL_PORT_DEFAULT_TIMEOUT 1000000
#define SERIAL_PORT_DEFAULT_CONTROL_MASK 0
#define SERIAL_PORT_LOOPBACK_BUFFER_FULL BIT8
//
// EFI_SERIAL_IO_MODE instance
//
EFI_SERIAL_IO_MODE mSerialIoMode = {
SERIAL_PORT_DEFAULT_CONTROL_MASK,
SERIAL_PORT_DEFAULT_TIMEOUT,
0, // default BaudRate
SERIAL_PORT_DEFAULT_RECEIVE_FIFO_DEPTH,
0, // default DataBits
0, // default Parity
0 // default StopBits
};
//
// EFI_SERIAL_IO_PROTOCOL instance
//
EFI_SERIAL_IO_PROTOCOL mSerialIo = {
SERIAL_IO_INTERFACE_REVISION,
SerialReset,
SerialSetAttributes,
SerialSetControl,
SerialGetControl,
SerialWrite,
SerialRead,
&mSerialIoMode
};
//
// Serial IO Device Path definition
//
typedef struct {
VENDOR_DEVICE_PATH VendorDevicePath;
UART_DEVICE_PATH UartDevicePath;
EFI_DEVICE_PATH_PROTOCOL EndDevicePath;
} SERIAL_IO_DEVICE_PATH;
//
// Serial IO Device Patch instance
//
SERIAL_IO_DEVICE_PATH mSerialIoDevicePath = {
{
{
HARDWARE_DEVICE_PATH,
HW_VENDOR_DP,
{
(UINT8)(sizeof (VENDOR_DEVICE_PATH)),
(UINT8)((sizeof (VENDOR_DEVICE_PATH)) >> 8)
}
},
EFI_DEBUG_AGENT_GUID,
},
{
{
MESSAGING_DEVICE_PATH,
MSG_UART_DP,
{
(UINT8)(sizeof (UART_DEVICE_PATH)),
(UINT8)((sizeof (UART_DEVICE_PATH)) >> 8)
}
},
0,
0, // default BaudRate
0, // default DataBits
0, // default Parity
0, // default StopBits
},
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{
END_DEVICE_PATH_LENGTH,
0
}
}
};
#define DEBUG_SERIAL_IO_FIFO_DEPTH 10
//
// Data buffer for Terminal input character and Debug Symbols.
// The depth is DEBUG_SERIAL_IO_FIFO_DEPTH.
// Fields:
// First UINT8: The index of the first data in array Data[].
// Last UINT8: The index, which you can put a new data into array Data[].
// Surplus UINT8: Identify how many data you can put into array Data[].
// Data[] UINT8: An array, which used to store data.
//
typedef struct {
UINT8 First;
UINT8 Last;
UINT8 Surplus;
UINT8 Data[DEBUG_SERIAL_IO_FIFO_DEPTH];
} DEBUG_SERIAL_FIFO;
//
// Global Variables
//
EFI_HANDLE mSerialIoHandle = NULL;
UINTN mLoopbackBuffer = 0;
DEBUG_SERIAL_FIFO mSerialFifoForTerminal = {
0, 0, DEBUG_SERIAL_IO_FIFO_DEPTH, { 0 }
};
DEBUG_SERIAL_FIFO mSerialFifoForDebug = {
0, 0, DEBUG_SERIAL_IO_FIFO_DEPTH, { 0 }
};
/**
Detect whether specific FIFO is empty or not.
@param[in] Fifo A pointer to the Data Structure DEBUG_SERIAL_FIFO.
@return whether specific FIFO is empty or not.
**/
BOOLEAN
IsDebugTerminalFifoEmpty (
IN DEBUG_SERIAL_FIFO *Fifo
)
{
if (Fifo->Surplus == DEBUG_SERIAL_IO_FIFO_DEPTH) {
return TRUE;
}
return FALSE;
}
/**
Detect whether specific FIFO is full or not.
@param[in] Fifo A pointer to the Data Structure DEBUG_SERIAL_FIFO.
@return whether specific FIFO is full or not.
**/
BOOLEAN
IsDebugTerminalFifoFull (
IN DEBUG_SERIAL_FIFO *Fifo
)
{
if (Fifo->Surplus == 0) {
return TRUE;
}
return FALSE;
}
/**
Add data to specific FIFO.
@param[in] Fifo A pointer to the Data Structure DEBUG_SERIAL_FIFO.
@param[in] Data The data added to FIFO.
@retval EFI_SUCCESS Add data to specific FIFO successfully.
@retval EFI_OUT_OF_RESOURCE Failed to add data because FIFO is already full.
**/
EFI_STATUS
DebugTerminalFifoAdd (
IN DEBUG_SERIAL_FIFO *Fifo,
IN UINT8 Data
)
{
//
// if FIFO full can not add data
//
if (IsDebugTerminalFifoFull (Fifo)) {
return EFI_OUT_OF_RESOURCES;
}
//
// FIFO is not full can add data
//
Fifo->Data[Fifo->Last] = Data;
Fifo->Surplus--;
Fifo->Last++;
if (Fifo->Last == DEBUG_SERIAL_IO_FIFO_DEPTH) {
Fifo->Last = 0;
}
return EFI_SUCCESS;
}
/**
Remove data from specific FIFO.
@param[in] Fifo A pointer to the Data Structure DEBUG_SERIAL_FIFO.
@param[out] Data The data removed from FIFO.
@retval EFI_SUCCESS Remove data from specific FIFO successfully.
@retval EFI_OUT_OF_RESOURCE Failed to remove data because FIFO is empty.
**/
EFI_STATUS
DebugTerminalFifoRemove (
IN DEBUG_SERIAL_FIFO *Fifo,
OUT UINT8 *Data
)
{
//
// if FIFO is empty, no data can remove
//
if (IsDebugTerminalFifoEmpty (Fifo)) {
return EFI_OUT_OF_RESOURCES;
}
//
// FIFO is not empty, can remove data
//
*Data = Fifo->Data[Fifo->First];
Fifo->Surplus++;
Fifo->First++;
if (Fifo->First == DEBUG_SERIAL_IO_FIFO_DEPTH) {
Fifo->First = 0;
}
return EFI_SUCCESS;
}
/**
Install EFI Serial IO protocol based on Debug Communication Library.
**/
VOID
InstallSerialIo (
VOID
)
{
EFI_STATUS Status;
Status = gBS->InstallMultipleProtocolInterfaces (
&mSerialIoHandle,
&gEfiDevicePathProtocolGuid,
&mSerialIoDevicePath,
&gEfiSerialIoProtocolGuid,
&mSerialIo,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Debug Agent: Failed to install EFI Serial IO Protocol on Debug Port!\n"));
}
}
/**
Reset serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@retval EFI_SUCCESS Reset successfully.
**/
EFI_STATUS
EFIAPI
SerialReset (
IN EFI_SERIAL_IO_PROTOCOL *This
)
{
mSerialIoMode.ControlMask = SERIAL_PORT_DEFAULT_CONTROL_MASK;
mLoopbackBuffer = 0;
//
// Not reset serial device hardware indeed.
//
return EFI_SUCCESS;
}
/**
Set new attributes to a serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in] BaudRate The baudrate of the serial device.
@param[in] ReceiveFifoDepth The depth of receive FIFO buffer.
@param[in] Timeout The request timeout for a single char.
@param[in] Parity The type of parity used in serial device.
@param[in] DataBits Number of databits used in serial device.
@param[in] StopBits Number of stopbits used in serial device.
@retval EFI_SUCCESS The new attributes were set.
@retval EFI_INVALID_PARAMETER One or more attributes have an unsupported value.
@retval EFI_DEVICE_ERROR The serial device is not functioning correctly (no return).
**/
EFI_STATUS
EFIAPI
SerialSetAttributes (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN UINT64 BaudRate,
IN UINT32 ReceiveFifoDepth,
IN UINT32 Timeout,
IN EFI_PARITY_TYPE Parity,
IN UINT8 DataBits,
IN EFI_STOP_BITS_TYPE StopBits
)
{
//
// The Debug Communication Library CAN NOT change communications parameters (if it has)
// actually. Because it also has no any idea on what parameters are based on, we cannot
// check the input parameters (like BaudRate, Parity, DataBits and StopBits).
//
//
// Update the Timeout value in the mode structure based on the request.
// The Debug Communication Library can not support a timeout on writes, but the timeout on
// reads can be provided by this module.
//
if (Timeout == 0) {
mSerialIoMode.Timeout = SERIAL_PORT_DEFAULT_TIMEOUT;
} else {
mSerialIoMode.Timeout = Timeout;
}
//
// Update the ReceiveFifoDepth value in the mode structure based on the request.
// This module assumes that the Debug Communication Library uses a FIFO depth of
// SERIAL_PORT_DEFAULT_RECEIVE_FIFO_DEPTH. The Debug Communication Library may actually be
// using a larger FIFO, but there is no way to tell.
//
if ((ReceiveFifoDepth == 0) || (ReceiveFifoDepth >= SERIAL_PORT_DEFAULT_RECEIVE_FIFO_DEPTH)) {
mSerialIoMode.ReceiveFifoDepth = SERIAL_PORT_DEFAULT_RECEIVE_FIFO_DEPTH;
} else {
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
/**
Set Control Bits.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in] Control Control bits that can be settable.
@retval EFI_SUCCESS New Control bits were set successfully.
@retval EFI_UNSUPPORTED The Control bits wanted to set are not supported.
**/
EFI_STATUS
EFIAPI
SerialSetControl (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN UINT32 Control
)
{
//
// The only control bit supported by this module is software loopback.
// If any other bit is set, then return an error
//
if ((Control & (~EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE)) != 0) {
return EFI_UNSUPPORTED;
}
mSerialIoMode.ControlMask = Control;
return EFI_SUCCESS;
}
/**
Get ControlBits.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[out] Control Control signals of the serial device.
@retval EFI_SUCCESS Get Control signals successfully.
**/
EFI_STATUS
EFIAPI
SerialGetControl (
IN EFI_SERIAL_IO_PROTOCOL *This,
OUT UINT32 *Control
)
{
DEBUG_PORT_HANDLE Handle;
BOOLEAN DebugTimerInterruptState;
EFI_TPL Tpl;
//
// Raise TPL to prevent recursion from EFI timer interrupts
//
Tpl = gBS->RaiseTPL (TPL_NOTIFY);
//
// Save and disable Debug Timer interrupt to avoid it to access Debug Port
//
DebugTimerInterruptState = SaveAndSetDebugTimerInterrupt (FALSE);
Handle = GetDebugPortHandle ();
//
// Always assume the output buffer is empty and the Debug Communication Library can process
// more write requests.
//
*Control = mSerialIoMode.ControlMask | EFI_SERIAL_OUTPUT_BUFFER_EMPTY;
//
// Check to see if the Terminal FIFO is empty and
// check to see if the input buffer in the Debug Communication Library is empty
//
if (!IsDebugTerminalFifoEmpty (&mSerialFifoForTerminal) || DebugPortPollBuffer (Handle)) {
*Control &= ~EFI_SERIAL_INPUT_BUFFER_EMPTY;
}
//
// Restore Debug Timer interrupt
//
SaveAndSetDebugTimerInterrupt (DebugTimerInterruptState);
//
// Restore to original TPL
//
gBS->RestoreTPL (Tpl);
return EFI_SUCCESS;
}
/**
Write the specified number of bytes to serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in, out] BufferSize On input the size of Buffer, on output the amount of
data actually written.
@param[in] Buffer The buffer of data to write.
@retval EFI_SUCCESS The data were written successfully.
@retval EFI_DEVICE_ERROR The device reported an error.
@retval EFI_TIMEOUT The write operation was stopped due to timeout.
**/
EFI_STATUS
EFIAPI
SerialWrite (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
)
{
DEBUG_PORT_HANDLE Handle;
BOOLEAN DebugTimerInterruptState;
EFI_TPL Tpl;
//
// Raise TPL to prevent recursion from EFI timer interrupts
//
Tpl = gBS->RaiseTPL (TPL_NOTIFY);
//
// Save and disable Debug Timer interrupt to avoid it to access Debug Port
//
DebugTimerInterruptState = SaveAndSetDebugTimerInterrupt (FALSE);
Handle = GetDebugPortHandle ();
if ((mSerialIoMode.ControlMask & EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE) != 0) {
if (*BufferSize == 0) {
return EFI_SUCCESS;
}
if ((mLoopbackBuffer & SERIAL_PORT_LOOPBACK_BUFFER_FULL) != 0) {
*BufferSize = 0;
return EFI_TIMEOUT;
}
mLoopbackBuffer = SERIAL_PORT_LOOPBACK_BUFFER_FULL | *(UINT8 *)Buffer;
*BufferSize = 1;
} else {
*BufferSize = DebugPortWriteBuffer (Handle, Buffer, *BufferSize);
}
//
// Restore Debug Timer interrupt
//
SaveAndSetDebugTimerInterrupt (DebugTimerInterruptState);
//
// Restore to original TPL
//
gBS->RestoreTPL (Tpl);
return EFI_SUCCESS;
}
/**
Read the specified number of bytes from serial device.
@param[in] This Pointer to EFI_SERIAL_IO_PROTOCOL.
@param[in, out] BufferSize On input the size of Buffer, on output the amount of
data returned in buffer.
@param[out] Buffer The buffer to return the data into.
@retval EFI_SUCCESS The data were read successfully.
@retval EFI_DEVICE_ERROR The device reported an error.
@retval EFI_TIMEOUT The read operation was stopped due to timeout.
**/
EFI_STATUS
EFIAPI
SerialRead (
IN EFI_SERIAL_IO_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
)
{
EFI_STATUS Status;
UINTN Index;
UINT8 *Uint8Buffer;
BOOLEAN DebugTimerInterruptState;
EFI_TPL Tpl;
DEBUG_PORT_HANDLE Handle;
DEBUG_PACKET_HEADER DebugHeader;
UINT8 *Data8;
//
// Raise TPL to prevent recursion from EFI timer interrupts
//
Tpl = gBS->RaiseTPL (TPL_NOTIFY);
//
// Save and disable Debug Timer interrupt to avoid it to access Debug Port
//
DebugTimerInterruptState = SaveAndSetDebugTimerInterrupt (FALSE);
Handle = GetDebugPortHandle ();
Data8 = (UINT8 *)&DebugHeader;
Uint8Buffer = (UINT8 *)Buffer;
if ((mSerialIoMode.ControlMask & EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE) != 0) {
if ((mLoopbackBuffer & SERIAL_PORT_LOOPBACK_BUFFER_FULL) == 0) {
return EFI_TIMEOUT;
}
*Uint8Buffer = (UINT8)(mLoopbackBuffer & 0xff);
mLoopbackBuffer = 0;
*BufferSize = 1;
} else {
for (Index = 0; Index < *BufferSize; Index++) {
//
// Read input character from terminal FIFO firstly
//
Status = DebugTerminalFifoRemove (&mSerialFifoForTerminal, Data8);
if (Status == EFI_SUCCESS) {
*Uint8Buffer = *Data8;
Uint8Buffer++;
continue;
}
//
// Read the input character from Debug Port
//
if (!DebugPortPollBuffer (Handle)) {
break;
}
DebugAgentReadBuffer (Handle, Data8, 1, 0);
if (*Data8 == DEBUG_STARTING_SYMBOL_ATTACH) {
//
// Add the debug symbol into Debug FIFO
//
DebugAgentMsgPrint (DEBUG_AGENT_INFO, "Terminal Timer attach symbol received %x", *Data8);
DebugTerminalFifoAdd (&mSerialFifoForDebug, *Data8);
} else if (*Data8 == DEBUG_STARTING_SYMBOL_NORMAL) {
Status = ReadRemainingBreakPacket (Handle, &DebugHeader);
if (Status == EFI_SUCCESS) {
DebugAgentMsgPrint (DEBUG_AGENT_INFO, "Terminal Timer break symbol received %x", DebugHeader.Command);
DebugTerminalFifoAdd (&mSerialFifoForDebug, DebugHeader.Command);
}
if (Status == EFI_TIMEOUT) {
continue;
}
} else {
*Uint8Buffer = *Data8;
Uint8Buffer++;
}
}
*BufferSize = (UINTN)Uint8Buffer - (UINTN)Buffer;
}
//
// Restore Debug Timer interrupt
//
SaveAndSetDebugTimerInterrupt (DebugTimerInterruptState);
//
// Restore to original TPL
//
gBS->RestoreTPL (Tpl);
return EFI_SUCCESS;
}
/**
Read the Attach/Break-in symbols from the debug port.
@param[in] Handle Pointer to Debug Port handle.
@param[out] BreakSymbol Returned break symbol.
@retval EFI_SUCCESS Read the symbol in BreakSymbol.
@retval EFI_NOT_FOUND No read the break symbol.
**/
EFI_STATUS
DebugReadBreakFromDebugPort (
IN DEBUG_PORT_HANDLE Handle,
OUT UINT8 *BreakSymbol
)
{
EFI_STATUS Status;
DEBUG_PACKET_HEADER DebugHeader;
UINT8 *Data8;
*BreakSymbol = 0;
//
// If Debug Port buffer has data, read it till it was break symbol or Debug Port buffer empty.
//
Data8 = (UINT8 *)&DebugHeader;
while (TRUE) {
//
// If start symbol is not received
//
if (!DebugPortPollBuffer (Handle)) {
//
// If no data in Debug Port, exit
//
break;
}
//
// Try to read the start symbol
//
DebugAgentReadBuffer (Handle, Data8, 1, 0);
if (*Data8 == DEBUG_STARTING_SYMBOL_ATTACH) {
DebugAgentMsgPrint (DEBUG_AGENT_INFO, "Debug Timer attach symbol received %x", *Data8);
*BreakSymbol = *Data8;
return EFI_SUCCESS;
}
if (*Data8 == DEBUG_STARTING_SYMBOL_NORMAL) {
Status = ReadRemainingBreakPacket (Handle, &DebugHeader);
if (Status == EFI_SUCCESS) {
DebugAgentMsgPrint (DEBUG_AGENT_INFO, "Debug Timer break symbol received %x", DebugHeader.Command);
*BreakSymbol = DebugHeader.Command;
return EFI_SUCCESS;
}
if (Status == EFI_TIMEOUT) {
break;
}
} else {
//
// Add to Terminal FIFO
//
DebugTerminalFifoAdd (&mSerialFifoForTerminal, *Data8);
}
}
return EFI_NOT_FOUND;
}
/**
Read the Attach/Break-in symbols.
@param[in] Handle Pointer to Debug Port handle.
@param[out] BreakSymbol Returned break symbol.
@retval EFI_SUCCESS Read the symbol in BreakSymbol.
@retval EFI_NOT_FOUND No read the break symbol.
**/
EFI_STATUS
DebugReadBreakSymbol (
IN DEBUG_PORT_HANDLE Handle,
OUT UINT8 *BreakSymbol
)
{
EFI_STATUS Status;
UINT8 Data8;
//
// Read break symbol from debug FIFO firstly
//
Status = DebugTerminalFifoRemove (&mSerialFifoForDebug, &Data8);
if (Status == EFI_SUCCESS) {
*BreakSymbol = Data8;
return EFI_SUCCESS;
} else {
//
// Read Break symbol from debug port
//
return DebugReadBreakFromDebugPort (Handle, BreakSymbol);
}
}