/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #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_HANDLE HiiHandle; // // Publish HII package list to HII Database. // Status = gHiiDatabase->NewPackageList ( gHiiDatabase, &gModuleHiiPackageList->Header, 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; }