Files
system76-edk2/OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.c
Ard Biesheuvel 2632178bc6 OvmfPkg: add 'initrd' shell command to expose Linux initrd via device path
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.

Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=2564
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2020-03-04 09:26:45 +00:00

430 lines
12 KiB
C

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