/** @file | |
Serial IO Abstraction for GDB stub. This allows an EFI consoles that shows up on the system | |
running GDB. One console for error information and another console for user input/output. | |
Basic packet format is $packet-data#checksum. So every command has 4 bytes of overhead: $, | |
#, 0, 0. The 0 and 0 are the ascii characters for the checksum. | |
Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <GdbStubInternal.h> | |
// | |
// Set TRUE if F Reply package signals a ctrl-c. We can not process the Ctrl-c | |
// here we need to wait for the periodic callback to do this. | |
// | |
BOOLEAN gCtrlCBreakFlag = FALSE; | |
// | |
// If the periodic callback is called while we are processing an F packet we need | |
// to let the callback know to not read from the serial stream as it could steal | |
// characters from the F response packet | |
// | |
BOOLEAN gProcessingFPacket = FALSE; | |
/** | |
Process a control-C break message. | |
Currently a place holder, remove the ASSERT when it gets implemented. | |
@param ErrNo Error information from the F reply packet or other source | |
**/ | |
VOID | |
GdbCtrlCBreakMessage ( | |
IN UINTN ErrNo | |
) | |
{ | |
// See D.10.5 of gdb.pdf | |
// This should look like a break message. Should look like SIGINT | |
/* TODO: Make sure if we should do anything with ErrNo */ | |
// Turn on the global Ctrl-C flag. | |
gCtrlCBreakFlag = TRUE; | |
} | |
/** | |
Parse the F reply packet and extract the return value and an ErrNo if it exists. | |
@param Packet Packet to parse like an F reply packet | |
@param ErrNo Buffer to hold Count bytes that were read | |
@retval -1 Error, not a valid F reply packet | |
@retval other Return the return code from the F reply packet | |
**/ | |
INTN | |
GdbParseFReplyPacket ( | |
IN CHAR8 *Packet, | |
OUT UINTN *ErrNo | |
) | |
{ | |
INTN RetCode; | |
if (Packet[0] != 'F') { | |
// A valid response would be an F packet | |
return -1; | |
} | |
RetCode = AsciiStrHexToUintn (&Packet[1]); | |
// Find 1st comma | |
for ( ; *Packet != '\0' && *Packet != ','; Packet++) { | |
} | |
if (*Packet == '\0') { | |
*ErrNo = 0; | |
return RetCode; | |
} | |
*ErrNo = AsciiStrHexToUintn (++Packet); | |
// Find 2nd comma | |
for ( ; *Packet != '\0' && *Packet != ','; Packet++) { | |
} | |
if (*Packet == '\0') { | |
return RetCode; | |
} | |
if (*(++Packet) == 'C') { | |
GdbCtrlCBreakMessage (*ErrNo); | |
} | |
return RetCode; | |
} | |
/** | |
Read data from a FileDescriptor. On success number of bytes read is returned. Zero indicates | |
the end of a file. On error -1 is returned. If count is zero, GdbRead returns zero. | |
@param FileDescriptor Device to talk to. | |
@param Buffer Buffer to hold Count bytes that were read | |
@param Count Number of bytes to transfer. | |
@retval -1 Error | |
@retval {other} Number of bytes read. | |
**/ | |
INTN | |
GdbRead ( | |
IN INTN FileDescriptor, | |
OUT VOID *Buffer, | |
IN UINTN Count | |
) | |
{ | |
CHAR8 Packet[128]; | |
UINTN Size; | |
INTN RetCode; | |
UINTN ErrNo; | |
BOOLEAN ReceiveDone = FALSE; | |
// Send: | |
// "Fread,XX,YYYYYYYY,XX | |
// | |
// XX - FileDescriptor in ASCII | |
// YYYYYYYY - Buffer address in ASCII | |
// XX - Count in ASCII | |
// SS - check sum | |
// | |
Size = AsciiSPrint (Packet, sizeof (Packet), "Fread,%x,%x,%x", FileDescriptor, Buffer, Count); | |
// Packet array is too small if you got this ASSERT | |
ASSERT (Size < sizeof (Packet)); | |
gProcessingFPacket = TRUE; | |
SendPacket (Packet); | |
Print ((CHAR16 *)L"Packet sent..\n"); | |
do { | |
// Reply: | |
ReceivePacket (Packet, sizeof (Packet)); | |
Print ((CHAR16 *)L"Command received..%c\n", Packet[0]); | |
// Process GDB commands | |
switch (Packet[0]) { | |
// Write memory command. | |
// M addr,length:XX... | |
case 'M': | |
WriteToMemory (Packet); | |
break; | |
// Fretcode, errno, Ctrl-C flag | |
// retcode - Count read | |
case 'F': | |
// Once target receives F reply packet that means the previous | |
// transactions are finished. | |
ReceiveDone = TRUE; | |
break; | |
// Send empty buffer | |
default: | |
SendNotSupported (); | |
break; | |
} | |
} while (ReceiveDone == FALSE); | |
RetCode = GdbParseFReplyPacket (Packet, &ErrNo); | |
Print ((CHAR16 *)L"RetCode: %x..ErrNo: %x..\n", RetCode, ErrNo); | |
if (ErrNo > 0) { | |
// Send error to the host if there is any. | |
SendError ((UINT8)ErrNo); | |
} | |
gProcessingFPacket = FALSE; | |
return RetCode; | |
} | |
/** | |
Write data to a FileDescriptor. On success number of bytes written is returned. Zero indicates | |
nothing was written. On error -1 is returned. | |
@param FileDescriptor Device to talk to. | |
@param Buffer Buffer to hold Count bytes that are to be written | |
@param Count Number of bytes to transfer. | |
@retval -1 Error | |
@retval {other} Number of bytes written. | |
**/ | |
INTN | |
GdbWrite ( | |
IN INTN FileDescriptor, | |
OUT CONST VOID *Buffer, | |
IN UINTN Count | |
) | |
{ | |
CHAR8 Packet[128]; | |
UINTN Size; | |
INTN RetCode; | |
UINTN ErrNo; | |
BOOLEAN ReceiveDone = FALSE; | |
// Send: | |
// #Fwrite,XX,YYYYYYYY,XX$SS | |
// | |
// XX - FileDescriptor in ASCII | |
// YYYYYYYY - Buffer address in ASCII | |
// XX - Count in ASCII | |
// SS - check sum | |
// | |
Size = AsciiSPrint (Packet, sizeof (Packet), "Fwrite,%x,%x,%x", FileDescriptor, Buffer, Count); | |
// Packet array is too small if you got this ASSERT | |
ASSERT (Size < sizeof (Packet)); | |
SendPacket (Packet); | |
Print ((CHAR16 *)L"Packet sent..\n"); | |
do { | |
// Reply: | |
ReceivePacket (Packet, sizeof (Packet)); | |
Print ((CHAR16 *)L"Command received..%c\n", Packet[0]); | |
// Process GDB commands | |
switch (Packet[0]) { | |
// Read memory command. | |
// m addr,length. | |
case 'm': | |
ReadFromMemory (Packet); | |
break; | |
// Fretcode, errno, Ctrl-C flag | |
// retcode - Count read | |
case 'F': | |
// Once target receives F reply packet that means the previous | |
// transactions are finished. | |
ReceiveDone = TRUE; | |
break; | |
// Send empty buffer | |
default: | |
SendNotSupported (); | |
break; | |
} | |
} while (ReceiveDone == FALSE); | |
RetCode = GdbParseFReplyPacket (Packet, &ErrNo); | |
Print ((CHAR16 *)L"RetCode: %x..ErrNo: %x..\n", RetCode, ErrNo); | |
// Send error to the host if there is any. | |
if (ErrNo > 0) { | |
SendError ((UINT8)ErrNo); | |
} | |
return RetCode; | |
} | |
/** | |
Reset the serial device. | |
@param This Protocol instance pointer. | |
@retval EFI_SUCCESS The device was reset. | |
@retval EFI_DEVICE_ERROR The serial device could not be reset. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialReset ( | |
IN EFI_SERIAL_IO_PROTOCOL *This | |
) | |
{ | |
return EFI_SUCCESS; | |
} | |
/** | |
Sets the baud rate, receive FIFO depth, transmit/receive time out, parity, | |
data buts, and stop bits on a serial device. | |
@param This Protocol instance pointer. | |
@param BaudRate The requested baud rate. A BaudRate value of 0 will use the | |
device's default interface speed. | |
@param ReceiveFifoDepth The requested depth of the FIFO on the receive side of the | |
serial interface. A ReceiveFifoDepth value of 0 will use | |
the device's default FIFO depth. | |
@param Timeout The requested time out for a single character in microseconds. | |
This timeout applies to both the transmit and receive side of the | |
interface. A Timeout value of 0 will use the device's default time | |
out value. | |
@param Parity The type of parity to use on this serial device. A Parity value of | |
DefaultParity will use the device's default parity value. | |
@param DataBits The number of data bits to use on the serial device. A DataBits | |
value of 0 will use the device's default data bit setting. | |
@param StopBits The number of stop bits to use on this serial device. A StopBits | |
value of DefaultStopBits will use the device's default number of | |
stop bits. | |
@retval EFI_SUCCESS The device was reset. | |
@retval EFI_DEVICE_ERROR The serial device could not be reset. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialSetAttributes ( | |
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 | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Set the control bits on a serial device | |
@param This Protocol instance pointer. | |
@param Control Set the bits of Control that are settable. | |
@retval EFI_SUCCESS The new control bits were set on the serial device. | |
@retval EFI_UNSUPPORTED The serial device does not support this operation. | |
@retval EFI_DEVICE_ERROR The serial device is not functioning correctly. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialSetControl ( | |
IN EFI_SERIAL_IO_PROTOCOL *This, | |
IN UINT32 Control | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Retrieves the status of the control bits on a serial device | |
@param This Protocol instance pointer. | |
@param Control A pointer to return the current Control signals from the serial device. | |
@retval EFI_SUCCESS The control bits were read from the serial device. | |
@retval EFI_DEVICE_ERROR The serial device is not functioning correctly. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialGetControl ( | |
IN EFI_SERIAL_IO_PROTOCOL *This, | |
OUT UINT32 *Control | |
) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Writes data to a serial device. | |
@param This Protocol instance pointer. | |
@param BufferSize On input, the size of the Buffer. On output, the amount of | |
data actually written. | |
@param Buffer The buffer of data to write | |
@retval EFI_SUCCESS The data was written. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_TIMEOUT The data write was stopped due to a timeout. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialWrite ( | |
IN EFI_SERIAL_IO_PROTOCOL *This, | |
IN OUT UINTN *BufferSize, | |
IN VOID *Buffer | |
) | |
{ | |
GDB_SERIAL_DEV *SerialDev; | |
UINTN Return; | |
SerialDev = GDB_SERIAL_DEV_FROM_THIS (This); | |
Return = GdbWrite (SerialDev->OutFileDescriptor, Buffer, *BufferSize); | |
if (Return == (UINTN)-1) { | |
return EFI_DEVICE_ERROR; | |
} | |
if (Return != *BufferSize) { | |
*BufferSize = Return; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Writes data to a serial device. | |
@param This Protocol instance pointer. | |
@param BufferSize On input, the size of the Buffer. On output, the amount of | |
data returned in Buffer. | |
@param Buffer The buffer to return the data into. | |
@retval EFI_SUCCESS The data was read. | |
@retval EFI_DEVICE_ERROR The device reported an error. | |
@retval EFI_TIMEOUT The data write was stopped due to a timeout. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GdbSerialRead ( | |
IN EFI_SERIAL_IO_PROTOCOL *This, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer | |
) | |
{ | |
GDB_SERIAL_DEV *SerialDev; | |
UINTN Return; | |
SerialDev = GDB_SERIAL_DEV_FROM_THIS (This); | |
Return = GdbRead (SerialDev->InFileDescriptor, Buffer, *BufferSize); | |
if (Return == (UINTN)-1) { | |
return EFI_DEVICE_ERROR; | |
} | |
if (Return != *BufferSize) { | |
*BufferSize = Return; | |
} | |
return EFI_SUCCESS; | |
} | |
// | |
// Template used to initialize the GDB Serial IO protocols | |
// | |
GDB_SERIAL_DEV gdbSerialDevTemplate = { | |
GDB_SERIAL_DEV_SIGNATURE, | |
NULL, | |
{ // SerialIo | |
SERIAL_IO_INTERFACE_REVISION, | |
GdbSerialReset, | |
GdbSerialSetAttributes, | |
GdbSerialSetControl, | |
GdbSerialGetControl, | |
GdbSerialWrite, | |
GdbSerialRead, | |
NULL | |
}, | |
{ // SerialMode | |
0, // ControlMask | |
0, // Timeout | |
0, // BaudRate | |
1, // RceiveFifoDepth | |
0, // DataBits | |
0, // Parity | |
0 // StopBits | |
}, | |
{ | |
{ | |
{ | |
HARDWARE_DEVICE_PATH, | |
HW_VENDOR_DP, | |
{ | |
(UINT8)(sizeof (VENDOR_DEVICE_PATH) + sizeof (UINT32)), | |
(UINT8)((sizeof (VENDOR_DEVICE_PATH) + sizeof (UINT32)) >> 8) | |
}, | |
}, | |
EFI_SERIAL_IO_PROTOCOL_GUID | |
}, | |
0, | |
{ | |
END_DEVICE_PATH_TYPE, | |
END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{ | |
(UINT8)(sizeof (EFI_DEVICE_PATH_PROTOCOL)), | |
(UINT8)(sizeof (EFI_DEVICE_PATH_PROTOCOL) >> 8) | |
} | |
}, | |
}, | |
GDB_STDIN, | |
GDB_STDOUT | |
}; | |
/** | |
Make two serial consoles: 1) StdIn and StdOut via GDB. 2) StdErr via GDB. | |
These console show up on the remote system running GDB | |
**/ | |
VOID | |
GdbInitializeSerialConsole ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
GDB_SERIAL_DEV *StdOutSerialDev; | |
GDB_SERIAL_DEV *StdErrSerialDev; | |
// Use the template to make a copy of the Serial Console private data structure. | |
StdOutSerialDev = AllocateCopyPool (sizeof (GDB_SERIAL_DEV), &gdbSerialDevTemplate); | |
ASSERT (StdOutSerialDev != NULL); | |
// Fixup pointer after the copy | |
StdOutSerialDev->SerialIo.Mode = &StdOutSerialDev->SerialMode; | |
StdErrSerialDev = AllocateCopyPool (sizeof (GDB_SERIAL_DEV), &gdbSerialDevTemplate); | |
ASSERT (StdErrSerialDev != NULL); | |
// Fixup pointer and modify stuff that is different for StdError | |
StdErrSerialDev->SerialIo.Mode = &StdErrSerialDev->SerialMode; | |
StdErrSerialDev->DevicePath.Index = 1; | |
StdErrSerialDev->OutFileDescriptor = GDB_STDERR; | |
// Make a new handle with Serial IO protocol and its device path on it. | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&StdOutSerialDev->Handle, | |
&gEfiSerialIoProtocolGuid, | |
&StdOutSerialDev->SerialIo, | |
&gEfiDevicePathProtocolGuid, | |
&StdOutSerialDev->DevicePath, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
// Make a new handle with Serial IO protocol and its device path on it. | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&StdErrSerialDev->Handle, | |
&gEfiSerialIoProtocolGuid, | |
&StdErrSerialDev->SerialIo, | |
&gEfiDevicePathProtocolGuid, | |
&StdErrSerialDev->DevicePath, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
} |