blob: d16929f2bbdbfdf4b92d9e021a9e1515cd9dc1f6 [file] [log] [blame]
/** @file
Copyright (c) 2013-2014, ARM Ltd. All rights reserved.<BR>
Copyright (c) 2017, Linaro. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <libfdt.h>
#include <Library/AndroidBootImgLib.h>
#include <Library/PrintLib.h>
#include <Library/DevicePathLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/AndroidBootImg.h>
#include <Protocol/LoadFile2.h>
#include <Protocol/LoadedImage.h>
#include <Guid/LinuxEfiInitrdMedia.h>
#define FDT_ADDITIONAL_ENTRIES_SIZE 0x400
typedef struct {
MEMMAP_DEVICE_PATH Node1;
EFI_DEVICE_PATH_PROTOCOL End;
} MEMORY_DEVICE_PATH;
typedef struct {
VENDOR_DEVICE_PATH VendorMediaNode;
EFI_DEVICE_PATH_PROTOCOL EndNode;
} RAMDISK_DEVICE_PATH;
STATIC ANDROID_BOOTIMG_PROTOCOL *mAndroidBootImg;
STATIC VOID *mRamdiskData = NULL;
STATIC UINTN mRamdiskSize = 0;
STATIC EFI_HANDLE mRamDiskLoadFileHandle = NULL;
STATIC CONST MEMORY_DEVICE_PATH mMemoryDevicePathTemplate =
{
{
{
HARDWARE_DEVICE_PATH,
HW_MEMMAP_DP,
{
(UINT8)(sizeof (MEMMAP_DEVICE_PATH)),
(UINT8)((sizeof (MEMMAP_DEVICE_PATH)) >> 8),
},
}, // Header
0, // StartingAddress (set at runtime)
0 // EndingAddress (set at runtime)
}, // Node1
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{ sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
} // End
};
STATIC CONST RAMDISK_DEVICE_PATH mRamdiskDevicePath =
{
{
{
MEDIA_DEVICE_PATH,
MEDIA_VENDOR_DP,
{ sizeof (VENDOR_DEVICE_PATH), 0 }
},
LINUX_EFI_INITRD_MEDIA_GUID
},
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{ sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
}
};
/**
Causes the driver to load a specified file.
@param This Protocol instance pointer.
@param FilePath The device specific path of the file to load.
@param BootPolicy Should always be FALSE.
@param BufferSize On input the size of Buffer in bytes. On output with a return
code of EFI_SUCCESS, the amount of data transferred to
Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL,
the size of Buffer required to retrieve the requested file.
@param Buffer The memory buffer to transfer the file to. IF Buffer is NULL,
then no the size of the requested file is returned in
BufferSize.
@retval EFI_SUCCESS The file was loaded.
@retval EFI_UNSUPPORTED BootPolicy is TRUE.
@retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or
BufferSize is NULL.
@retval EFI_NO_MEDIA No medium was present to load the file.
@retval EFI_DEVICE_ERROR The file was not loaded due to a device error.
@retval EFI_NO_RESPONSE The remote system did not respond.
@retval EFI_NOT_FOUND The file was not found
@retval EFI_ABORTED The file load process was manually canceled.
@retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current
directory entry. BufferSize has been updated with
the size needed to complete the request.
**/
EFI_STATUS
EFIAPI
AndroidBootImgLoadFile2 (
IN EFI_LOAD_FILE2_PROTOCOL *This,
IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
IN BOOLEAN BootPolicy,
IN OUT UINTN *BufferSize,
IN VOID *Buffer OPTIONAL
)
{
// Verify if the valid parameters
if ((This == NULL) ||
(BufferSize == NULL) ||
(FilePath == NULL) ||
!IsDevicePathValid (FilePath, 0))
{
return EFI_INVALID_PARAMETER;
}
if (BootPolicy) {
return EFI_UNSUPPORTED;
}
// Check if the given buffer size is big enough
// EFI_BUFFER_TOO_SMALL to allow caller to allocate a bigger buffer
if (mRamdiskSize == 0) {
return EFI_NOT_FOUND;
}
if ((Buffer == NULL) || (*BufferSize < mRamdiskSize)) {
*BufferSize = mRamdiskSize;
return EFI_BUFFER_TOO_SMALL;
}
// Copy InitRd
CopyMem (Buffer, mRamdiskData, mRamdiskSize);
*BufferSize = mRamdiskSize;
return EFI_SUCCESS;
}
///
/// Load File Protocol instance
///
STATIC EFI_LOAD_FILE2_PROTOCOL mAndroidBootImgLoadFile2 = {
AndroidBootImgLoadFile2
};
EFI_STATUS
AndroidBootImgGetImgSize (
IN VOID *BootImg,
OUT UINTN *ImgSize
)
{
ANDROID_BOOTIMG_HEADER *Header;
Header = (ANDROID_BOOTIMG_HEADER *)BootImg;
if (AsciiStrnCmp (
(CONST CHAR8 *)Header->BootMagic,
ANDROID_BOOT_MAGIC,
ANDROID_BOOT_MAGIC_LENGTH
) != 0)
{
return EFI_INVALID_PARAMETER;
}
/* The page size is not specified, but it should be power of 2 at least */
ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));
/* Get real size of abootimg */
*ImgSize = ALIGN_VALUE (Header->KernelSize, Header->PageSize) +
ALIGN_VALUE (Header->RamdiskSize, Header->PageSize) +
ALIGN_VALUE (Header->SecondStageBootloaderSize, Header->PageSize) +
Header->PageSize;
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgGetKernelInfo (
IN VOID *BootImg,
OUT VOID **Kernel,
OUT UINTN *KernelSize
)
{
ANDROID_BOOTIMG_HEADER *Header;
Header = (ANDROID_BOOTIMG_HEADER *)BootImg;
if (AsciiStrnCmp (
(CONST CHAR8 *)Header->BootMagic,
ANDROID_BOOT_MAGIC,
ANDROID_BOOT_MAGIC_LENGTH
) != 0)
{
return EFI_INVALID_PARAMETER;
}
if (Header->KernelSize == 0) {
return EFI_NOT_FOUND;
}
ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));
*KernelSize = Header->KernelSize;
*Kernel = (VOID *)((UINTN)BootImg + Header->PageSize);
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgGetRamdiskInfo (
IN VOID *BootImg,
OUT VOID **Ramdisk,
OUT UINTN *RamdiskSize
)
{
ANDROID_BOOTIMG_HEADER *Header;
Header = (ANDROID_BOOTIMG_HEADER *)BootImg;
if (AsciiStrnCmp (
(CONST CHAR8 *)Header->BootMagic,
ANDROID_BOOT_MAGIC,
ANDROID_BOOT_MAGIC_LENGTH
) != 0)
{
return EFI_INVALID_PARAMETER;
}
ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));
*RamdiskSize = Header->RamdiskSize;
if (Header->RamdiskSize != 0) {
*Ramdisk = (VOID *)((INTN)BootImg
+ Header->PageSize
+ ALIGN_VALUE (Header->KernelSize, Header->PageSize));
}
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgGetSecondBootLoaderInfo (
IN VOID *BootImg,
OUT VOID **Second,
OUT UINTN *SecondSize
)
{
ANDROID_BOOTIMG_HEADER *Header;
Header = (ANDROID_BOOTIMG_HEADER *)BootImg;
if (AsciiStrnCmp (
(CONST CHAR8 *)Header->BootMagic,
ANDROID_BOOT_MAGIC,
ANDROID_BOOT_MAGIC_LENGTH
) != 0)
{
return EFI_INVALID_PARAMETER;
}
ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));
*SecondSize = Header->SecondStageBootloaderSize;
if (Header->SecondStageBootloaderSize != 0) {
*Second = (VOID *)((UINTN)BootImg
+ Header->PageSize
+ ALIGN_VALUE (Header->KernelSize, Header->PageSize)
+ ALIGN_VALUE (Header->RamdiskSize, Header->PageSize));
}
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgGetKernelArgs (
IN VOID *BootImg,
OUT CHAR8 *KernelArgs
)
{
ANDROID_BOOTIMG_HEADER *Header;
Header = (ANDROID_BOOTIMG_HEADER *)BootImg;
AsciiStrnCpyS (
KernelArgs,
ANDROID_BOOTIMG_KERNEL_ARGS_SIZE,
Header->KernelArgs,
ANDROID_BOOTIMG_KERNEL_ARGS_SIZE
);
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgGetFdt (
IN VOID *BootImg,
IN VOID **FdtBase
)
{
UINTN SecondLoaderSize;
EFI_STATUS Status;
/* Check whether FDT is located in second boot region as some vendor do so,
* because second loader is never used as far as I know. */
Status = AndroidBootImgGetSecondBootLoaderInfo (
BootImg,
FdtBase,
&SecondLoaderSize
);
return Status;
}
EFI_STATUS
AndroidBootImgUpdateArgs (
IN VOID *BootImg,
OUT VOID **KernelArgs
)
{
CHAR8 ImageKernelArgs[ANDROID_BOOTIMG_KERNEL_ARGS_SIZE];
EFI_STATUS Status;
UINT32 NewKernelArgSize;
// Get kernel arguments from Android boot image
Status = AndroidBootImgGetKernelArgs (BootImg, ImageKernelArgs);
if (EFI_ERROR (Status)) {
return Status;
}
NewKernelArgSize = ANDROID_BOOTIMG_KERNEL_ARGS_SIZE + PcdGet32 (PcdAndroidKernelCommandLineOverflow);
*KernelArgs = AllocateZeroPool (sizeof (CHAR16) * NewKernelArgSize);
if (*KernelArgs == NULL) {
DEBUG ((DEBUG_ERROR, "Fail to allocate memory\n"));
return EFI_OUT_OF_RESOURCES;
}
AsciiStrToUnicodeStrS (
ImageKernelArgs,
*KernelArgs,
NewKernelArgSize
);
// Append platform kernel arguments
if (mAndroidBootImg->AppendArgs) {
Status = mAndroidBootImg->AppendArgs (
*KernelArgs,
NewKernelArgSize
);
}
return Status;
}
EFI_STATUS
AndroidBootImgInstallLoadFile2 (
IN VOID *RamdiskData,
IN UINTN RamdiskSize
)
{
mRamDiskLoadFileHandle = NULL;
mRamdiskData = RamdiskData;
mRamdiskSize = RamdiskSize;
return gBS->InstallMultipleProtocolInterfaces (
&mRamDiskLoadFileHandle,
&gEfiLoadFile2ProtocolGuid,
&mAndroidBootImgLoadFile2,
&gEfiDevicePathProtocolGuid,
&mRamdiskDevicePath,
NULL
);
}
EFI_STATUS
AndroidBootImgUninstallLoadFile2 (
VOID
)
{
EFI_STATUS Status;
Status = EFI_SUCCESS;
mRamdiskData = NULL;
mRamdiskSize = 0;
if (mRamDiskLoadFileHandle != NULL) {
Status = gBS->UninstallMultipleProtocolInterfaces (
mRamDiskLoadFileHandle,
&gEfiLoadFile2ProtocolGuid,
&mAndroidBootImgLoadFile2,
&gEfiDevicePathProtocolGuid,
&mRamdiskDevicePath,
NULL
);
mRamDiskLoadFileHandle = NULL;
}
return Status;
}
BOOLEAN
AndroidBootImgAcpiSupported (
VOID
)
{
EFI_STATUS Status;
VOID *AcpiTable;
Status = EfiGetSystemConfigurationTable (&gEfiAcpiTableGuid, &AcpiTable);
return !EFI_ERROR (Status);
}
EFI_STATUS
AndroidBootImgLocateFdt (
IN VOID *BootImg,
IN VOID **FdtBase
)
{
INTN Err;
EFI_STATUS Status;
Status = EfiGetSystemConfigurationTable (&gFdtTableGuid, FdtBase);
if (!EFI_ERROR (Status)) {
return EFI_SUCCESS;
}
Status = AndroidBootImgGetFdt (BootImg, FdtBase);
if (EFI_ERROR (Status)) {
return Status;
}
Err = fdt_check_header (*FdtBase);
if (Err != 0) {
DEBUG ((
DEBUG_ERROR,
"ERROR: Device Tree header not valid (Err:%d)\n",
Err
));
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
INTN
AndroidBootImgGetChosenNode (
IN INTN UpdatedFdtBase
)
{
INTN ChosenNode;
ChosenNode = fdt_subnode_offset ((CONST VOID *)UpdatedFdtBase, 0, "chosen");
if (ChosenNode < 0) {
ChosenNode = fdt_add_subnode ((VOID *)UpdatedFdtBase, 0, "chosen");
if (ChosenNode < 0) {
DEBUG ((DEBUG_ERROR, "Fail to find fdt node chosen!\n"));
return 0;
}
}
return ChosenNode;
}
EFI_STATUS
AndroidBootImgSetProperty64 (
IN INTN UpdatedFdtBase,
IN INTN ChosenNode,
IN CHAR8 *PropertyName,
IN UINT64 Val
)
{
INTN Err;
struct fdt_property *Property;
int Len;
Property = fdt_get_property_w (
(VOID *)UpdatedFdtBase,
ChosenNode,
PropertyName,
&Len
);
if ((NULL == Property) && (Len == -FDT_ERR_NOTFOUND)) {
Val = cpu_to_fdt64 (Val);
Err = fdt_appendprop (
(VOID *)UpdatedFdtBase,
ChosenNode,
PropertyName,
&Val,
sizeof (UINT64)
);
if (Err) {
DEBUG ((DEBUG_ERROR, "fdt_appendprop() fail: %a\n", fdt_strerror (Err)));
return EFI_INVALID_PARAMETER;
}
} else if (Property != NULL) {
Err = fdt_setprop_u64 (
(VOID *)UpdatedFdtBase,
ChosenNode,
PropertyName,
Val
);
if (Err) {
DEBUG ((DEBUG_ERROR, "fdt_setprop_u64() fail: %a\n", fdt_strerror (Err)));
return EFI_INVALID_PARAMETER;
}
} else {
DEBUG ((DEBUG_ERROR, "Failed to set fdt Property %a\n", PropertyName));
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
EFI_STATUS
AndroidBootImgUpdateFdt (
IN VOID *BootImg,
IN VOID *FdtBase,
IN VOID *RamdiskData,
IN UINTN RamdiskSize
)
{
INTN ChosenNode, Err, NewFdtSize;
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS UpdatedFdtBase, NewFdtBase;
NewFdtSize = (UINTN)fdt_totalsize (FdtBase)
+ FDT_ADDITIONAL_ENTRIES_SIZE;
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiBootServicesData,
EFI_SIZE_TO_PAGES (NewFdtSize),
&UpdatedFdtBase
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_WARN,
"Warning: Failed to reallocate FDT, err %d.\n",
Status
));
return Status;
}
// Load the Original FDT tree into the new region
Err = fdt_open_into (FdtBase, (VOID *)(INTN)UpdatedFdtBase, NewFdtSize);
if (Err) {
DEBUG ((DEBUG_ERROR, "fdt_open_into(): %a\n", fdt_strerror (Err)));
Status = EFI_INVALID_PARAMETER;
goto Fdt_Exit;
}
if (FeaturePcdGet (PcdAndroidBootLoadFile2)) {
Status = AndroidBootImgInstallLoadFile2 (RamdiskData, RamdiskSize);
if (EFI_ERROR (Status)) {
goto Fdt_Exit;
}
} else {
ChosenNode = AndroidBootImgGetChosenNode (UpdatedFdtBase);
if (!ChosenNode) {
goto Fdt_Exit;
}
Status = AndroidBootImgSetProperty64 (
UpdatedFdtBase,
ChosenNode,
"linux,initrd-start",
(UINTN)RamdiskData
);
if (EFI_ERROR (Status)) {
goto Fdt_Exit;
}
Status = AndroidBootImgSetProperty64 (
UpdatedFdtBase,
ChosenNode,
"linux,initrd-end",
(UINTN)RamdiskData + RamdiskSize
);
if (EFI_ERROR (Status)) {
goto Fdt_Exit;
}
}
if (mAndroidBootImg->UpdateDtb) {
Status = mAndroidBootImg->UpdateDtb (UpdatedFdtBase, &NewFdtBase);
if (EFI_ERROR (Status)) {
goto Fdt_Exit;
}
} else {
NewFdtBase = UpdatedFdtBase;
}
Status = gBS->InstallConfigurationTable (
&gFdtTableGuid,
(VOID *)(UINTN)NewFdtBase
);
if (!EFI_ERROR (Status)) {
return EFI_SUCCESS;
}
Fdt_Exit:
gBS->FreePages (UpdatedFdtBase, EFI_SIZE_TO_PAGES (NewFdtSize));
return Status;
}
EFI_STATUS
AndroidBootImgBoot (
IN VOID *Buffer,
IN UINTN BufferSize
)
{
EFI_STATUS Status;
VOID *Kernel;
UINTN KernelSize;
MEMORY_DEVICE_PATH KernelDevicePath;
EFI_HANDLE ImageHandle;
VOID *NewKernelArg;
EFI_LOADED_IMAGE_PROTOCOL *ImageInfo;
VOID *RamdiskData;
UINTN RamdiskSize;
IN VOID *FdtBase;
if ((Buffer == NULL) || (BufferSize == 0)) {
return EFI_INVALID_PARAMETER;
}
NewKernelArg = NULL;
ImageHandle = NULL;
Status = gBS->LocateProtocol (
&gAndroidBootImgProtocolGuid,
NULL,
(VOID **)&mAndroidBootImg
);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = AndroidBootImgGetKernelInfo (
Buffer,
&Kernel,
&KernelSize
);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = AndroidBootImgUpdateArgs (Buffer, &NewKernelArg);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = AndroidBootImgGetRamdiskInfo (
Buffer,
&RamdiskData,
&RamdiskSize
);
if (EFI_ERROR (Status)) {
goto Exit;
}
if (AndroidBootImgAcpiSupported ()) {
Status = AndroidBootImgInstallLoadFile2 (RamdiskData, RamdiskSize);
if (EFI_ERROR (Status)) {
goto Exit;
}
} else {
Status = AndroidBootImgLocateFdt (Buffer, &FdtBase);
if (EFI_ERROR (Status)) {
goto Exit;
}
Status = AndroidBootImgUpdateFdt (Buffer, FdtBase, RamdiskData, RamdiskSize);
if (EFI_ERROR (Status)) {
goto Exit;
}
}
KernelDevicePath = mMemoryDevicePathTemplate;
KernelDevicePath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Kernel;
KernelDevicePath.Node1.EndingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Kernel
+ KernelSize;
Status = gBS->LoadImage (
TRUE,
gImageHandle,
(EFI_DEVICE_PATH *)&KernelDevicePath,
(VOID *)(UINTN)Kernel,
KernelSize,
&ImageHandle
);
if (EFI_ERROR (Status)) {
goto Exit;
}
// Set kernel arguments
Status = gBS->HandleProtocol (
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID **)&ImageInfo
);
if (EFI_ERROR (Status)) {
goto Exit;
}
ImageInfo->LoadOptions = NewKernelArg;
ImageInfo->LoadOptionsSize = StrLen (NewKernelArg) * sizeof (CHAR16);
// Before calling the image, enable the Watchdog Timer for the 5 Minute period
gBS->SetWatchdogTimer (5 * 60, 0x10000, 0, NULL);
// Start the image
Status = gBS->StartImage (ImageHandle, NULL, NULL);
// Clear the Watchdog Timer if the image returns
gBS->SetWatchdogTimer (0, 0x10000, 0, NULL);
Exit:
// Unload image as it will not be used anymore
if (ImageHandle != NULL) {
gBS->UnloadImage (ImageHandle);
ImageHandle = NULL;
}
if (EFI_ERROR (Status)) {
if (NewKernelArg != NULL) {
FreePool (NewKernelArg);
NewKernelArg = NULL;
}
}
AndroidBootImgUninstallLoadFile2 ();
return Status;
}