/** @file | |
Provides 'initrd' dynamic UEFI shell command to load a Linux initrd | |
via its GUIDed vendor media path | |
Copyright (c) 2020, Arm, Ltd. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Uefi.h> | |
#include <Library/DebugLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/HiiLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/ShellLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiHiiServicesLib.h> | |
#include <Guid/LinuxEfiInitrdMedia.h> | |
#include <Protocol/DevicePath.h> | |
#include <Protocol/HiiPackageList.h> | |
#include <Protocol/LoadFile2.h> | |
#include <Protocol/ShellDynamicCommand.h> | |
#pragma pack (1) | |
typedef struct { | |
VENDOR_DEVICE_PATH VenMediaNode; | |
EFI_DEVICE_PATH_PROTOCOL EndNode; | |
} SINGLE_NODE_VENDOR_MEDIA_DEVPATH; | |
#pragma pack () | |
STATIC EFI_HII_HANDLE mLinuxInitrdShellCommandHiiHandle; | |
STATIC EFI_PHYSICAL_ADDRESS mInitrdFileAddress; | |
STATIC UINTN mInitrdFileSize; | |
STATIC EFI_HANDLE mInitrdLoadFile2Handle; | |
STATIC CONST SHELL_PARAM_ITEM ParamList[] = { | |
{ L"-u", TypeFlag }, | |
{ NULL, TypeMax } | |
}; | |
STATIC CONST SINGLE_NODE_VENDOR_MEDIA_DEVPATH mInitrdDevicePath = { | |
{ | |
{ | |
MEDIA_DEVICE_PATH, MEDIA_VENDOR_DP,{ sizeof (VENDOR_DEVICE_PATH) } | |
}, | |
LINUX_EFI_INITRD_MEDIA_GUID | |
},{ | |
END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{ sizeof (EFI_DEVICE_PATH_PROTOCOL) } | |
} | |
}; | |
STATIC | |
BOOLEAN | |
IsOtherInitrdDevicePathAlreadyInstalled ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
EFI_HANDLE Handle; | |
DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)&mInitrdDevicePath; | |
Status = gBS->LocateDevicePath ( | |
&gEfiLoadFile2ProtocolGuid, | |
&DevicePath, | |
&Handle | |
); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
// | |
// Check whether the existing instance is one that we installed during | |
// a previous invocation. | |
// | |
if (Handle == mInitrdLoadFile2Handle) { | |
return FALSE; | |
} | |
return TRUE; | |
} | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
InitrdLoadFile2 ( | |
IN EFI_LOAD_FILE2_PROTOCOL *This, | |
IN EFI_DEVICE_PATH_PROTOCOL *FilePath, | |
IN BOOLEAN BootPolicy, | |
IN OUT UINTN *BufferSize, | |
OUT VOID *Buffer OPTIONAL | |
) | |
{ | |
if (BootPolicy) { | |
return EFI_UNSUPPORTED; | |
} | |
if ((BufferSize == NULL) || !IsDevicePathValid (FilePath, 0)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((FilePath->Type != END_DEVICE_PATH_TYPE) || | |
(FilePath->SubType != END_ENTIRE_DEVICE_PATH_SUBTYPE) || | |
(mInitrdFileSize == 0)) | |
{ | |
return EFI_NOT_FOUND; | |
} | |
if ((Buffer == NULL) || (*BufferSize < mInitrdFileSize)) { | |
*BufferSize = mInitrdFileSize; | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
ASSERT (mInitrdFileAddress != 0); | |
gBS->CopyMem (Buffer, (VOID *)(UINTN)mInitrdFileAddress, mInitrdFileSize); | |
*BufferSize = mInitrdFileSize; | |
return EFI_SUCCESS; | |
} | |
STATIC CONST EFI_LOAD_FILE2_PROTOCOL mInitrdLoadFile2 = { | |
InitrdLoadFile2, | |
}; | |
STATIC | |
EFI_STATUS | |
UninstallLoadFile2Protocol ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
if (mInitrdLoadFile2Handle != NULL) { | |
Status = gBS->UninstallMultipleProtocolInterfaces ( | |
mInitrdLoadFile2Handle, | |
&gEfiDevicePathProtocolGuid, | |
&mInitrdDevicePath, | |
&gEfiLoadFile2ProtocolGuid, | |
&mInitrdLoadFile2, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
mInitrdLoadFile2Handle = NULL; | |
} | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
STATIC | |
VOID | |
FreeInitrdFile ( | |
VOID | |
) | |
{ | |
if (mInitrdFileSize != 0) { | |
gBS->FreePages (mInitrdFileAddress, EFI_SIZE_TO_PAGES (mInitrdFileSize)); | |
mInitrdFileSize = 0; | |
} | |
} | |
STATIC | |
EFI_STATUS | |
CacheInitrdFile ( | |
IN SHELL_FILE_HANDLE FileHandle | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 FileSize; | |
UINTN ReadSize; | |
Status = gEfiShellProtocol->GetFileSize (FileHandle, &FileSize); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((FileSize == 0) || (FileSize > MAX_UINTN)) { | |
return EFI_UNSUPPORTED; | |
} | |
Status = gBS->AllocatePages ( | |
AllocateAnyPages, | |
EfiLoaderData, | |
EFI_SIZE_TO_PAGES ((UINTN)FileSize), | |
&mInitrdFileAddress | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
ReadSize = (UINTN)FileSize; | |
Status = gEfiShellProtocol->ReadFile ( | |
FileHandle, | |
&ReadSize, | |
(VOID *)(UINTN)mInitrdFileAddress | |
); | |
if (EFI_ERROR (Status) || (ReadSize < FileSize)) { | |
DEBUG (( | |
DEBUG_WARN, | |
"%a: failed to read initrd file - %r 0x%lx 0x%lx\n", | |
__func__, | |
Status, | |
(UINT64)ReadSize, | |
FileSize | |
)); | |
goto FreeMemory; | |
} | |
if (mInitrdLoadFile2Handle == NULL) { | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&mInitrdLoadFile2Handle, | |
&gEfiDevicePathProtocolGuid, | |
&mInitrdDevicePath, | |
&gEfiLoadFile2ProtocolGuid, | |
&mInitrdLoadFile2, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
mInitrdFileSize = (UINTN)FileSize; | |
return EFI_SUCCESS; | |
FreeMemory: | |
gBS->FreePages (mInitrdFileAddress, EFI_SIZE_TO_PAGES ((UINTN)FileSize)); | |
return Status; | |
} | |
/** | |
Function for 'initrd' command. | |
@param[in] ImageHandle Handle to the Image (NULL if Internal). | |
@param[in] SystemTable Pointer to the System Table (NULL if Internal). | |
**/ | |
STATIC | |
SHELL_STATUS | |
EFIAPI | |
RunInitrd ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
LIST_ENTRY *Package; | |
CHAR16 *ProblemParam; | |
CONST CHAR16 *Param; | |
CHAR16 *Filename; | |
SHELL_STATUS ShellStatus; | |
SHELL_FILE_HANDLE FileHandle; | |
ProblemParam = NULL; | |
ShellStatus = SHELL_SUCCESS; | |
Status = ShellInitialize (); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// parse the command line | |
// | |
Status = ShellCommandLineParse (ParamList, &Package, &ProblemParam, TRUE); | |
if (EFI_ERROR (Status)) { | |
if ((Status == EFI_VOLUME_CORRUPTED) && (ProblemParam != NULL)) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_PROBLEM), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd", | |
ProblemParam | |
); | |
FreePool (ProblemParam); | |
ShellStatus = SHELL_INVALID_PARAMETER; | |
} else { | |
ASSERT (FALSE); | |
} | |
} else if (IsOtherInitrdDevicePathAlreadyInstalled ()) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_ALREADY_INSTALLED), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd" | |
); | |
ShellStatus = SHELL_UNSUPPORTED; | |
} else { | |
if (ShellCommandLineGetCount (Package) > 2) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_TOO_MANY), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd" | |
); | |
ShellStatus = SHELL_INVALID_PARAMETER; | |
} else if (ShellCommandLineGetCount (Package) < 2) { | |
if (ShellCommandLineGetFlag (Package, L"-u")) { | |
FreeInitrdFile (); | |
UninstallLoadFile2Protocol (); | |
} else { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_TOO_FEW), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd" | |
); | |
ShellStatus = SHELL_INVALID_PARAMETER; | |
} | |
} else { | |
Param = ShellCommandLineGetRawValue (Package, 1); | |
ASSERT (Param != NULL); | |
Filename = ShellFindFilePath (Param); | |
if (Filename == NULL) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_FIND_FAIL), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd", | |
Param | |
); | |
ShellStatus = SHELL_NOT_FOUND; | |
} else { | |
Status = ShellOpenFileByName ( | |
Filename, | |
&FileHandle, | |
EFI_FILE_MODE_READ, | |
0 | |
); | |
if (!EFI_ERROR (Status)) { | |
FreeInitrdFile (); | |
Status = CacheInitrdFile (FileHandle); | |
ShellCloseFile (&FileHandle); | |
} | |
if (EFI_ERROR (Status)) { | |
ShellPrintHiiEx ( | |
-1, | |
-1, | |
NULL, | |
STRING_TOKEN (STR_GEN_FILE_OPEN_FAIL), | |
mLinuxInitrdShellCommandHiiHandle, | |
L"initrd", | |
Param | |
); | |
ShellStatus = SHELL_NOT_FOUND; | |
} | |
FreePool (Filename); | |
} | |
} | |
} | |
return ShellStatus; | |
} | |
/** | |
This is the shell command handler function pointer callback type. This | |
function handles the command when it is invoked in the shell. | |
@param[in] This The instance of the | |
EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL. | |
@param[in] SystemTable The pointer to the system table. | |
@param[in] ShellParameters The parameters associated with the command. | |
@param[in] Shell The instance of the shell protocol used in | |
the context of processing this command. | |
@return EFI_SUCCESS the operation was successful | |
@return other the operation failed. | |
**/ | |
SHELL_STATUS | |
EFIAPI | |
LinuxInitrdCommandHandler ( | |
IN EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *This, | |
IN EFI_SYSTEM_TABLE *SystemTable, | |
IN EFI_SHELL_PARAMETERS_PROTOCOL *ShellParameters, | |
IN EFI_SHELL_PROTOCOL *Shell | |
) | |
{ | |
gEfiShellParametersProtocol = ShellParameters; | |
gEfiShellProtocol = Shell; | |
return RunInitrd (gImageHandle, SystemTable); | |
} | |
/** | |
This is the command help handler function pointer callback type. This | |
function is responsible for displaying help information for the associated | |
command. | |
@param[in] This The instance of the | |
EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL. | |
@param[in] Language The pointer to the language string to use. | |
@return string Pool allocated help string, must be freed | |
by caller | |
**/ | |
STATIC | |
CHAR16 * | |
EFIAPI | |
LinuxInitrdGetHelp ( | |
IN EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *This, | |
IN CONST CHAR8 *Language | |
) | |
{ | |
return HiiGetString ( | |
mLinuxInitrdShellCommandHiiHandle, | |
STRING_TOKEN (STR_GET_HELP_INITRD), | |
Language | |
); | |
} | |
STATIC EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL mLinuxInitrdDynamicCommand = { | |
L"initrd", | |
LinuxInitrdCommandHandler, | |
LinuxInitrdGetHelp | |
}; | |
/** | |
Retrieve HII package list from ImageHandle and publish to HII database. | |
@param ImageHandle The image handle of the process. | |
@return HII handle. | |
**/ | |
STATIC | |
EFI_HII_HANDLE | |
InitializeHiiPackage ( | |
EFI_HANDLE ImageHandle | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HII_PACKAGE_LIST_HEADER *PackageList; | |
EFI_HII_HANDLE HiiHandle; | |
// | |
// Retrieve HII package list from ImageHandle | |
// | |
Status = gBS->OpenProtocol ( | |
ImageHandle, | |
&gEfiHiiPackageListProtocolGuid, | |
(VOID **)&PackageList, | |
ImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
ASSERT_EFI_ERROR (Status); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
// | |
// Publish HII package list to HII Database. | |
// | |
Status = gHiiDatabase->NewPackageList ( | |
gHiiDatabase, | |
PackageList, | |
NULL, | |
&HiiHandle | |
); | |
ASSERT_EFI_ERROR (Status); | |
if (EFI_ERROR (Status)) { | |
return NULL; | |
} | |
return HiiHandle; | |
} | |
/** | |
Entry point of Linux Initrd dynamic UEFI Shell command. | |
Produce the DynamicCommand protocol to handle "initrd" command. | |
@param ImageHandle The image handle of the process. | |
@param SystemTable The EFI System Table pointer. | |
@retval EFI_SUCCESS Initrd command is executed successfully. | |
@retval EFI_ABORTED HII package was failed to initialize. | |
@retval others Other errors when executing Initrd command. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LinuxInitrdDynamicShellCommandEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
mLinuxInitrdShellCommandHiiHandle = InitializeHiiPackage (ImageHandle); | |
if (mLinuxInitrdShellCommandHiiHandle == NULL) { | |
return EFI_ABORTED; | |
} | |
Status = gBS->InstallProtocolInterface ( | |
&ImageHandle, | |
&gEfiShellDynamicCommandProtocolGuid, | |
EFI_NATIVE_INTERFACE, | |
&mLinuxInitrdDynamicCommand | |
); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} | |
/** | |
Unload the dynamic UEFI Shell command. | |
@param ImageHandle The image handle of the process. | |
@retval EFI_SUCCESS The image is unloaded. | |
@retval Others Failed to unload the image. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
LinuxInitrdDynamicShellCommandUnload ( | |
IN EFI_HANDLE ImageHandle | |
) | |
{ | |
EFI_STATUS Status; | |
FreeInitrdFile (); | |
Status = UninstallLoadFile2Protocol (); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = gBS->UninstallProtocolInterface ( | |
ImageHandle, | |
&gEfiShellDynamicCommandProtocolGuid, | |
&mLinuxInitrdDynamicCommand | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
HiiRemovePackages (mLinuxInitrdShellCommandHiiHandle); | |
return EFI_SUCCESS; | |
} |