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