Adding Ray and Zhichao; comments below On 02/11/20 19:03, 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...@arm.com> > --- > Note that the vendor media device path initrd loading method is still > under review on the Linux side, so this is for discussion only for now. > > ArmVirtPkg/ArmVirt.dsc.inc | > 1 + > OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.c | > 269 ++++++++++++++++++++ > OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.inf | > 49 ++++ > OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.uni | > 37 +++ > OvmfPkg/OvmfPkg.dec | > 1 + > OvmfPkg/OvmfPkgIa32.dsc | > 1 + > OvmfPkg/OvmfPkgIa32X64.dsc | > 1 + > OvmfPkg/OvmfPkgX64.dsc | > 1 + > 8 files changed, 360 insertions(+)
(1) As usual (and I'm sure you're aware -- this is just an RFC), please split the ArmVirtPkg change to a different patch. (2) "ShellPkg/Application/Shell/Shell.inf" is part of "OvmfPkg/OvmfXen.dsc" too, so I'd suggest enabling the new shell command there as well. > diff --git > a/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.inf > b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.inf > new file mode 100644 > index 000000000000..ed8a0ca85e85 > --- /dev/null > +++ > b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.inf > @@ -0,0 +1,49 @@ > +## @file > +# Provides initrd 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 = LinuxInitrdShellCommandLib > + FILE_GUID = 2f30da26-f51b-4b6f-85c4-31873c281bca > + MODULE_TYPE = UEFI_APPLICATION > + VERSION_STRING = 1.0 > + LIBRARY_CLASS = NULL|UEFI_APPLICATION UEFI_DRIVER > + CONSTRUCTOR = LinuxInitrdShellCommandLibConstructor > + DESTRUCTOR = LinuxInitrdShellCommandLibDestructor > + > +# > +# VALID_ARCHITECTURES = IA32 X64 ARM AARCH64 > +# > + > +[Sources.common] > + LinuxInitrdShellCommandLib.c > + LinuxInitrdShellCommandLib.uni > + > +[Packages] > + MdePkg/MdePkg.dec > + ShellPkg/ShellPkg.dec > + MdeModulePkg/MdeModulePkg.dec > + OvmfPkg/OvmfPkg.dec > + > +[LibraryClasses] > + DebugLib > + DevicePathLib > + HiiLib > + MemoryAllocationLib > + ShellCommandLib > + ShellLib > + UefiBootServicesTableLib > + > +[Protocols] > + gEfiLoadFile2ProtocolGuid ## SOMETIMES_PRODUCES > + > +[Guids] > + gShellInitrdHiiGuid ## SOMETIMES_CONSUMES ## > HII Seems reasonable, and to match e.g. "ShellPkg/Library/UefiShellLevel1CommandsLib/UefiShellLevel1CommandsLib.inf". (3) However: I think this should be added as a Dynamic Command instead. I'm basing this on the message of commit 0961002352e9 ("ShellPkg/tftp: Convert from NULL class library to Dynamic Command", 2017-11-28), which is the first commit in edk2 ever to introduce a Dynamic Command. And the commit message there says: The guideline is: 1. Only use NULL class library for Shell spec defined commands. 2. New commands can be provided as not only a standalone application but also a dynamic command. So it can be used either as an internal command, but also as a standalone application. I'm not asking for the command to be usable as a separate application, but I think we might want to follow the first guideline. (I've checked the UEFI Shell 2.2 spec. While it talks about dynamic commands, it does not seem to spell out guideline#1. So I think it's rather an edk2-specific guideline than a standard one. Nonetheless we might want to adhere to it.) Implementing the feature as a dynamic command would imply MODULE_TYPE=DXE_DRIVER, and using ENTRY_POINT/UNLOAD_IMAGE rathern than CONSTRUCTOR/DESTRUCTOR; I think. > diff --git > a/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.uni > b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.uni > new file mode 100644 > index 000000000000..d4fe798b1ea2 > --- /dev/null > +++ > b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.uni > @@ -0,0 +1,37 @@ > +// /** > +// > +// Copyright (c) 2020, Arm, Ltd. All rights reserved.<BR> > +// SPDX-License-Identifier: BSD-2-Clause-Patent > +// > +// Module Name: > +// > +// LinuxInitrdShellCommandLib.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" (4) If these are copied from another UNI file, please add a comment here, or in the commit message. It's not really convenient to review format strings in isolation :) > + > +#string STR_GET_HELP_INITRD #language en-US "" > +".TH vol 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" Seems OK. (I won't check the formatting directives; I trust they look good in the shell.) > diff --git > a/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.c > b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.c > new file mode 100644 > index 000000000000..cfa4526df6d8 > --- /dev/null > +++ b/OvmfPkg/Library/LinuxInitrdShellCommandLib/LinuxInitrdShellCommandLib.c > @@ -0,0 +1,269 @@ > +/** @file > + Provides initrd 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/ShellCommandLib.h> > +#include <Library/ShellLib.h> > +#include <Library/UefiBootServicesTableLib.h> > + > +#include <Protocol/DevicePath.h> > +#include <Protocol/LoadFile2.h> > + > +#pragma pack(1) > +typedef struct { > + VENDOR_DEVICE_PATH VenMediaNode; > + EFI_DEVICE_PATH_PROTOCOL EndNode; > +} SINGLE_NODE_VENDOR_MEDIA_DEVPATH; > +#pragma pack() > + > +STATIC CONST CHAR16 mFileName[] = L"<unspecified>"; > +STATIC EFI_HII_HANDLE gLinuxInitrdShellCommandHiiHandle; > +STATIC SHELL_FILE_HANDLE mInitrdFileHandle; > +STATIC EFI_HANDLE mInitrdLoadFile2Handle; > + > +STATIC CONST SHELL_PARAM_ITEM ParamList[] = { > + {L"-u", TypeFlag}, > + {NULL, TypeMax} > + }; > + > +/** > + Get the filename to get help text from if not using HII. > + > + @retval The filename. > +**/ > +STATIC > +CONST CHAR16* > +EFIAPI > +ShellCommandGetManFileNameInitrd ( > + VOID > + ) > +{ > + return mFileName; > +} > + > +STATIC CONST SINGLE_NODE_VENDOR_MEDIA_DEVPATH mInitrdDevicePath = { > + { > + { > + MEDIA_DEVICE_PATH, MEDIA_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH) } > + }, > + { > + // LINUX_EFI_INITRD_MEDIA_GUID > + 0x5568e427, 0x68fc, 0x4f3d, > + { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } (5) It's probably better to introduce this in the OvmfPkg DEC file as a GUID too, plus add a header file under OvmfPkg/Include/Guid. (The latter primarily for the initializer macro.) If you are fine using CopyGuid() programmatically (and dropping CONST here), then I think the header file is not needed. > + } > + }, > + > + { > + 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, > + IN VOID *Buffer OPTIONAL > + ) > +{ > + UINTN InitrdSize; > + EFI_STATUS Status; > + > + 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 || > + mInitrdFileHandle == NULL) { > + return EFI_NOT_FOUND; > + } > + > + Status = gEfiShellProtocol->GetFileSize (mInitrdFileHandle, &InitrdSize); > + ASSERT_EFI_ERROR(Status); (6) Probably not much of a burden to just propagate the error. Either way, please insert a space character before the opening paren. > + > + if (Buffer == NULL || *BufferSize < InitrdSize) { > + *BufferSize = InitrdSize; > + return EFI_BUFFER_TOO_SMALL; > + } > + > + return gEfiShellProtocol->ReadFile (mInitrdFileHandle, BufferSize, Buffer); OK. Looks like EFI_SHELL_PROTOCOL.ReadFile() has the same EOF and "buffer too small" semantics as EFI_LOAD_FILE2_PROTOCOL.LoadFile(). > +} > + > +STATIC CONST EFI_LOAD_FILE2_PROTOCOL mInitrdLoadFile2 = { > + InitrdLoadFile2, > +}; > + > +/** > + 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). > +**/ > +SHELL_STATUS > +EFIAPI > +ShellCommandRunInitrd ( > + IN EFI_HANDLE ImageHandle, > + IN EFI_SYSTEM_TABLE *SystemTable > + ) I think I'll mostly skip this function (and the ones below) for now; I assume they could change once you rewrite the patch as a Dynamic Command. Just two logic-related comments below: > +{ > + EFI_STATUS Status; > + LIST_ENTRY *Package; > + CHAR16 *ProblemParam; > + CONST CHAR16 *Param; > + CONST CHAR16 *Filename; > + SHELL_STATUS ShellStatus; > + > + 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), > + gLinuxInitrdShellCommandHiiHandle, 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), > + gLinuxInitrdShellCommandHiiHandle, L"initrd"); > + ShellStatus = SHELL_INVALID_PARAMETER; > + } else if (ShellCommandLineGetCount (Package) < 2) { > + if (ShellCommandLineGetFlag(Package, L"-u")) { > + if (mInitrdFileHandle != NULL) { > + ShellCloseFile (&mInitrdFileHandle); > + mInitrdFileHandle = NULL; > + } > + if (mInitrdLoadFile2Handle) { > + gBS->UninstallMultipleProtocolInterfaces (mInitrdLoadFile2Handle, > + &gEfiDevicePathProtocolGuid, &mInitrdDevicePath, > + &gEfiLoadFile2ProtocolGuid, &mInitrdLoadFile2, > + NULL); > + mInitrdLoadFile2Handle = NULL; > + } > + } else { > + ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_GEN_TOO_FEW), > + gLinuxInitrdShellCommandHiiHandle, 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), > + gLinuxInitrdShellCommandHiiHandle, L"initrd", Param); > + ShellStatus = SHELL_NOT_FOUND; > + } else { > + if (mInitrdFileHandle != NULL) { > + ShellCloseFile (&mInitrdFileHandle); > + mInitrdFileHandle = NULL; > + } > + Status = ShellOpenFileByName (Filename, &mInitrdFileHandle, > + EFI_FILE_MODE_READ, 0); (7) The help text given in STR_GET_HELP_INITRD is a bit unclear. I suggest clarifying in STR_GET_HELP_INITRD that: - at any time, at most one shell file may be registered as initrd, - registering a 2nd file causes the 1st to be auto-deregistered, - de-registration applies to the last (i.e., only) registered file, if any. (8) Did you test this patch with: - registering a file as initrd, - deleting the file with the "rm" command (is that permitted or rejected?), - booting linux? I wonder if this test could trigger the ASSERT_EFI_ERROR() -- from GetFileSize() -- in InitrdLoadFile2(). Basically I'm unsure if we are allowed to hang on to an SHELL_FILE_HANDLE while the user may enter another command. This question could be especially relevant when this feature is implemented as a dynamic command -- the dynamic command woul dbe provided by a DXE_DRIVER, and so the cached shell file handle wouldn't even be owned by the shell. In that case, we might have to load the full initrd image contents into a memory block at once, when the registration happens -- and then LoadFile2 would return the file from memory. We might want to consider removable media too (e.g. an initrd file registered from a (virtual) CD-ROM, where an EFI_MEDIA_CHANGED retval could be plausible upon later access). I mean if we don't crash and the sudden absence of the file is correctly propagated through LoadFile2 to the kernel's EFI stub, that's 100% fine too. Unfortunately I'm not familiar with the expected life cycle of shell file handles in dynamic shell commands. Hopefully Ray and Zhichao can advise us. Thanks! Laszlo > + if (EFI_ERROR (Status)) { > + ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN > (STR_GEN_FILE_OPEN_FAIL), > + gLinuxInitrdShellCommandHiiHandle, L"initrd", Param); > + ShellStatus = SHELL_NOT_FOUND; > + } > + if (mInitrdLoadFile2Handle == NULL) { > + Status = gBS->InstallMultipleProtocolInterfaces ( > + &mInitrdLoadFile2Handle, > + &gEfiDevicePathProtocolGuid, &mInitrdDevicePath, > + &gEfiLoadFile2ProtocolGuid, &mInitrdLoadFile2, > + NULL); > + ASSERT_EFI_ERROR (Status); > + } > + } > + } > + } > + > + return ShellStatus; > +} > + > +/** > + Constructor for the 'initrd' UEFI Shell command library > + > + @param ImageHandle the image handle of the process > + @param SystemTable the EFI System Table pointer > + > + @retval EFI_SUCCESS the shell command handlers were installed > sucessfully > + @retval EFI_UNSUPPORTED the shell level required was not found. > +**/ > +EFI_STATUS > +EFIAPI > +LinuxInitrdShellCommandLibConstructor ( > + IN EFI_HANDLE ImageHandle, > + IN EFI_SYSTEM_TABLE *SystemTable > + ) > +{ > + gLinuxInitrdShellCommandHiiHandle = HiiAddPackages (&gShellInitrdHiiGuid, > + gImageHandle, LinuxInitrdShellCommandLibStrings, NULL); > + if (gLinuxInitrdShellCommandHiiHandle == NULL) { > + return EFI_DEVICE_ERROR; > + } > + > + ShellCommandRegisterCommandName (L"initrd", ShellCommandRunInitrd, > + ShellCommandGetManFileNameInitrd, 0, L"initrd", TRUE, > + gLinuxInitrdShellCommandHiiHandle, STRING_TOKEN(STR_GET_HELP_INITRD)); > + > + return EFI_SUCCESS; > +} > + > +/** > + Destructor for the library. free any resources. > + > + @param ImageHandle The image handle of the process. > + @param SystemTable The EFI System Table pointer. > + > + @retval EFI_SUCCESS Always returned. > +**/ > +EFI_STATUS > +EFIAPI > +LinuxInitrdShellCommandLibDestructor ( > + IN EFI_HANDLE ImageHandle, > + IN EFI_SYSTEM_TABLE *SystemTable > + ) > +{ > + if (gLinuxInitrdShellCommandHiiHandle != NULL) { > + HiiRemovePackages (gLinuxInitrdShellCommandHiiHandle); > + } > + > + if (mInitrdLoadFile2Handle) { > + gBS->UninstallMultipleProtocolInterfaces (mInitrdLoadFile2Handle, > + &gEfiDevicePathProtocolGuid, &mInitrdDevicePath, > + &gEfiLoadFile2ProtocolGuid, &mInitrdLoadFile2, > + NULL); > + } > + return EFI_SUCCESS; > +} -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#54286): https://edk2.groups.io/g/devel/message/54286 Mute This Topic: https://groups.io/mt/71177416/21656 Group Owner: devel+ow...@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-