blob: 07baa0e8d10001eb2ec6fd003d6f8f4389a622fe [file] [log] [blame]
/** @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
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;
}