X-Git-Url: https://git.proxmox.com/?p=mirror_edk2.git;a=blobdiff_plain;f=ArmPkg%2FLibrary%2FBdsLib%2FBdsFilePath.c;h=41557bb91816e7df488298e72e44994090b2af6b;hp=90be9c1e117895282f106a47abb2dd21d80012c1;hb=edc65fc4d83a7083ed139c00b8669c959a7fae8f;hpb=1aaa6f61a55a4db594d264ab8c0b0c9ddac1110e diff --git a/ArmPkg/Library/BdsLib/BdsFilePath.c b/ArmPkg/Library/BdsLib/BdsFilePath.c index 90be9c1e11..41557bb918 100644 --- a/ArmPkg/Library/BdsLib/BdsFilePath.c +++ b/ArmPkg/Library/BdsLib/BdsFilePath.c @@ -1,25 +1,62 @@ /** @file * -* Copyright (c) 2011-2013, ARM Limited. All rights reserved. -* -* This program and the accompanying materials -* are licensed and made available under the terms and conditions of the BSD License -* which accompanies this distribution. The full text of the license may be found at -* http://opensource.org/licenses/bsd-license.php +* Copyright (c) 2011-2014, ARM Limited. All rights reserved. * -* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, -* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +* This program and the accompanying materials +* are licensed and made available under the terms and conditions of the BSD License +* which accompanies this distribution. The full text of the license may be found at +* http://opensource.org/licenses/bsd-license.php +* +* THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +* WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. * **/ #include "BdsInternal.h" +#include + +#include #include #include #include #include +#include +#include + +/* Type and defines to set up the DHCP4 options */ + +typedef struct { + EFI_DHCP4_PACKET_OPTION Head; + UINT8 Route; +} DHCP4_OPTION; + +#define DHCP_TAG_PARA_LIST 55 +#define DHCP_TAG_NETMASK 1 +#define DHCP_TAG_ROUTER 3 + +/* + Constant strings and define related to the message indicating the amount of + progress in the dowloading of a TFTP file. +*/ + +// Frame for the progression slider +STATIC CONST CHAR16 mTftpProgressFrame[] = L"[ ]"; + +// Number of steps in the progression slider +#define TFTP_PROGRESS_SLIDER_STEPS ((sizeof (mTftpProgressFrame) / sizeof (CHAR16)) - 3) + +// Size in number of characters plus one (final zero) of the message to +// indicate the progress of a tftp download. The format is "[(progress slider: +// 40 characters)] (nb of KBytes downloaded so far: 7 characters) Kb". There +// are thus the number of characters in mTftpProgressFrame[] plus 11 characters +// (2 // spaces, "Kb" and seven characters for the number of KBytes). +#define TFTP_PROGRESS_MESSAGE_SIZE ((sizeof (mTftpProgressFrame) / sizeof (CHAR16)) + 12) + +// String to delete the tftp progress message to be able to update it : +// (TFTP_PROGRESS_MESSAGE_SIZE-1) '\b' +STATIC CONST CHAR16 mTftpProgressDelete[] = L"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"; -#define IS_DEVICE_PATH_NODE(node,type,subtype) (((node)->Type == (type)) && ((node)->SubType == (subtype))) // Extract the FilePath from the Device Path CHAR16* @@ -300,44 +337,57 @@ TryRemovableDevice ( return Status; } -STATIC +STATIC EFI_STATUS -BdsConnectAndUpdateDevicePath ( - IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, - OUT EFI_HANDLE *Handle, - OUT EFI_DEVICE_PATH_PROTOCOL **RemainingDevicePath +BdsConnectAndUpdateDevicePath ( + IN OUT EFI_DEVICE_PATH_PROTOCOL **DevicePath, + OUT EFI_HANDLE *Handle, + OUT EFI_DEVICE_PATH_PROTOCOL **RemainingDevicePath ) { EFI_DEVICE_PATH* Remaining; EFI_DEVICE_PATH* NewDevicePath; EFI_STATUS Status; + EFI_HANDLE PreviousHandle; - if ((DevicePath == NULL) || (*DevicePath == NULL) || (Handle == NULL)) { + if ((DevicePath == NULL) || (*DevicePath == NULL) || (Handle == NULL)) { return EFI_INVALID_PARAMETER; } + PreviousHandle = NULL; do { - Remaining = *DevicePath; + Remaining = *DevicePath; + // The LocateDevicePath() function locates all devices on DevicePath that support Protocol and returns // the handle to the device that is closest to DevicePath. On output, the device path pointer is modified // to point to the remaining part of the device path Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &Remaining, Handle); + if (!EFI_ERROR (Status)) { - // Recursive = FALSE: We do not want to start all the device tree - Status = gBS->ConnectController (*Handle, NULL, Remaining, FALSE); - } + if (*Handle == PreviousHandle) { + // + // If no forward progress is made try invoking the Dispatcher. + // A new FV may have been added to the system and new drivers + // may now be found. + // Status == EFI_SUCCESS means a driver was dispatched + // Status == EFI_NOT_FOUND means no new drivers were dispatched + // + Status = gDS->Dispatch (); + } - /*// We need to check if RemainingDevicePath does not point on the last node. Otherwise, calling - // NextDevicePathNode () will return an undetermined Device Path Node - if (!IsDevicePathEnd (RemainingDevicePath)) { - RemainingDevicePath = NextDevicePathNode (RemainingDevicePath); - }*/ + if (!EFI_ERROR (Status)) { + PreviousHandle = *Handle; + + // Recursive = FALSE: We do not want to start the whole device tree + Status = gBS->ConnectController (*Handle, NULL, Remaining, FALSE); + } + } } while (!EFI_ERROR (Status) && !IsDevicePathEnd (Remaining)); if (!EFI_ERROR (Status)) { // Now, we have got the whole Device Path connected, call again ConnectController to ensure all the supported Driver // Binding Protocol are connected (such as DiskIo and SimpleFileSystem) - Remaining = *DevicePath; + Remaining = *DevicePath; Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &Remaining, Handle); if (!EFI_ERROR (Status)) { Status = gBS->ConnectController (*Handle, NULL, Remaining, FALSE); @@ -360,11 +410,11 @@ BdsConnectAndUpdateDevicePath ( //TODO: Should we just return success and leave the caller decide if it is the expected RemainingPath Status = EFI_SUCCESS; } else { - Status = TryRemovableDevice (*DevicePath, Handle, &NewDevicePath); + Status = TryRemovableDevice (*DevicePath, Handle, &NewDevicePath); if (!EFI_ERROR (Status)) { - Status = BdsConnectAndUpdateDevicePath (&NewDevicePath, Handle, RemainingDevicePath); - *DevicePath = NewDevicePath; - return Status; + Status = BdsConnectAndUpdateDevicePath (&NewDevicePath, Handle, RemainingDevicePath); + *DevicePath = NewDevicePath; + return Status; } } @@ -375,28 +425,28 @@ BdsConnectAndUpdateDevicePath ( return Status; } -/** - Connect a Device Path and return the handle of the driver that support this DevicePath - - @param DevicePath Device Path of the File to connect - @param Handle Handle of the driver that support this DevicePath - @param RemainingDevicePath Remaining DevicePath nodes that do not match the driver DevicePath - - @retval EFI_SUCCESS A driver that matches the Device Path has been found - @retval EFI_NOT_FOUND No handles match the search. - @retval EFI_INVALID_PARAMETER DevicePath or Handle is NULL - +/** + Connect a Device Path and return the handle of the driver that support this DevicePath + + @param DevicePath Device Path of the File to connect + @param Handle Handle of the driver that support this DevicePath + @param RemainingDevicePath Remaining DevicePath nodes that do not match the driver DevicePath + + @retval EFI_SUCCESS A driver that matches the Device Path has been found + @retval EFI_NOT_FOUND No handles match the search. + @retval EFI_INVALID_PARAMETER DevicePath or Handle is NULL + **/ -EFI_STATUS -BdsConnectDevicePath ( - IN EFI_DEVICE_PATH_PROTOCOL* DevicePath, - OUT EFI_HANDLE *Handle, - OUT EFI_DEVICE_PATH_PROTOCOL **RemainingDevicePath - ) -{ - return BdsConnectAndUpdateDevicePath (&DevicePath, Handle, RemainingDevicePath); -} - +EFI_STATUS +BdsConnectDevicePath ( + IN EFI_DEVICE_PATH_PROTOCOL* DevicePath, + OUT EFI_HANDLE *Handle, + OUT EFI_DEVICE_PATH_PROTOCOL **RemainingDevicePath + ) +{ + return BdsConnectAndUpdateDevicePath (&DevicePath, Handle, RemainingDevicePath); +} + BOOLEAN BdsFileSystemSupport ( IN EFI_DEVICE_PATH *DevicePath, @@ -414,27 +464,34 @@ BdsFileSystemSupport ( EFI_STATUS BdsFileSystemLoadImage ( - IN EFI_DEVICE_PATH *DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH *RemainingDevicePath, + IN OUT EFI_DEVICE_PATH **DevicePath, + IN EFI_HANDLE Handle, + IN EFI_DEVICE_PATH *RemainingDevicePath, IN EFI_ALLOCATE_TYPE Type, - IN OUT EFI_PHYSICAL_ADDRESS* Image, + IN OUT EFI_PHYSICAL_ADDRESS *Image, OUT UINTN *ImageSize ) { - FILEPATH_DEVICE_PATH* FilePathDevicePath; - EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FsProtocol; - EFI_FILE_PROTOCOL *Fs; - EFI_STATUS Status; - EFI_FILE_INFO *FileInfo; - EFI_FILE_PROTOCOL *File; - UINTN Size; + EFI_STATUS Status; + FILEPATH_DEVICE_PATH *FilePathDevicePath; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FsProtocol; + EFI_FILE_PROTOCOL *Fs; + EFI_FILE_INFO *FileInfo; + EFI_FILE_PROTOCOL *File; + UINTN Size; ASSERT (IS_DEVICE_PATH_NODE (RemainingDevicePath, MEDIA_DEVICE_PATH, MEDIA_FILEPATH_DP)); FilePathDevicePath = (FILEPATH_DEVICE_PATH*)RemainingDevicePath; - Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&FsProtocol); + Status = gBS->OpenProtocol ( + Handle, + &gEfiSimpleFileSystemProtocolGuid, + (VOID**)&FsProtocol, + gImageHandle, + Handle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); if (EFI_ERROR (Status)) { return Status; } @@ -442,13 +499,12 @@ BdsFileSystemLoadImage ( // Try to Open the volume and get root directory Status = FsProtocol->OpenVolume (FsProtocol, &Fs); if (EFI_ERROR (Status)) { - return Status; + goto CLOSE_PROTOCOL; } - File = NULL; Status = Fs->Open (Fs, &File, FilePathDevicePath->PathName, EFI_FILE_MODE_READ, 0); if (EFI_ERROR (Status)) { - return Status; + goto CLOSE_PROTOCOL; } Size = 0; @@ -456,7 +512,7 @@ BdsFileSystemLoadImage ( FileInfo = AllocatePool (Size); Status = File->GetInfo (File, &gEfiFileInfoGuid, &Size, FileInfo); if (EFI_ERROR (Status)) { - return Status; + goto CLOSE_FILE; } // Get the file size @@ -475,6 +531,16 @@ BdsFileSystemLoadImage ( Status = File->Read (File, &Size, (VOID*)(UINTN)(*Image)); } +CLOSE_FILE: + File->Close (File); + +CLOSE_PROTOCOL: + gBS->CloseProtocol ( + Handle, + &gEfiSimpleFileSystemProtocolGuid, + gImageHandle, + Handle); + return Status; } @@ -491,9 +557,9 @@ BdsMemoryMapSupport ( EFI_STATUS BdsMemoryMapLoadImage ( - IN EFI_DEVICE_PATH *DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH *RemainingDevicePath, + IN OUT EFI_DEVICE_PATH **DevicePath, + IN EFI_HANDLE Handle, + IN EFI_DEVICE_PATH *RemainingDevicePath, IN EFI_ALLOCATE_TYPE Type, IN OUT EFI_PHYSICAL_ADDRESS* Image, OUT UINTN *ImageSize @@ -506,8 +572,8 @@ BdsMemoryMapLoadImage ( if (IS_DEVICE_PATH_NODE (RemainingDevicePath, HARDWARE_DEVICE_PATH, HW_MEMMAP_DP)) { MemMapPathDevicePath = (MEMMAP_DEVICE_PATH*)RemainingDevicePath; } else { - ASSERT (IS_DEVICE_PATH_NODE (DevicePath, HARDWARE_DEVICE_PATH, HW_MEMMAP_DP)); - MemMapPathDevicePath = (MEMMAP_DEVICE_PATH*)DevicePath; + ASSERT (IS_DEVICE_PATH_NODE (*DevicePath, HARDWARE_DEVICE_PATH, HW_MEMMAP_DP)); + MemMapPathDevicePath = (MEMMAP_DEVICE_PATH*)*DevicePath; } Size = MemMapPathDevicePath->EndingAddress - MemMapPathDevicePath->StartingAddress; @@ -543,9 +609,9 @@ BdsFirmwareVolumeSupport ( EFI_STATUS BdsFirmwareVolumeLoadImage ( - IN EFI_DEVICE_PATH *DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH *RemainingDevicePath, + IN OUT EFI_DEVICE_PATH **DevicePath, + IN EFI_HANDLE Handle, + IN EFI_DEVICE_PATH *RemainingDevicePath, IN EFI_ALLOCATE_TYPE Type, IN OUT EFI_PHYSICAL_ADDRESS* Image, OUT UINTN *ImageSize @@ -628,7 +694,7 @@ BdsFirmwareVolumeLoadImage ( Status = FwVol->ReadFile ( FwVol, FvNameGuid, - (VOID*)(UINTN)(*Image), + (VOID**)Image, ImageSize, &FvType, &Attrib, @@ -664,12 +730,12 @@ BdsPxeSupport ( EFI_STATUS BdsPxeLoadImage ( - IN EFI_DEVICE_PATH* DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH* RemainingDevicePath, - IN EFI_ALLOCATE_TYPE Type, - IN OUT EFI_PHYSICAL_ADDRESS *Image, - OUT UINTN *ImageSize + IN OUT EFI_DEVICE_PATH **DevicePath, + IN EFI_HANDLE Handle, + IN EFI_DEVICE_PATH *RemainingDevicePath, + IN EFI_ALLOCATE_TYPE Type, + IN OUT EFI_PHYSICAL_ADDRESS* Image, + OUT UINTN *ImageSize ) { EFI_STATUS Status; @@ -683,14 +749,14 @@ BdsPxeLoadImage ( return Status; } - Status = LoadFileProtocol->LoadFile (LoadFileProtocol, DevicePath, TRUE, &BufferSize, NULL); + Status = LoadFileProtocol->LoadFile (LoadFileProtocol, RemainingDevicePath, TRUE, &BufferSize, NULL); if (Status == EFI_BUFFER_TOO_SMALL) { Status = gBS->AllocatePages (Type, EfiBootServicesCode, EFI_SIZE_TO_PAGES(BufferSize), Image); if (EFI_ERROR (Status)) { return Status; } - Status = LoadFileProtocol->LoadFile (LoadFileProtocol, DevicePath, TRUE, &BufferSize, (VOID*)(UINTN)(*Image)); + Status = LoadFileProtocol->LoadFile (LoadFileProtocol, RemainingDevicePath, TRUE, &BufferSize, (VOID*)(UINTN)(*Image)); if (!EFI_ERROR (Status) && (ImageSize != NULL)) { *ImageSize = BufferSize; } @@ -710,14 +776,14 @@ BdsPxeLoadImage ( BOOLEAN BdsTftpSupport ( - IN EFI_DEVICE_PATH* DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH* RemainingDevicePath + IN EFI_DEVICE_PATH *DevicePath, + IN EFI_HANDLE Handle, + IN EFI_DEVICE_PATH *RemainingDevicePath ) { - EFI_STATUS Status; + EFI_STATUS Status; EFI_DEVICE_PATH *NextDevicePath; - EFI_PXE_BASE_CODE_PROTOCOL *PxeBcProtocol; + VOID *Interface; // Validate the Remaining Device Path if (IsDevicePathEnd (RemainingDevicePath)) { @@ -735,139 +801,504 @@ BdsTftpSupport ( return FALSE; } - Status = gBS->HandleProtocol (Handle, &gEfiPxeBaseCodeProtocolGuid, (VOID **)&PxeBcProtocol); + Status = gBS->HandleProtocol ( + Handle, &gEfiDevicePathProtocolGuid, + &Interface + ); if (EFI_ERROR (Status)) { return FALSE; - } else { - return TRUE; } + + // + // Check that the controller (identified by its handle "Handle") supports the + // MTFTPv4 Service Binding Protocol. If it does, it means that it supports the + // EFI MTFTPv4 Protocol needed to download the image through TFTP. + // + Status = gBS->HandleProtocol ( + Handle, &gEfiMtftp4ServiceBindingProtocolGuid, + &Interface + ); + if (EFI_ERROR (Status)) { + return FALSE; + } + + return TRUE; +} + +/** + Worker function that get the size in numbers of bytes of a file from a TFTP + server before to download the file. + + @param[in] Mtftp4 MTFTP4 protocol interface + @param[in] FilePath Path of the file, Ascii encoded + @param[out] FileSize Address where to store the file size in number of + bytes. + + @retval EFI_SUCCESS The size of the file was returned. + @retval !EFI_SUCCESS The size of the file was not returned. + +**/ +STATIC +EFI_STATUS +Mtftp4GetFileSize ( + IN EFI_MTFTP4_PROTOCOL *Mtftp4, + IN CHAR8 *FilePath, + OUT UINT64 *FileSize + ) +{ + EFI_STATUS Status; + EFI_MTFTP4_OPTION ReqOpt[1]; + EFI_MTFTP4_PACKET *Packet; + UINT32 PktLen; + EFI_MTFTP4_OPTION *TableOfOptions; + EFI_MTFTP4_OPTION *Option; + UINT32 OptCnt; + UINT8 OptBuf[128]; + + ReqOpt[0].OptionStr = (UINT8*)"tsize"; + OptBuf[0] = '0'; + OptBuf[1] = 0; + ReqOpt[0].ValueStr = OptBuf; + + Status = Mtftp4->GetInfo ( + Mtftp4, + NULL, + (UINT8*)FilePath, + NULL, + 1, + ReqOpt, + &PktLen, + &Packet + ); + + if (EFI_ERROR (Status)) { + goto Error; + } + + Status = Mtftp4->ParseOptions ( + Mtftp4, + PktLen, + Packet, + (UINT32 *) &OptCnt, + &TableOfOptions + ); + if (EFI_ERROR (Status)) { + goto Error; + } + + Option = TableOfOptions; + while (OptCnt != 0) { + if (AsciiStrnCmp ((CHAR8 *)Option->OptionStr, "tsize", 5) == 0) { + *FileSize = AsciiStrDecimalToUint64 ((CHAR8 *)Option->ValueStr); + break; + } + OptCnt--; + Option++; + } + FreePool (TableOfOptions); + + if (OptCnt == 0) { + Status = EFI_UNSUPPORTED; + } + +Error : + + return Status; +} + +/** + Update the progress of a file download + This procedure is called each time a new TFTP packet is received. + + @param[in] This MTFTP4 protocol interface + @param[in] Token Parameters for the download of the file + @param[in] PacketLen Length of the packet + @param[in] Packet Address of the packet + + @retval EFI_SUCCESS All packets are accepted. + +**/ +STATIC +EFI_STATUS +Mtftp4CheckPacket ( + IN EFI_MTFTP4_PROTOCOL *This, + IN EFI_MTFTP4_TOKEN *Token, + IN UINT16 PacketLen, + IN EFI_MTFTP4_PACKET *Packet + ) +{ + BDS_TFTP_CONTEXT *Context; + CHAR16 Progress[TFTP_PROGRESS_MESSAGE_SIZE]; + UINT64 NbOfKb; + UINTN Index; + UINTN LastStep; + UINTN Step; + UINT64 LastNbOf50Kb; + UINT64 NbOf50Kb; + + if ((NTOHS (Packet->OpCode)) == EFI_MTFTP4_OPCODE_DATA) { + Context = (BDS_TFTP_CONTEXT*)Token->Context; + + if (Context->DownloadedNbOfBytes == 0) { + if (Context->FileSize > 0) { + Print (L"%s 0 Kb", mTftpProgressFrame); + } else { + Print (L" 0 Kb"); + } + } + + // + // The data is the packet are prepended with two UINT16 : + // . OpCode = EFI_MTFTP4_OPCODE_DATA + // . Block = the number of this block of data + // + Context->DownloadedNbOfBytes += PacketLen - sizeof (Packet->OpCode) - sizeof (Packet->Data.Block); + NbOfKb = Context->DownloadedNbOfBytes / 1024; + + Progress[0] = L'\0'; + if (Context->FileSize > 0) { + LastStep = (Context->LastReportedNbOfBytes * TFTP_PROGRESS_SLIDER_STEPS) / Context->FileSize; + Step = (Context->DownloadedNbOfBytes * TFTP_PROGRESS_SLIDER_STEPS) / Context->FileSize; + if (Step > LastStep) { + Print (mTftpProgressDelete); + CopyMem (Progress, mTftpProgressFrame, sizeof mTftpProgressFrame); + for (Index = 1; Index < Step; Index++) { + Progress[Index] = L'='; + } + Progress[Step] = L'>'; + + UnicodeSPrint ( + Progress + (sizeof (mTftpProgressFrame) / sizeof (CHAR16)) - 1, + sizeof (Progress) - sizeof (mTftpProgressFrame), + L" %7d Kb", + NbOfKb + ); + Context->LastReportedNbOfBytes = Context->DownloadedNbOfBytes; + } + } else { + // + // Case when we do not know the size of the final file. + // We print the updated size every 50KB of downloaded data + // + LastNbOf50Kb = Context->LastReportedNbOfBytes / (50*1024); + NbOf50Kb = Context->DownloadedNbOfBytes / (50*1024); + if (NbOf50Kb > LastNbOf50Kb) { + Print (L"\b\b\b\b\b\b\b\b\b\b"); + UnicodeSPrint (Progress, sizeof (Progress), L"%7d Kb", NbOfKb); + Context->LastReportedNbOfBytes = Context->DownloadedNbOfBytes; + } + } + if (Progress[0] != L'\0') { + Print (L"%s", Progress); + } + } + + return EFI_SUCCESS; } +/** + Download an image from a TFTP server + + @param[in] DevicePath Device path of the TFTP boot option + @param[in] ControllerHandle Handle of the network controller + @param[in] RemainingDevicePath Device path of the TFTP boot option but + the first node that identifies the network controller + @param[in] Type Type to allocate memory pages + @param[out] Image Address of the bufer where the image is stored in + case of success + @param[out] ImageSize Size in number of bytes of the i;age in case of + success + + @retval EFI_SUCCESS The image was returned. + @retval !EFI_SUCCESS Something went wrong. + +**/ EFI_STATUS BdsTftpLoadImage ( - IN EFI_DEVICE_PATH* DevicePath, - IN EFI_HANDLE Handle, - IN EFI_DEVICE_PATH* RemainingDevicePath, - IN EFI_ALLOCATE_TYPE Type, - IN OUT EFI_PHYSICAL_ADDRESS *Image, - OUT UINTN *ImageSize + IN OUT EFI_DEVICE_PATH **DevicePath, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH *RemainingDevicePath, + IN EFI_ALLOCATE_TYPE Type, + IN OUT EFI_PHYSICAL_ADDRESS *Image, + OUT UINTN *ImageSize ) { - EFI_STATUS Status; - EFI_PXE_BASE_CODE_PROTOCOL *Pxe; - UINT64 TftpBufferSize; - EFI_IP_ADDRESS ServerIp; - IPv4_DEVICE_PATH* IPv4DevicePathNode; - FILEPATH_DEVICE_PATH* FilePathDevicePath; - EFI_IP_ADDRESS LocalIp; - CHAR8* AsciiPathName; - EFI_SIMPLE_NETWORK_PROTOCOL *Snp; + EFI_STATUS Status; + EFI_HANDLE Dhcp4ChildHandle; + EFI_DHCP4_PROTOCOL *Dhcp4; + BOOLEAN Dhcp4ToStop; + EFI_HANDLE Mtftp4ChildHandle; + EFI_MTFTP4_PROTOCOL *Mtftp4; + DHCP4_OPTION ParaList; + EFI_DHCP4_PACKET_OPTION *OptionList[2]; + EFI_DHCP4_CONFIG_DATA Dhcp4CfgData; + EFI_DHCP4_MODE_DATA Dhcp4Mode; + EFI_MTFTP4_CONFIG_DATA Mtftp4CfgData; + IPv4_DEVICE_PATH *IPv4DevicePathNode; + CHAR16 *PathName; + CHAR8 *AsciiFilePath; + EFI_MTFTP4_TOKEN Mtftp4Token; + UINT64 FileSize; + UINT64 TftpBufferSize; + BDS_TFTP_CONTEXT *TftpContext; + UINTN PathNameLen; ASSERT(IS_DEVICE_PATH_NODE (RemainingDevicePath, MESSAGING_DEVICE_PATH, MSG_IPv4_DP)); - IPv4DevicePathNode = (IPv4_DEVICE_PATH*)RemainingDevicePath; - FilePathDevicePath = (FILEPATH_DEVICE_PATH*)(IPv4DevicePathNode + 1); - Status = gBS->LocateProtocol (&gEfiPxeBaseCodeProtocolGuid, NULL, (VOID **)&Pxe); - if (EFI_ERROR (Status)) { - return Status; + Dhcp4ChildHandle = NULL; + Dhcp4 = NULL; + Dhcp4ToStop = FALSE; + Mtftp4ChildHandle = NULL; + Mtftp4 = NULL; + AsciiFilePath = NULL; + TftpContext = NULL; + + if (!IPv4DevicePathNode->StaticIpAddress) { + // + // Using the DHCP4 Service Binding Protocol, create a child handle of the DHCP4 service and + // install the DHCP4 protocol on it. Then, open the DHCP protocol. + // + Status = NetLibCreateServiceChild ( + ControllerHandle, + gImageHandle, + &gEfiDhcp4ServiceBindingProtocolGuid, + &Dhcp4ChildHandle + ); + if (!EFI_ERROR (Status)) { + Status = gBS->OpenProtocol ( + Dhcp4ChildHandle, + &gEfiDhcp4ProtocolGuid, + (VOID **) &Dhcp4, + gImageHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + } + if (EFI_ERROR (Status)) { + Print (L"Unable to open DHCP4 protocol\n"); + goto Error; + } } - Status = Pxe->Start (Pxe, FALSE); - if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) { - return Status; + // + // Using the MTFTP4 Service Binding Protocol, create a child handle of the MTFTP4 service and + // install the MTFTP4 protocol on it. Then, open the MTFTP4 protocol. + // + Status = NetLibCreateServiceChild ( + ControllerHandle, + gImageHandle, + &gEfiMtftp4ServiceBindingProtocolGuid, + &Mtftp4ChildHandle + ); + if (!EFI_ERROR (Status)) { + Status = gBS->OpenProtocol ( + Mtftp4ChildHandle, + &gEfiMtftp4ProtocolGuid, + (VOID **) &Mtftp4, + gImageHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + } + if (EFI_ERROR (Status)) { + Print (L"Unable to open MTFTP4 protocol\n"); + goto Error; } - do { - if (!IPv4DevicePathNode->StaticIpAddress) { - Status = Pxe->Dhcp (Pxe, TRUE); - } else { - CopyMem (&LocalIp.v4, &IPv4DevicePathNode->LocalIpAddress, sizeof (EFI_IPv4_ADDRESS)); - Status = Pxe->SetStationIp (Pxe, &LocalIp, NULL); - } - - // If an IP Address has already been set and a different static IP address is requested then restart - // the Network service. - if (Status == EFI_ALREADY_STARTED) { - Status = gBS->LocateProtocol (&gEfiSimpleNetworkProtocolGuid, NULL, (VOID **)&Snp); - if (!EFI_ERROR (Status) && IPv4DevicePathNode->StaticIpAddress && - (CompareMem (&Snp->Mode->CurrentAddress, &IPv4DevicePathNode->LocalIpAddress, sizeof(EFI_MAC_ADDRESS)) != 0)) - { - Pxe->Stop (Pxe); - Status = Pxe->Start (Pxe, FALSE); - if (EFI_ERROR(Status)) { - break; - } - // After restarting the PXE protocol, we want to try again with our new IP Address - Status = EFI_ALREADY_STARTED; + if (!IPv4DevicePathNode->StaticIpAddress) { + // + // Configure the DHCP4, all default settings. It is acceptable for the configuration to + // fail if the return code is equal to EFI_ACCESS_DENIED which means that the configuration + // has been done by another instance of the DHCP4 protocol or that the DHCP configuration + // process has been started but is not completed yet. + // + ZeroMem (&Dhcp4CfgData, sizeof (EFI_DHCP4_CONFIG_DATA)); + ParaList.Head.OpCode = DHCP_TAG_PARA_LIST; + ParaList.Head.Length = 2; + ParaList.Head.Data[0] = DHCP_TAG_NETMASK; + ParaList.Route = DHCP_TAG_ROUTER; + OptionList[0] = &ParaList.Head; + Dhcp4CfgData.OptionCount = 1; + Dhcp4CfgData.OptionList = OptionList; + + Status = Dhcp4->Configure (Dhcp4, &Dhcp4CfgData); + if (EFI_ERROR (Status)) { + if (Status != EFI_ACCESS_DENIED) { + Print (L"Error while configuring the DHCP4 protocol\n"); + goto Error; } } - } while (Status == EFI_ALREADY_STARTED); - if (EFI_ERROR(Status)) { - return Status; + // + // Start the DHCP configuration. This may have already been done thus do not leave in error + // if the return code is EFI_ALREADY_STARTED. + // + Status = Dhcp4->Start (Dhcp4, NULL); + if (EFI_ERROR (Status)) { + if (Status != EFI_ALREADY_STARTED) { + Print (L"DHCP configuration failed\n"); + goto Error; + } + } else { + Dhcp4ToStop = TRUE; + } + + Status = Dhcp4->GetModeData (Dhcp4, &Dhcp4Mode); + if (EFI_ERROR (Status)) { + goto Error; + } + + if (Dhcp4Mode.State != Dhcp4Bound) { + Status = EFI_TIMEOUT; + Print (L"DHCP configuration failed\n"); + goto Error; + } + } + + // + // Configure the TFTP4 protocol + // + + ZeroMem (&Mtftp4CfgData, sizeof (EFI_MTFTP4_CONFIG_DATA)); + Mtftp4CfgData.UseDefaultSetting = FALSE; + Mtftp4CfgData.TimeoutValue = 4; + Mtftp4CfgData.TryCount = 6; + + if (IPv4DevicePathNode->StaticIpAddress) { + CopyMem (&Mtftp4CfgData.StationIp , &IPv4DevicePathNode->LocalIpAddress, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Mtftp4CfgData.SubnetMask, &IPv4DevicePathNode->SubnetMask, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Mtftp4CfgData.GatewayIp , &IPv4DevicePathNode->GatewayIpAddress, sizeof (EFI_IPv4_ADDRESS)); + } else { + CopyMem (&Mtftp4CfgData.StationIp , &Dhcp4Mode.ClientAddress, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Mtftp4CfgData.SubnetMask, &Dhcp4Mode.SubnetMask , sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Mtftp4CfgData.GatewayIp , &Dhcp4Mode.RouterAddress, sizeof (EFI_IPv4_ADDRESS)); } - CopyMem (&ServerIp.v4, &IPv4DevicePathNode->RemoteIpAddress, sizeof (EFI_IPv4_ADDRESS)); + CopyMem (&Mtftp4CfgData.ServerIp , &IPv4DevicePathNode->RemoteIpAddress, sizeof (EFI_IPv4_ADDRESS)); - // Convert the Unicode PathName to Ascii - AsciiPathName = AllocatePool ((StrLen (FilePathDevicePath->PathName) + 1) * sizeof (CHAR8)); - if (AsciiPathName == NULL) { - return EFI_OUT_OF_RESOURCES; + Status = Mtftp4->Configure (Mtftp4, &Mtftp4CfgData); + if (EFI_ERROR (Status)) { + Print (L"Error while configuring the MTFTP4 protocol\n"); + goto Error; } - UnicodeStrToAsciiStr (FilePathDevicePath->PathName, AsciiPathName); - Status = Pxe->Mtftp ( - Pxe, - EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, - NULL, - FALSE, - &TftpBufferSize, - NULL, - &ServerIp, - (UINT8*)AsciiPathName, - NULL, - FALSE - ); - if (EFI_ERROR(Status)) { - if (Status == EFI_TFTP_ERROR) { - DEBUG((EFI_D_ERROR, "TFTP Error: Fail to get the size of the file\n")); + // The Device Path might contain multiple FilePath nodes + PathName = ConvertDevicePathToText ((EFI_DEVICE_PATH_PROTOCOL*)(IPv4DevicePathNode + 1), FALSE, FALSE); + PathNameLen = StrLen (PathName) + 1; + AsciiFilePath = AllocatePool (PathNameLen); + UnicodeStrToAsciiStrS (PathName, AsciiFilePath, PathNameLen); + + // + // Try to get the size of the file in bytes from the server. If it fails, + // start with a 8MB buffer to download the file. + // + FileSize = 0; + if (Mtftp4GetFileSize (Mtftp4, AsciiFilePath, &FileSize) == EFI_SUCCESS) { + TftpBufferSize = FileSize; + } else { + TftpBufferSize = SIZE_16MB; + } + + TftpContext = AllocatePool (sizeof (BDS_TFTP_CONTEXT)); + if (TftpContext == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Error; + } + TftpContext->FileSize = FileSize; + + for (; TftpBufferSize <= FixedPcdGet32 (PcdMaxTftpFileSize); + TftpBufferSize = (TftpBufferSize + SIZE_16MB) & (~(SIZE_16MB-1))) { + // + // Allocate a buffer to hold the whole file. + // + Status = gBS->AllocatePages ( + Type, + EfiBootServicesCode, + EFI_SIZE_TO_PAGES (TftpBufferSize), + Image + ); + if (EFI_ERROR (Status)) { + Print (L"Failed to allocate space for image\n"); + goto Error; } - goto EXIT; + + TftpContext->DownloadedNbOfBytes = 0; + TftpContext->LastReportedNbOfBytes = 0; + + ZeroMem (&Mtftp4Token, sizeof (EFI_MTFTP4_TOKEN)); + Mtftp4Token.Filename = (UINT8*)AsciiFilePath; + Mtftp4Token.BufferSize = TftpBufferSize; + Mtftp4Token.Buffer = (VOID *)(UINTN)*Image; + Mtftp4Token.CheckPacket = Mtftp4CheckPacket; + Mtftp4Token.Context = (VOID*)TftpContext; + + Print (L"Downloading the file <%a> from the TFTP server\n", AsciiFilePath); + Status = Mtftp4->ReadFile (Mtftp4, &Mtftp4Token); + Print (L"\n"); + if (EFI_ERROR (Status)) { + gBS->FreePages (*Image, EFI_SIZE_TO_PAGES (TftpBufferSize)); + if (Status == EFI_BUFFER_TOO_SMALL) { + Print (L"Downloading failed, file larger than expected.\n"); + continue; + } else { + goto Error; + } + } + + *ImageSize = Mtftp4Token.BufferSize; + break; + } + +Error: + if (Dhcp4ChildHandle != NULL) { + if (Dhcp4 != NULL) { + if (Dhcp4ToStop) { + Dhcp4->Stop (Dhcp4); + } + gBS->CloseProtocol ( + Dhcp4ChildHandle, + &gEfiDhcp4ProtocolGuid, + gImageHandle, + ControllerHandle + ); + } + NetLibDestroyServiceChild ( + ControllerHandle, + gImageHandle, + &gEfiDhcp4ServiceBindingProtocolGuid, + Dhcp4ChildHandle + ); + } + + if (Mtftp4ChildHandle != NULL) { + if (Mtftp4 != NULL) { + if (AsciiFilePath != NULL) { + FreePool (AsciiFilePath); + } + if (TftpContext != NULL) { + FreePool (TftpContext); + } + gBS->CloseProtocol ( + Mtftp4ChildHandle, + &gEfiMtftp4ProtocolGuid, + gImageHandle, + ControllerHandle + ); + } + NetLibDestroyServiceChild ( + ControllerHandle, + gImageHandle, + &gEfiMtftp4ServiceBindingProtocolGuid, + Mtftp4ChildHandle + ); } - // Allocate a buffer to hold the whole file. - Status = gBS->AllocatePages ( - Type, - EfiBootServicesCode, - EFI_SIZE_TO_PAGES (TftpBufferSize), - Image - ); - if (EFI_ERROR (Status)) { - DEBUG ((EFI_D_ERROR, "Failed to allocate space for kernel image: %r\n", Status)); - goto EXIT; - } - - Status = Pxe->Mtftp ( - Pxe, - EFI_PXE_BASE_CODE_TFTP_READ_FILE, - (VOID *)(UINTN)*Image, - FALSE, - &TftpBufferSize, - NULL, - &ServerIp, - (UINT8*)AsciiPathName, - NULL, - FALSE - ); if (EFI_ERROR (Status)) { - gBS->FreePages (*Image, EFI_SIZE_TO_PAGES (TftpBufferSize)); - } else { - *ImageSize = (UINTN)TftpBufferSize; + *Image = 0; + Print (L"Failed to download the file - Error=%r\n", Status); } -EXIT: - FreePool (AsciiPathName); return Status; } @@ -882,8 +1313,8 @@ BDS_FILE_LOADER FileLoaders[] = { }; EFI_STATUS -BdsLoadImageAndUpdateDevicePath ( - IN OUT EFI_DEVICE_PATH **DevicePath, +BdsLoadImageAndUpdateDevicePath ( + IN OUT EFI_DEVICE_PATH **DevicePath, IN EFI_ALLOCATE_TYPE Type, IN OUT EFI_PHYSICAL_ADDRESS* Image, OUT UINTN *FileSize @@ -894,15 +1325,15 @@ BdsLoadImageAndUpdateDevicePath ( EFI_DEVICE_PATH *RemainingDevicePath; BDS_FILE_LOADER* FileLoader; - Status = BdsConnectAndUpdateDevicePath (DevicePath, &Handle, &RemainingDevicePath); + Status = BdsConnectAndUpdateDevicePath (DevicePath, &Handle, &RemainingDevicePath); if (EFI_ERROR (Status)) { return Status; } FileLoader = FileLoaders; while (FileLoader->Support != NULL) { - if (FileLoader->Support (*DevicePath, Handle, RemainingDevicePath)) { - return FileLoader->LoadImage (*DevicePath, Handle, RemainingDevicePath, Type, Image, FileSize); + if (FileLoader->Support (*DevicePath, Handle, RemainingDevicePath)) { + return FileLoader->LoadImage (DevicePath, Handle, RemainingDevicePath, Type, Image, FileSize); } FileLoader++; } @@ -911,16 +1342,16 @@ BdsLoadImageAndUpdateDevicePath ( } EFI_STATUS -BdsLoadImage ( - IN EFI_DEVICE_PATH *DevicePath, - IN EFI_ALLOCATE_TYPE Type, - IN OUT EFI_PHYSICAL_ADDRESS* Image, - OUT UINTN *FileSize - ) -{ - return BdsLoadImageAndUpdateDevicePath (&DevicePath, Type, Image, FileSize); -} - +BdsLoadImage ( + IN EFI_DEVICE_PATH *DevicePath, + IN EFI_ALLOCATE_TYPE Type, + IN OUT EFI_PHYSICAL_ADDRESS* Image, + OUT UINTN *FileSize + ) +{ + return BdsLoadImageAndUpdateDevicePath (&DevicePath, Type, Image, FileSize); +} + /** Start an EFI Application from a Device Path @@ -947,7 +1378,7 @@ BdsStartEfiApplication ( EFI_LOADED_IMAGE_PROTOCOL* LoadedImage; // Find the nearest supported file loader - Status = BdsLoadImageAndUpdateDevicePath (&DevicePath, AllocateAnyPages, &BinaryBuffer, &BinarySize); + Status = BdsLoadImageAndUpdateDevicePath (&DevicePath, AllocateAnyPages, &BinaryBuffer, &BinarySize); if (EFI_ERROR (Status)) { return Status; }