| /** @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; | |
| } |