On 02/26/20 20:43, Ard Biesheuvel wrote: > Add a new 'initrd' command to the UEFI Shell that allows any file that is > accessible to the shell to be registered as the initrd that is returned > when Linux's EFI stub loader invokes the LoadFile2 protocol on its special > vendor media device path. > > Signed-off-by: Ard Biesheuvel <ard.biesheu...@linaro.org> > --- > OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.c | > 429 ++++++++++++++++++++ > OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf | > 53 +++ > OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.uni | > 49 +++ > 3 files changed, 531 insertions(+) > > diff --git > a/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.c > b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.c > new file mode 100644 > index 000000000000..7ddeba0df624 > --- /dev/null > +++ b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.c > @@ -0,0 +1,429 @@ > +/** @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 > +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
"BufferSize" is IN OUT, so if we wanted to align those annotations nicely, we'd do IN EFI_LOAD_FILE2_PROTOCOL *This, IN EFI_DEVICE_PATH_PROTOCOL *FilePath, IN BOOLEAN BootPolicy, IN OUT UINTN *BufferSize, OUT VOID *Buffer OPTIONAL But I'm fine with the current columns too. One request below: > + ) > +{ > + 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; > +} > + > +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 = 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; > + CONST 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 (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); (1) This FreePool() is in the wrong spot; FreePool() can't deal with NULL (unlike free() in standard C) in general. Please move FreePool() just one line higher up (and two spaces to the right); i.e., to the end of the "else" branch of the (Filename == NULL) check. With (1) addressed (no need to repost): Reviewed-by: Laszlo Ersek <ler...@redhat.com> Thanks! Laszlo > + } > + } > + 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; > +} > diff --git > a/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf > b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf > new file mode 100644 > index 000000000000..6da6ef6d7818 > --- /dev/null > +++ > b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf > @@ -0,0 +1,53 @@ > +## @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 > +# > +# > +## > + > +[Defines] > + INF_VERSION = 1.27 > + BASE_NAME = LinuxInitrdDynamicShellCommand > + FILE_GUID = 2f30da26-f51b-4b6f-85c4-31873c281bca > + MODULE_TYPE = DXE_DRIVER > + VERSION_STRING = 1.0 > + ENTRY_POINT = LinuxInitrdDynamicShellCommandEntryPoint > + UNLOAD_IMAGE = LinuxInitrdDynamicShellCommandUnload > + UEFI_HII_RESOURCE_SECTION = TRUE > + > +# > +# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 EBC > +# > + > +[Sources.common] > + LinuxInitrdDynamicShellCommand.c > + LinuxInitrdDynamicShellCommand.uni > + > +[Packages] > + MdePkg/MdePkg.dec > + ShellPkg/ShellPkg.dec > + MdeModulePkg/MdeModulePkg.dec > + OvmfPkg/OvmfPkg.dec > + > +[LibraryClasses] > + DebugLib > + DevicePathLib > + HiiLib > + MemoryAllocationLib > + ShellLib > + UefiBootServicesTableLib > + UefiDriverEntryPoint > + UefiHiiServicesLib > + > +[Protocols] > + gEfiDevicePathProtocolGuid ## SOMETIMES_PRODUCES > + gEfiHiiPackageListProtocolGuid ## CONSUMES > + gEfiLoadFile2ProtocolGuid ## SOMETIMES_PRODUCES > + gEfiShellDynamicCommandProtocolGuid ## PRODUCES > + > +[DEPEX] > + TRUE > diff --git > a/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.uni > b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.uni > new file mode 100644 > index 000000000000..a88fa6e3641b > --- /dev/null > +++ > b/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.uni > @@ -0,0 +1,49 @@ > +// /** > +// > +// Copyright (c) 2020, Arm, Ltd. All rights reserved.<BR> > +// SPDX-License-Identifier: BSD-2-Clause-Patent > +// > +// Module Name: > +// > +// LinuxInitrdDynamicShellCommand.uni > +// > +// Abstract: > +// > +// String definitions for 'initrd' UEFI Shell command > +// > +// **/ > + > +/=# > + > +#langdef en-US "english" > + > +#string STR_GEN_PROBLEM #language en-US "%H%s%N: Unknown flag - > '%H%s%N'\r\n" > +#string STR_GEN_TOO_MANY #language en-US "%H%s%N: Too many > arguments.\r\n" > +#string STR_GEN_TOO_FEW #language en-US "%H%s%N: Too few > arguments.\r\n" > +#string STR_GEN_FIND_FAIL #language en-US "%H%s%N: File not found - > '%H%s%N'\r\n" > +#string STR_GEN_FILE_OPEN_FAIL #language en-US "%H%s%N: Cannot open file > - '%H%s%N'\r\n" > + > +#string STR_GET_HELP_INITRD #language en-US "" > +".TH initrd 0 "Registers or unregisters a file as Linux initrd."\r\n" > +".SH NAME\r\n" > +"Registers or unregisters a file as Linux initrd.\r\n" > +".SH SYNOPSIS\r\n" > +" \r\n" > +"initrd <FileName>\r\n" > +"initrd -u\r\n" > +".SH OPTIONS\r\n" > +" \r\n" > +" FileName - Specifies a file to register as initrd.\r\n" > +" -u - Unregisters any previously registered initrd files.\r\n" > +".SH DESCRIPTION\r\n" > +" \r\n" > +"NOTES:\r\n" > +" 1. Only a single file can be loaded as initrd at any given time. Using > the\r\n" > +" command twice with a <FileName> option will result in the first file > to\r\n" > +" be unloaded again, regardless of whether the second invocation > succeeded\r\n" > +" or not.\r\n" > +" 2. The initrd is not unloaded when the shell exits, and will remain > active\r\n" > +" until it is unloaded again by a different invocation of the shell.\r\n" > +" Consumers of the LoadFile2 protocol on the > LINUX_EFI_INITRD_MEDIA_GUID\r\n" > +" device path that are started via means other than the shell will be > able\r\n" > +" to locate the protocol and invoke it.\r\n" > -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#54994): https://edk2.groups.io/g/devel/message/54994 Mute This Topic: https://groups.io/mt/71572292/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-