/** @file | |
# | |
# Copyright (c) 2014, ARM Ltd. All rights reserved.<BR> | |
# | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
# | |
# | |
#**/ | |
#include <Protocol/AndroidFastbootTransport.h> | |
#include <Protocol/Dhcp4.h> | |
#include <Protocol/Tcp4.h> | |
#include <Protocol/ServiceBinding.h> | |
#include <Protocol/SimpleTextOut.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/PrintLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiDriverEntryPoint.h> | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#define IP4_ADDR_TO_STRING(IpAddr, IpAddrString) UnicodeSPrint ( \ | |
IpAddrString, \ | |
16 * 2, \ | |
L"%d.%d.%d.%d", \ | |
IpAddr.Addr[0], \ | |
IpAddr.Addr[1], \ | |
IpAddr.Addr[2], \ | |
IpAddr.Addr[3] \ | |
); | |
// Fastboot says max packet size is 512, but FASTBOOT_TRANSPORT_PROTOCOL | |
// doesn't place a limit on the size of buffers returned by Receive. | |
// (This isn't actually a packet size - it's just the size of the buffers we | |
// pass to the TCP driver to fill with received data.) | |
// We can achieve much better performance by doing this in larger chunks. | |
#define RX_FRAGMENT_SIZE 2048 | |
STATIC EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *mTextOut; | |
STATIC EFI_TCP4_PROTOCOL *mTcpConnection; | |
STATIC EFI_TCP4_PROTOCOL *mTcpListener; | |
STATIC EFI_EVENT mReceiveEvent; | |
STATIC EFI_SERVICE_BINDING_PROTOCOL *mTcpServiceBinding; | |
STATIC EFI_HANDLE mTcpHandle = NULL; | |
// We only ever use one IO token for receive and one for transmit. To save | |
// repeatedly allocating and freeing, just allocate statically and re-use. | |
#define NUM_RX_TOKENS 16 | |
#define TOKEN_NEXT(Index) (((Index) + 1) % NUM_RX_TOKENS) | |
STATIC UINTN mNextSubmitIndex; | |
STATIC UINTN mNextReceiveIndex; | |
STATIC EFI_TCP4_IO_TOKEN mReceiveToken[NUM_RX_TOKENS]; | |
STATIC EFI_TCP4_RECEIVE_DATA mRxData[NUM_RX_TOKENS]; | |
STATIC EFI_TCP4_IO_TOKEN mTransmitToken; | |
STATIC EFI_TCP4_TRANSMIT_DATA mTxData; | |
// We also reuse the accept token | |
STATIC EFI_TCP4_LISTEN_TOKEN mAcceptToken; | |
// .. and the close token | |
STATIC EFI_TCP4_CLOSE_TOKEN mCloseToken; | |
// List type for queued received packets | |
typedef struct _FASTBOOT_TCP_PACKET_LIST { | |
LIST_ENTRY Link; | |
VOID *Buffer; | |
UINTN BufferSize; | |
} FASTBOOT_TCP_PACKET_LIST; | |
STATIC LIST_ENTRY mPacketListHead; | |
STATIC | |
VOID | |
EFIAPI | |
DataReceived ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
); | |
/* | |
Helper function to set up a receive IO token and call Tcp->Receive | |
*/ | |
STATIC | |
EFI_STATUS | |
SubmitRecieveToken ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *FragmentBuffer; | |
Status = EFI_SUCCESS; | |
FragmentBuffer = AllocatePool (RX_FRAGMENT_SIZE); | |
ASSERT (FragmentBuffer != NULL); | |
if (FragmentBuffer == NULL) { | |
DEBUG ((DEBUG_ERROR, "TCP Fastboot out of resources")); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
mRxData[mNextSubmitIndex].DataLength = RX_FRAGMENT_SIZE; | |
mRxData[mNextSubmitIndex].FragmentTable[0].FragmentLength = RX_FRAGMENT_SIZE; | |
mRxData[mNextSubmitIndex].FragmentTable[0].FragmentBuffer = FragmentBuffer; | |
Status = mTcpConnection->Receive (mTcpConnection, &mReceiveToken[mNextSubmitIndex]); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Receive: %r\n", Status)); | |
FreePool (FragmentBuffer); | |
} | |
mNextSubmitIndex = TOKEN_NEXT (mNextSubmitIndex); | |
return Status; | |
} | |
/* | |
Event notify function for when we have closed our TCP connection. | |
We can now start listening for another connection. | |
*/ | |
STATIC | |
VOID | |
ConnectionClosed ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EFI_STATUS Status; | |
// Possible bug in EDK2 TCP4 driver: closing a connection doesn't remove its | |
// PCB from the list of live connections. Subsequent attempts to Configure() | |
// a TCP instance with the same local port will fail with INVALID_PARAMETER. | |
// Calling Configure with NULL is a workaround for this issue. | |
Status = mTcpConnection->Configure (mTcpConnection, NULL); | |
mTcpConnection = NULL; | |
Status = mTcpListener->Accept (mTcpListener, &mAcceptToken); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Accept: %r\n", Status)); | |
} | |
} | |
STATIC | |
VOID | |
CloseReceiveEvents ( | |
VOID | |
) | |
{ | |
UINTN Index; | |
for (Index = 0; Index < NUM_RX_TOKENS; Index++) { | |
gBS->CloseEvent (mReceiveToken[Index].CompletionToken.Event); | |
} | |
} | |
/* | |
Event notify function to be called when we receive TCP data. | |
*/ | |
STATIC | |
VOID | |
EFIAPI | |
DataReceived ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EFI_STATUS Status; | |
FASTBOOT_TCP_PACKET_LIST *NewEntry; | |
EFI_TCP4_IO_TOKEN *ReceiveToken; | |
ReceiveToken = &mReceiveToken[mNextReceiveIndex]; | |
Status = ReceiveToken->CompletionToken.Status; | |
if (Status == EFI_CONNECTION_FIN) { | |
// | |
// Remote host closed connection. Close our end. | |
// | |
CloseReceiveEvents (); | |
Status = mTcpConnection->Close (mTcpConnection, &mCloseToken); | |
ASSERT_EFI_ERROR (Status); | |
return; | |
} | |
// | |
// Add an element to the receive queue | |
// | |
NewEntry = AllocatePool (sizeof (FASTBOOT_TCP_PACKET_LIST)); | |
if (NewEntry == NULL) { | |
DEBUG ((DEBUG_ERROR, "TCP Fastboot: Out of resources\n")); | |
return; | |
} | |
mNextReceiveIndex = TOKEN_NEXT (mNextReceiveIndex); | |
if (!EFI_ERROR (Status)) { | |
NewEntry->Buffer | |
= ReceiveToken->Packet.RxData->FragmentTable[0].FragmentBuffer; | |
NewEntry->BufferSize | |
= ReceiveToken->Packet.RxData->FragmentTable[0].FragmentLength; | |
// Prepare to receive more data | |
SubmitRecieveToken (); | |
} else { | |
// Fatal receive error. Put an entry with NULL in the queue, signifying | |
// to return EFI_DEVICE_ERROR from TcpFastbootTransportReceive. | |
NewEntry->Buffer = NULL; | |
NewEntry->BufferSize = 0; | |
DEBUG ((DEBUG_ERROR, "\nTCP Fastboot Receive error: %r\n", Status)); | |
} | |
InsertTailList (&mPacketListHead, &NewEntry->Link); | |
Status = gBS->SignalEvent (mReceiveEvent); | |
ASSERT_EFI_ERROR (Status); | |
} | |
/* | |
Event notify function to be called when we accept an incoming TCP connection. | |
*/ | |
STATIC | |
VOID | |
EFIAPI | |
ConnectionAccepted ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EFI_TCP4_LISTEN_TOKEN *AcceptToken; | |
EFI_STATUS Status; | |
UINTN Index; | |
AcceptToken = (EFI_TCP4_LISTEN_TOKEN *)Context; | |
Status = AcceptToken->CompletionToken.Status; | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Fastboot: Connection Error: %r\n", Status)); | |
return; | |
} | |
DEBUG ((DEBUG_ERROR, "TCP Fastboot: Connection Received.\n")); | |
// | |
// Accepting a new TCP connection creates a new instance of the TCP protocol. | |
// Open it and prepare to receive on it. | |
// | |
Status = gBS->OpenProtocol ( | |
AcceptToken->NewChildHandle, | |
&gEfiTcp4ProtocolGuid, | |
(VOID **)&mTcpConnection, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Open TCP Connection: %r\n", Status)); | |
return; | |
} | |
mNextSubmitIndex = 0; | |
mNextReceiveIndex = 0; | |
for (Index = 0; Index < NUM_RX_TOKENS; Index++) { | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
DataReceived, | |
NULL, | |
&(mReceiveToken[Index].CompletionToken.Event) | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
for (Index = 0; Index < NUM_RX_TOKENS; Index++) { | |
SubmitRecieveToken (); | |
} | |
} | |
/* | |
Set up TCP Fastboot transport: Configure the network device via DHCP then | |
start waiting for a TCP connection on the Fastboot port. | |
*/ | |
EFI_STATUS | |
TcpFastbootTransportStart ( | |
EFI_EVENT ReceiveEvent | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE NetDeviceHandle; | |
EFI_HANDLE *HandleBuffer; | |
EFI_IP4_MODE_DATA Ip4ModeData; | |
UINTN NumHandles; | |
CHAR16 IpAddrString[16]; | |
UINTN Index; | |
EFI_TCP4_CONFIG_DATA TcpConfigData = { | |
0x00, // IPv4 Type of Service | |
255, // IPv4 Time to Live | |
{ // AccessPoint: | |
TRUE, // Use default address | |
{ | |
{ 0, 0, 0, 0 } | |
}, // IP Address (ignored - use default) | |
{ | |
{ 0, 0, 0, 0 } | |
}, // Subnet mask (ignored - use default) | |
FixedPcdGet32 (PcdAndroidFastbootTcpPort), // Station port | |
{ | |
{ 0, 0, 0, 0 } | |
}, // Remote address: accept any | |
0, // Remote Port: accept any | |
FALSE // ActiveFlag: be a "server" | |
}, | |
NULL // Default advanced TCP options | |
}; | |
mReceiveEvent = ReceiveEvent; | |
InitializeListHead (&mPacketListHead); | |
mTextOut->OutputString (mTextOut, L"Initialising TCP Fastboot transport...\r\n"); | |
// | |
// Open a passive TCP instance | |
// | |
Status = gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiTcp4ServiceBindingProtocolGuid, | |
NULL, | |
&NumHandles, | |
&HandleBuffer | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Find TCP Service Binding: %r\n", Status)); | |
return Status; | |
} | |
// We just use the first network device | |
NetDeviceHandle = HandleBuffer[0]; | |
Status = gBS->OpenProtocol ( | |
NetDeviceHandle, | |
&gEfiTcp4ServiceBindingProtocolGuid, | |
(VOID **)&mTcpServiceBinding, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Open TCP Service Binding: %r\n", Status)); | |
return Status; | |
} | |
Status = mTcpServiceBinding->CreateChild (mTcpServiceBinding, &mTcpHandle); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP ServiceBinding Create: %r\n", Status)); | |
return Status; | |
} | |
Status = gBS->OpenProtocol ( | |
mTcpHandle, | |
&gEfiTcp4ProtocolGuid, | |
(VOID **)&mTcpListener, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Open TCP Protocol: %r\n", Status)); | |
} | |
// | |
// Set up re-usable tokens | |
// | |
for (Index = 0; Index < NUM_RX_TOKENS; Index++) { | |
mRxData[Index].UrgentFlag = FALSE; | |
mRxData[Index].FragmentCount = 1; | |
mReceiveToken[Index].Packet.RxData = &mRxData[Index]; | |
} | |
mTxData.Push = TRUE; | |
mTxData.Urgent = FALSE; | |
mTxData.FragmentCount = 1; | |
mTransmitToken.Packet.TxData = &mTxData; | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
ConnectionAccepted, | |
&mAcceptToken, | |
&mAcceptToken.CompletionToken.Event | |
); | |
ASSERT_EFI_ERROR (Status); | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
ConnectionClosed, | |
&mCloseToken, | |
&mCloseToken.CompletionToken.Event | |
); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Configure the TCP instance | |
// | |
Status = mTcpListener->Configure (mTcpListener, &TcpConfigData); | |
if (Status == EFI_NO_MAPPING) { | |
// Wait until the IP configuration process (probably DHCP) has finished | |
do { | |
Status = mTcpListener->GetModeData ( | |
mTcpListener, | |
NULL, | |
NULL, | |
&Ip4ModeData, | |
NULL, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
} while (!Ip4ModeData.IsConfigured); | |
Status = mTcpListener->Configure (mTcpListener, &TcpConfigData); | |
} else if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Configure: %r\n", Status)); | |
return Status; | |
} | |
// | |
// Tell the user our address and hostname | |
// | |
IP4_ADDR_TO_STRING (Ip4ModeData.ConfigData.StationAddress, IpAddrString); | |
mTextOut->OutputString (mTextOut, L"TCP Fastboot transport configured."); | |
mTextOut->OutputString (mTextOut, L"\r\nIP address: "); | |
mTextOut->OutputString (mTextOut, IpAddrString); | |
mTextOut->OutputString (mTextOut, L"\r\n"); | |
// | |
// Start listening for a connection | |
// | |
Status = mTcpListener->Accept (mTcpListener, &mAcceptToken); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Accept: %r\n", Status)); | |
return Status; | |
} | |
mTextOut->OutputString (mTextOut, L"TCP Fastboot transport initialised.\r\n"); | |
FreePool (HandleBuffer); | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
TcpFastbootTransportStop ( | |
VOID | |
) | |
{ | |
EFI_TCP4_CLOSE_TOKEN CloseToken; | |
EFI_STATUS Status; | |
UINTN EventIndex; | |
FASTBOOT_TCP_PACKET_LIST *Entry; | |
FASTBOOT_TCP_PACKET_LIST *NextEntry; | |
// Close any existing TCP connection, blocking until it's done. | |
if (mTcpConnection != NULL) { | |
CloseReceiveEvents (); | |
CloseToken.AbortOnClose = FALSE; | |
Status = gBS->CreateEvent (0, 0, NULL, NULL, &CloseToken.CompletionToken.Event); | |
ASSERT_EFI_ERROR (Status); | |
Status = mTcpConnection->Close (mTcpConnection, &CloseToken); | |
ASSERT_EFI_ERROR (Status); | |
Status = gBS->WaitForEvent ( | |
1, | |
&CloseToken.CompletionToken.Event, | |
&EventIndex | |
); | |
ASSERT_EFI_ERROR (Status); | |
ASSERT_EFI_ERROR (CloseToken.CompletionToken.Status); | |
// Possible bug in EDK2 TCP4 driver: closing a connection doesn't remove its | |
// PCB from the list of live connections. Subsequent attempts to Configure() | |
// a TCP instance with the same local port will fail with INVALID_PARAMETER. | |
// Calling Configure with NULL is a workaround for this issue. | |
Status = mTcpConnection->Configure (mTcpConnection, NULL); | |
ASSERT_EFI_ERROR (Status); | |
} | |
gBS->CloseEvent (mAcceptToken.CompletionToken.Event); | |
// Stop listening for connections. | |
// Ideally we would do this with Cancel, but it isn't implemented by EDK2. | |
// So we just "reset this TCPv4 instance brutally". | |
Status = mTcpListener->Configure (mTcpListener, NULL); | |
ASSERT_EFI_ERROR (Status); | |
Status = mTcpServiceBinding->DestroyChild (mTcpServiceBinding, mTcpHandle); | |
// Free any data the user didn't pick up | |
Entry = (FASTBOOT_TCP_PACKET_LIST *)GetFirstNode (&mPacketListHead); | |
while (!IsNull (&mPacketListHead, &Entry->Link)) { | |
NextEntry = (FASTBOOT_TCP_PACKET_LIST *)GetNextNode (&mPacketListHead, &Entry->Link); | |
RemoveEntryList (&Entry->Link); | |
if (Entry->Buffer) { | |
FreePool (Entry->Buffer); | |
} | |
FreePool (Entry); | |
Entry = NextEntry; | |
} | |
return EFI_SUCCESS; | |
} | |
/* | |
Event notify function for when data has been sent. Free resources and report | |
errors. | |
Context should point to the transmit IO token passed to | |
TcpConnection->Transmit. | |
*/ | |
STATIC | |
VOID | |
DataSent ( | |
EFI_EVENT Event, | |
VOID *Context | |
) | |
{ | |
EFI_STATUS Status; | |
Status = mTransmitToken.CompletionToken.Status; | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Fastboot transmit result: %r\n", Status)); | |
gBS->SignalEvent (*(EFI_EVENT *)Context); | |
} | |
FreePool (mTransmitToken.Packet.TxData->FragmentTable[0].FragmentBuffer); | |
} | |
EFI_STATUS | |
TcpFastbootTransportSend ( | |
IN UINTN BufferSize, | |
IN CONST VOID *Buffer, | |
IN EFI_EVENT *FatalErrorEvent | |
) | |
{ | |
EFI_STATUS Status; | |
if (BufferSize > 512) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Build transmit IO token | |
// | |
// Create an event so we are notified when a transmission is complete. | |
// We use this to free resources and report errors. | |
Status = gBS->CreateEvent ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_CALLBACK, | |
DataSent, | |
FatalErrorEvent, | |
&mTransmitToken.CompletionToken.Event | |
); | |
ASSERT_EFI_ERROR (Status); | |
mTxData.DataLength = BufferSize; | |
mTxData.FragmentTable[0].FragmentLength = BufferSize; | |
mTxData.FragmentTable[0].FragmentBuffer = AllocateCopyPool ( | |
BufferSize, | |
Buffer | |
); | |
Status = mTcpConnection->Transmit (mTcpConnection, &mTransmitToken); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "TCP Transmit: %r\n", Status)); | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
TcpFastbootTransportReceive ( | |
OUT UINTN *BufferSize, | |
OUT VOID **Buffer | |
) | |
{ | |
FASTBOOT_TCP_PACKET_LIST *Entry; | |
if (IsListEmpty (&mPacketListHead)) { | |
return EFI_NOT_READY; | |
} | |
Entry = (FASTBOOT_TCP_PACKET_LIST *)GetFirstNode (&mPacketListHead); | |
if (Entry->Buffer == NULL) { | |
// There was an error receiving this packet. | |
return EFI_DEVICE_ERROR; | |
} | |
*Buffer = Entry->Buffer; | |
*BufferSize = Entry->BufferSize; | |
RemoveEntryList (&Entry->Link); | |
FreePool (Entry); | |
return EFI_SUCCESS; | |
} | |
FASTBOOT_TRANSPORT_PROTOCOL mTransportProtocol = { | |
TcpFastbootTransportStart, | |
TcpFastbootTransportStop, | |
TcpFastbootTransportSend, | |
TcpFastbootTransportReceive | |
}; | |
EFI_STATUS | |
TcpFastbootTransportEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
Status = gBS->LocateProtocol ( | |
&gEfiSimpleTextOutProtocolGuid, | |
NULL, | |
(VOID **)&mTextOut | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Fastboot: Open Text Output Protocol: %r\n", Status)); | |
return Status; | |
} | |
Status = gBS->InstallProtocolInterface ( | |
&ImageHandle, | |
&gAndroidFastbootTransportProtocolGuid, | |
EFI_NATIVE_INTERFACE, | |
&mTransportProtocol | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Fastboot: Install transport Protocol: %r\n", Status)); | |
} | |
return Status; | |
} |