+/** @file\r
+\r
+ Copyright (c) 2013-2014, ARM Ltd. All rights reserved.<BR>\r
+ Copyright (c) 2017, Linaro. All rights reserved.\r
+\r
+ This program and the accompanying materials\r
+ are licensed and made available under the terms and conditions of the BSD License\r
+ which accompanies this distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+\r
+**/\r
+\r
+#include <libfdt.h>\r
+#include <Library/AndroidBootImgLib.h>\r
+#include <Library/PrintLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/UefiLib.h>\r
+\r
+#include <Protocol/AndroidBootImg.h>\r
+#include <Protocol/LoadedImage.h>\r
+\r
+#include <libfdt.h>\r
+\r
+#define FDT_ADDITIONAL_ENTRIES_SIZE 0x400\r
+\r
+typedef struct {\r
+ MEMMAP_DEVICE_PATH Node1;\r
+ EFI_DEVICE_PATH_PROTOCOL End;\r
+} MEMORY_DEVICE_PATH;\r
+\r
+STATIC ANDROID_BOOTIMG_PROTOCOL *mAndroidBootImg;\r
+\r
+STATIC CONST MEMORY_DEVICE_PATH mMemoryDevicePathTemplate =\r
+{\r
+ {\r
+ {\r
+ HARDWARE_DEVICE_PATH,\r
+ HW_MEMMAP_DP,\r
+ {\r
+ (UINT8)(sizeof (MEMMAP_DEVICE_PATH)),\r
+ (UINT8)((sizeof (MEMMAP_DEVICE_PATH)) >> 8),\r
+ },\r
+ }, // Header\r
+ 0, // StartingAddress (set at runtime)\r
+ 0 // EndingAddress (set at runtime)\r
+ }, // Node1\r
+ {\r
+ END_DEVICE_PATH_TYPE,\r
+ END_ENTIRE_DEVICE_PATH_SUBTYPE,\r
+ { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }\r
+ } // End\r
+};\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetImgSize (\r
+ IN VOID *BootImg,\r
+ OUT UINTN *ImgSize\r
+ )\r
+{\r
+ ANDROID_BOOTIMG_HEADER *Header;\r
+\r
+ Header = (ANDROID_BOOTIMG_HEADER *) BootImg;\r
+\r
+ if (AsciiStrnCmp ((CONST CHAR8 *)Header->BootMagic, ANDROID_BOOT_MAGIC,\r
+ ANDROID_BOOT_MAGIC_LENGTH) != 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ /* The page size is not specified, but it should be power of 2 at least */\r
+ ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));\r
+\r
+ /* Get real size of abootimg */\r
+ *ImgSize = ALIGN_VALUE (Header->KernelSize, Header->PageSize) +\r
+ ALIGN_VALUE (Header->RamdiskSize, Header->PageSize) +\r
+ ALIGN_VALUE (Header->SecondStageBootloaderSize, Header->PageSize) +\r
+ Header->PageSize;\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetKernelInfo (\r
+ IN VOID *BootImg,\r
+ OUT VOID **Kernel,\r
+ OUT UINTN *KernelSize\r
+ )\r
+{\r
+ ANDROID_BOOTIMG_HEADER *Header;\r
+\r
+ Header = (ANDROID_BOOTIMG_HEADER *) BootImg;\r
+\r
+ if (AsciiStrnCmp ((CONST CHAR8 *)Header->BootMagic, ANDROID_BOOT_MAGIC,\r
+ ANDROID_BOOT_MAGIC_LENGTH) != 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ if (Header->KernelSize == 0) {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));\r
+\r
+ *KernelSize = Header->KernelSize;\r
+ *Kernel = BootImg + Header->PageSize;\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetRamdiskInfo (\r
+ IN VOID *BootImg,\r
+ OUT VOID **Ramdisk,\r
+ OUT UINTN *RamdiskSize\r
+ )\r
+{\r
+ ANDROID_BOOTIMG_HEADER *Header;\r
+\r
+ Header = (ANDROID_BOOTIMG_HEADER *)BootImg;\r
+\r
+ if (AsciiStrnCmp ((CONST CHAR8 *)Header->BootMagic, ANDROID_BOOT_MAGIC,\r
+ ANDROID_BOOT_MAGIC_LENGTH) != 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));\r
+\r
+ *RamdiskSize = Header->RamdiskSize;\r
+\r
+ if (Header->RamdiskSize != 0) {\r
+ *Ramdisk = (VOID *)((INTN)BootImg\r
+ + Header->PageSize\r
+ + ALIGN_VALUE (Header->KernelSize, Header->PageSize));\r
+ }\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetSecondBootLoaderInfo (\r
+ IN VOID *BootImg,\r
+ OUT VOID **Second,\r
+ OUT UINTN *SecondSize\r
+ )\r
+{\r
+ ANDROID_BOOTIMG_HEADER *Header;\r
+\r
+ Header = (ANDROID_BOOTIMG_HEADER *)BootImg;\r
+\r
+ if (AsciiStrnCmp ((CONST CHAR8 *)Header->BootMagic, ANDROID_BOOT_MAGIC,\r
+ ANDROID_BOOT_MAGIC_LENGTH) != 0) {\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ ASSERT (IS_VALID_ANDROID_PAGE_SIZE (Header->PageSize));\r
+\r
+ *SecondSize = Header->SecondStageBootloaderSize;\r
+\r
+ if (Header->SecondStageBootloaderSize != 0) {\r
+ *Second = (VOID *)((UINTN)BootImg\r
+ + Header->PageSize\r
+ + ALIGN_VALUE (Header->KernelSize, Header->PageSize)\r
+ + ALIGN_VALUE (Header->RamdiskSize, Header->PageSize));\r
+ }\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetKernelArgs (\r
+ IN VOID *BootImg,\r
+ OUT CHAR8 *KernelArgs\r
+ )\r
+{\r
+ ANDROID_BOOTIMG_HEADER *Header;\r
+\r
+ Header = (ANDROID_BOOTIMG_HEADER *) BootImg;\r
+ AsciiStrnCpyS (KernelArgs, ANDROID_BOOTIMG_KERNEL_ARGS_SIZE, Header->KernelArgs,\r
+ ANDROID_BOOTIMG_KERNEL_ARGS_SIZE);\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgGetFdt (\r
+ IN VOID *BootImg,\r
+ IN VOID **FdtBase\r
+ )\r
+{\r
+ UINTN SecondLoaderSize;\r
+ EFI_STATUS Status;\r
+\r
+ /* Check whether FDT is located in second boot region as some vendor do so,\r
+ * because second loader is never used as far as I know. */\r
+ Status = AndroidBootImgGetSecondBootLoaderInfo (\r
+ BootImg,\r
+ FdtBase,\r
+ &SecondLoaderSize\r
+ );\r
+ return Status;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgUpdateArgs (\r
+ IN VOID *BootImg,\r
+ OUT VOID *KernelArgs\r
+ )\r
+{\r
+ CHAR8 ImageKernelArgs[ANDROID_BOOTIMG_KERNEL_ARGS_SIZE];\r
+ EFI_STATUS Status;\r
+\r
+ // Get kernel arguments from Android boot image\r
+ Status = AndroidBootImgGetKernelArgs (BootImg, ImageKernelArgs);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+ AsciiStrToUnicodeStrS (ImageKernelArgs, KernelArgs,\r
+ ANDROID_BOOTIMG_KERNEL_ARGS_SIZE >> 1);\r
+ // Append platform kernel arguments\r
+ if(mAndroidBootImg->AppendArgs) {\r
+ Status = mAndroidBootImg->AppendArgs (KernelArgs,\r
+ ANDROID_BOOTIMG_KERNEL_ARGS_SIZE);\r
+ }\r
+ return Status;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgLocateFdt (\r
+ IN VOID *BootImg,\r
+ IN VOID **FdtBase\r
+ )\r
+{\r
+ INTN Err;\r
+ EFI_STATUS Status;\r
+\r
+ Status = EfiGetSystemConfigurationTable (&gFdtTableGuid, FdtBase);\r
+ if (!EFI_ERROR (Status)) {\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+ Status = AndroidBootImgGetFdt (BootImg, FdtBase);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+ Err = fdt_check_header (*FdtBase);\r
+ if (Err != 0) {\r
+ DEBUG ((DEBUG_ERROR, "ERROR: Device Tree header not valid (Err:%d)\n",\r
+ Err));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+INTN\r
+AndroidBootImgGetChosenNode (\r
+ IN INTN UpdatedFdtBase\r
+ )\r
+{\r
+ INTN ChosenNode;\r
+\r
+ ChosenNode = fdt_subnode_offset ((CONST VOID *)UpdatedFdtBase, 0, "chosen");\r
+ if (ChosenNode < 0) {\r
+ ChosenNode = fdt_add_subnode((VOID *)UpdatedFdtBase, 0, "chosen");\r
+ if (ChosenNode < 0) {\r
+ DEBUG ((DEBUG_ERROR, "Fail to find fdt node chosen!\n"));\r
+ return 0;\r
+ }\r
+ }\r
+ return ChosenNode;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgSetProperty64 (\r
+ IN INTN UpdatedFdtBase,\r
+ IN INTN ChosenNode,\r
+ IN CHAR8 *PropertyName,\r
+ IN UINT64 Val\r
+ )\r
+{\r
+ INTN Err;\r
+ struct fdt_property *Property;\r
+ int Len;\r
+\r
+ Property = fdt_get_property_w((VOID *)UpdatedFdtBase, ChosenNode,\r
+ PropertyName, &Len);\r
+ if (NULL == Property && Len == -FDT_ERR_NOTFOUND) {\r
+ Val = cpu_to_fdt64(Val);\r
+ Err = fdt_appendprop ((VOID *)UpdatedFdtBase, ChosenNode,\r
+ PropertyName, &Val, sizeof (UINT64));\r
+ if (Err) {\r
+ DEBUG ((DEBUG_ERROR, "fdt_appendprop() fail: %a\n", fdt_strerror (Err)));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ } else if (Property != NULL) {\r
+ Err = fdt_setprop_u64((VOID *)UpdatedFdtBase, ChosenNode,\r
+ PropertyName, Val);\r
+ if (Err) {\r
+ DEBUG ((DEBUG_ERROR, "fdt_setprop_u64() fail: %a\n", fdt_strerror (Err)));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ } else {\r
+ DEBUG ((DEBUG_ERROR, "Failed to set fdt Property %a\n", PropertyName));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgUpdateFdt (\r
+ IN VOID *BootImg,\r
+ IN VOID *FdtBase,\r
+ IN VOID *RamdiskData,\r
+ IN UINTN RamdiskSize\r
+ )\r
+{\r
+ INTN ChosenNode, Err, NewFdtSize;\r
+ EFI_STATUS Status;\r
+ EFI_PHYSICAL_ADDRESS UpdatedFdtBase, NewFdtBase;\r
+\r
+ NewFdtSize = (UINTN)fdt_totalsize (FdtBase)\r
+ + FDT_ADDITIONAL_ENTRIES_SIZE;\r
+ Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData,\r
+ EFI_SIZE_TO_PAGES (NewFdtSize), &UpdatedFdtBase);\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_WARN, "Warning: Failed to reallocate FDT, err %d.\n",\r
+ Status));\r
+ return Status;\r
+ }\r
+\r
+ // Load the Original FDT tree into the new region\r
+ Err = fdt_open_into(FdtBase, (VOID*)(INTN)UpdatedFdtBase, NewFdtSize);\r
+ if (Err) {\r
+ DEBUG ((DEBUG_ERROR, "fdt_open_into(): %a\n", fdt_strerror (Err)));\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Fdt_Exit;\r
+ }\r
+\r
+ ChosenNode = AndroidBootImgGetChosenNode(UpdatedFdtBase);\r
+ if (!ChosenNode) {\r
+ goto Fdt_Exit;\r
+ }\r
+\r
+ Status = AndroidBootImgSetProperty64 (UpdatedFdtBase, ChosenNode,\r
+ "linux,initrd-start",\r
+ (UINTN)RamdiskData);\r
+ if (EFI_ERROR (Status)) {\r
+ goto Fdt_Exit;\r
+ }\r
+\r
+ Status = AndroidBootImgSetProperty64 (UpdatedFdtBase, ChosenNode,\r
+ "linux,initrd-end",\r
+ (UINTN)(RamdiskData + RamdiskSize));\r
+ if (EFI_ERROR (Status)) {\r
+ goto Fdt_Exit;\r
+ }\r
+\r
+ if (mAndroidBootImg->UpdateDtb) {\r
+ Status = mAndroidBootImg->UpdateDtb (UpdatedFdtBase, &NewFdtBase);\r
+ if (EFI_ERROR (Status)) {\r
+ goto Fdt_Exit;\r
+ }\r
+ }\r
+\r
+ Status = gBS->InstallConfigurationTable (\r
+ &gFdtTableGuid,\r
+ (VOID *)(UINTN)NewFdtBase\r
+ );\r
+ if (!EFI_ERROR (Status)) {\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+Fdt_Exit:\r
+ gBS->FreePages (UpdatedFdtBase, EFI_SIZE_TO_PAGES (NewFdtSize));\r
+ return Status;\r
+}\r
+\r
+EFI_STATUS\r
+AndroidBootImgBoot (\r
+ IN VOID *Buffer,\r
+ IN UINTN BufferSize\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ VOID *Kernel;\r
+ UINTN KernelSize;\r
+ MEMORY_DEVICE_PATH KernelDevicePath;\r
+ EFI_HANDLE ImageHandle;\r
+ VOID *NewKernelArg;\r
+ EFI_LOADED_IMAGE_PROTOCOL *ImageInfo;\r
+ VOID *RamdiskData;\r
+ UINTN RamdiskSize;\r
+ IN VOID *FdtBase;\r
+\r
+ Status = gBS->LocateProtocol (&gAndroidBootImgProtocolGuid, NULL,\r
+ (VOID **) &mAndroidBootImg);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+\r
+ Status = AndroidBootImgGetKernelInfo (\r
+ Buffer,\r
+ &Kernel,\r
+ &KernelSize\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+\r
+ NewKernelArg = AllocateZeroPool (ANDROID_BOOTIMG_KERNEL_ARGS_SIZE);\r
+ if (NewKernelArg == NULL) {\r
+ DEBUG ((DEBUG_ERROR, "Fail to allocate memory\n"));\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+\r
+ Status = AndroidBootImgUpdateArgs (Buffer, NewKernelArg);\r
+ if (EFI_ERROR (Status)) {\r
+ FreePool (NewKernelArg);\r
+ return Status;\r
+ }\r
+\r
+ Status = AndroidBootImgGetRamdiskInfo (\r
+ Buffer,\r
+ &RamdiskData,\r
+ &RamdiskSize\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+\r
+ Status = AndroidBootImgLocateFdt (Buffer, &FdtBase);\r
+ if (EFI_ERROR (Status)) {\r
+ FreePool (NewKernelArg);\r
+ return Status;\r
+ }\r
+\r
+ Status = AndroidBootImgUpdateFdt (Buffer, FdtBase, RamdiskData, RamdiskSize);\r
+ if (EFI_ERROR (Status)) {\r
+ FreePool (NewKernelArg);\r
+ return Status;\r
+ }\r
+\r
+ KernelDevicePath = mMemoryDevicePathTemplate;\r
+\r
+ KernelDevicePath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN) Kernel;\r
+ KernelDevicePath.Node1.EndingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN) Kernel\r
+ + KernelSize;\r
+\r
+ Status = gBS->LoadImage (TRUE, gImageHandle,\r
+ (EFI_DEVICE_PATH *)&KernelDevicePath,\r
+ (VOID*)(UINTN)Kernel, KernelSize, &ImageHandle);\r
+\r
+ // Set kernel arguments\r
+ Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid,\r
+ (VOID **) &ImageInfo);\r
+ ImageInfo->LoadOptions = NewKernelArg;\r
+ ImageInfo->LoadOptionsSize = StrLen (NewKernelArg) * sizeof (CHAR16);\r
+\r
+ // Before calling the image, enable the Watchdog Timer for the 5 Minute period\r
+ gBS->SetWatchdogTimer (5 * 60, 0x10000, 0, NULL);\r
+ // Start the image\r
+ Status = gBS->StartImage (ImageHandle, NULL, NULL);\r
+ // Clear the Watchdog Timer if the image returns\r
+ gBS->SetWatchdogTimer (0, 0x10000, 0, NULL);\r
+ return EFI_SUCCESS;\r
+}\r