/** @file
Copyright (c) 2013-2014, ARM Ltd. All rights reserved.
Copyright (c) 2017, Linaro. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#define FDT_ADDITIONAL_ENTRIES_SIZE 0x400
typedef struct {
MEMMAP_DEVICE_PATH Node1;
EFI_DEVICE_PATH_PROTOCOL End;
} MEMORY_DEVICE_PATH;
STATIC ANDROID_BOOTIMG_PROTOCOL *mAndroidBootImg;
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
};
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 = 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;
// Get kernel arguments from Android boot image
Status = AndroidBootImgGetKernelArgs (BootImg, ImageKernelArgs);
if (EFI_ERROR (Status)) {
return Status;
}
AsciiStrToUnicodeStrS (ImageKernelArgs, KernelArgs,
ANDROID_BOOTIMG_KERNEL_ARGS_SIZE >> 1);
// Append platform kernel arguments
if(mAndroidBootImg->AppendArgs) {
Status = mAndroidBootImg->AppendArgs (KernelArgs,
ANDROID_BOOTIMG_KERNEL_ARGS_SIZE);
}
return 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;
}
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;
}
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;
Status = gBS->LocateProtocol (&gAndroidBootImgProtocolGuid, NULL,
(VOID **) &mAndroidBootImg);
if (EFI_ERROR (Status)) {
return Status;
}
Status = AndroidBootImgGetKernelInfo (
Buffer,
&Kernel,
&KernelSize
);
if (EFI_ERROR (Status)) {
return Status;
}
NewKernelArg = AllocateZeroPool (ANDROID_BOOTIMG_KERNEL_ARGS_SIZE);
if (NewKernelArg == NULL) {
DEBUG ((DEBUG_ERROR, "Fail to allocate memory\n"));
return EFI_OUT_OF_RESOURCES;
}
Status = AndroidBootImgUpdateArgs (Buffer, NewKernelArg);
if (EFI_ERROR (Status)) {
FreePool (NewKernelArg);
return Status;
}
Status = AndroidBootImgGetRamdiskInfo (
Buffer,
&RamdiskData,
&RamdiskSize
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = AndroidBootImgLocateFdt (Buffer, &FdtBase);
if (EFI_ERROR (Status)) {
FreePool (NewKernelArg);
return Status;
}
Status = AndroidBootImgUpdateFdt (Buffer, FdtBase, RamdiskData, RamdiskSize);
if (EFI_ERROR (Status)) {
FreePool (NewKernelArg);
return Status;
}
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)) {
//
// With EFI_SECURITY_VIOLATION retval, the Image was loaded and an ImageHandle was created
// with a valid EFI_LOADED_IMAGE_PROTOCOL, but the image can not be started right now.
// If the caller doesn't have the option to defer the execution of an image, we should
// unload image for the EFI_SECURITY_VIOLATION to avoid resource leak.
//
if (Status == EFI_SECURITY_VIOLATION) {
gBS->UnloadImage (ImageHandle);
}
return Status;
}
// Set kernel arguments
Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid,
(VOID **) &ImageInfo);
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);
return EFI_SUCCESS;
}