EmbeddedPkg/AndroidFastboot: support raw kernel image

AndroidFastbootApp could boot raw image with ramdisk & args in existed
boot image on storage device.

Signed-off-by: Haojian Zhuang <haojian.zhuang@linaro.org>
diff --git a/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.c b/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.c
index 4fe3ac8..edda0f0 100644
--- a/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.c
+++ b/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.c
@@ -14,11 +14,14 @@
 

 #include <Protocol/AndroidFastbootTransport.h>

 #include <Protocol/AndroidFastbootPlatform.h>

+#include <Protocol/BlockIo.h>

+#include <Protocol/DevicePathFromText.h>

 #include <Protocol/SimpleTextOut.h>

 #include <Protocol/SimpleTextIn.h>

 

 #include <Library/AbootimgLib.h>

 #include <Library/BaseMemoryLib.h>

+#include <Library/DevicePathLib.h>

 #include <Library/PcdLib.h>

 #include <Library/PrintLib.h>

 #include <Library/UefiApplicationEntryPoint.h>

@@ -35,6 +38,10 @@
 

 #define FILL_BUF_SIZE               1024

 

+#define IS_DEVICE_PATH_NODE(node,type,subtype) (((node)->Type == (type)) && ((node)->SubType == (subtype)))

+

+#define ALIGN(x, a)        (((x) + ((a) - 1)) & ~((a) - 1))

+

 typedef struct _SPARSE_HEADER {

   UINT32       Magic;

   UINT16       MajorVersion;

@@ -313,6 +320,109 @@
 }

 

 STATIC

+EFI_STATUS

+BootImageWithKernel (

+  IN VOID                             *Kernel,

+  IN UINTN                            KernelSize

+  )

+{

+  EFI_STATUS                          Status;

+  CHAR16                              *BootPathStr;

+  EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL  *EfiDevicePathFromTextProtocol;

+  EFI_DEVICE_PATH                     *DevicePath;

+  EFI_DEVICE_PATH_PROTOCOL            *Node, *NextNode;

+  EFI_BLOCK_IO_PROTOCOL               *BlockIo;

+  UINT32                              MediaId, BlockSize;

+  VOID                                *Buffer;

+  EFI_HANDLE                          Handle;

+  UINTN                               Size;

+

+  BootPathStr = (CHAR16 *)PcdGetPtr (PcdAndroidBootDevicePath);

+  ASSERT (BootPathStr != NULL);

+  Status = gBS->LocateProtocol (&gEfiDevicePathFromTextProtocolGuid, NULL, (VOID **)&EfiDevicePathFromTextProtocol);

+  ASSERT_EFI_ERROR(Status);

+  DevicePath = (EFI_DEVICE_PATH *)EfiDevicePathFromTextProtocol->ConvertTextToDevicePath (BootPathStr);

+  ASSERT (DevicePath != NULL);

+

+  /* Find DevicePath node of Partition */

+  NextNode = DevicePath;

+  while (1) {

+    Node = NextNode;

+    if (IS_DEVICE_PATH_NODE (Node, MEDIA_DEVICE_PATH, MEDIA_HARDDRIVE_DP)) {

+      break;

+    }

+    NextNode = NextDevicePathNode (Node);

+  }

+

+  Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &DevicePath, &Handle);

+  if (EFI_ERROR (Status)) {

+    return Status;

+  }

+

+  Status = gBS->OpenProtocol (

+                  Handle,

+                  &gEfiBlockIoProtocolGuid,

+                  (VOID **) &BlockIo,

+                  gImageHandle,

+                  NULL,

+                  EFI_OPEN_PROTOCOL_GET_PROTOCOL

+                  );

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "Failed to get BlockIo: %r\n", Status));

+    return Status;

+  }

+

+  MediaId = BlockIo->Media->MediaId;

+  BlockSize = BlockIo->Media->BlockSize;

+  Buffer = AllocatePages (1);

+  if (Buffer == NULL) {

+    return EFI_BUFFER_TOO_SMALL;

+  }

+  /* Load header of boot.img */

+  Status = BlockIo->ReadBlocks (

+                      BlockIo,

+                      MediaId,

+                      0,

+                      BlockSize,

+                      Buffer

+                      );

+  if (EFI_ERROR (Status)) {

+    return Status;

+  }

+  Status = AbootimgGetImgSize (Buffer, &Size);

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "Failed to get Abootimg Size: %r\n", Status));

+    return Status;

+  }

+  Size = ALIGN (Size, BlockSize);

+  FreePages (Buffer, 1);

+

+  /* Both PartitionStart and PartitionSize are counted as block size. */

+  Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Size));

+  if (Buffer == NULL) {

+    return EFI_BUFFER_TOO_SMALL;

+  }

+

+  /* Load header of boot.img */

+  Status = BlockIo->ReadBlocks (

+                      BlockIo,

+                      MediaId,

+                      0,

+                      Size,

+                      Buffer

+                      );

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "Failed to read blocks: %r\n", Status));

+    goto EXIT;

+  }

+

+  Status = AbootimgBootKernel (Kernel, KernelSize, Buffer, Size);

+

+EXIT:

+  return Status;

+}

+

+STATIC

 VOID

 HandleBoot (

   VOID

@@ -335,6 +445,12 @@
   Status = AbootimgBoot (mDataBuffer, mNumDataBytes);

   if (EFI_ERROR (Status)) {

     DEBUG ((EFI_D_ERROR, "Failed to boot downloaded image: %r\n", Status));

+

+    // Try to boot kernel with original boot image

+    Status = BootImageWithKernel (mDataBuffer, mNumDataBytes);

+    if (EFI_ERROR (Status)) {

+      DEBUG ((EFI_D_ERROR, "Failed to boot downloaded kernel: %r\n", Status));

+    }

   }

   // We shouldn't get here

 }

diff --git a/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.inf b/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.inf
index 20b4330..d4cdf72 100644
--- a/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.inf
+++ b/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp.inf
@@ -43,6 +43,7 @@
 [Protocols]

   gAndroidFastbootTransportProtocolGuid

   gAndroidFastbootPlatformProtocolGuid

+  gEfiBlockIoProtocolGuid

   gEfiSimpleTextOutProtocolGuid

   gEfiSimpleTextInProtocolGuid

 

@@ -57,3 +58,6 @@
 

 [Guids]

   gFdtTableGuid

+

+[Pcd]

+  gEmbeddedTokenSpaceGuid.PcdAndroidBootDevicePath

diff --git a/EmbeddedPkg/Include/Library/AbootimgLib.h b/EmbeddedPkg/Include/Library/AbootimgLib.h
index 6364d04..3b67d36 100644
--- a/EmbeddedPkg/Include/Library/AbootimgLib.h
+++ b/EmbeddedPkg/Include/Library/AbootimgLib.h
@@ -62,4 +62,12 @@
   IN UINTN                   BufferSize
   );
 
+EFI_STATUS
+AbootimgBootKernel (
+  IN VOID                            *Buffer,
+  IN UINTN                            BufferSize,
+  IN VOID                            *ImgBuffer,
+  IN UINTN                            ImgBufferSize
+  );
+
 #endif /* __ABOOTIMG_H__ */
diff --git a/EmbeddedPkg/Library/AbootimgLib/AbootimgLib.c b/EmbeddedPkg/Library/AbootimgLib/AbootimgLib.c
index b1e529e..040a933 100644
--- a/EmbeddedPkg/Library/AbootimgLib/AbootimgLib.c
+++ b/EmbeddedPkg/Library/AbootimgLib/AbootimgLib.c
@@ -238,7 +238,13 @@
   EFI_PHYSICAL_ADDRESS                FdtBase;
   VOID                               *NewKernelArg;
   EFI_LOADED_IMAGE_PROTOCOL          *ImageInfo;
+  ANDROID_BOOTIMG_HEADER             *Header;
 
+  Header = Buffer;
+  if (Header->KernelArgs[0] == '\0') {
+    // It's not valid boot image since it's lack of kernel args.
+    return EFI_INVALID_PARAMETER;
+  }
   Status = AbootimgGetKernelInfo (
             Buffer,
             &Kernel,
@@ -268,7 +274,7 @@
     return EFI_INVALID_PARAMETER;
   }
 
-  KernelDevicePath = MemoryDevicePathTemplate;
+  CopyMem (&KernelDevicePath, &MemoryDevicePathTemplate, sizeof (MemoryDevicePathTemplate));
 
   // Have to cast to UINTN before casting to EFI_PHYSICAL_ADDRESS in order to
   // appease GCC.
@@ -290,3 +296,82 @@
   gBS->SetWatchdogTimer (0x0000, 0x0000, 0x0000, NULL);
   return EFI_SUCCESS;
 }
+
+EFI_STATUS
+AbootimgBootKernel (
+  IN VOID                            *Buffer,
+  IN UINTN                            BufferSize,
+  IN VOID                            *ImgBuffer,
+  IN UINTN                            ImgBufferSize
+  )
+{
+  EFI_STATUS                          Status;
+  VOID                               *ImgKernel;
+  UINTN                               ImgKernelSize;
+  VOID                               *DwnldKernel;
+  UINTN                               DwnldKernelSize;
+  EFI_HANDLE                          ImageHandle;
+  EFI_PHYSICAL_ADDRESS                ImgFdtBase;
+  VOID                               *NewKernelArg;
+  EFI_LOADED_IMAGE_PROTOCOL          *ImageInfo;
+  MEMORY_DEVICE_PATH                  KernelDevicePath;
+
+  Status = AbootimgGetKernelInfo (
+            Buffer,
+            &DwnldKernel,
+            &DwnldKernelSize
+            );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  Status = AbootimgGetKernelInfo (
+            ImgBuffer,
+            &ImgKernel,
+            &ImgKernelSize
+            );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  /* For flatten image, Fdt is attached at the end of kernel.
+     Get real kernel size.
+   */
+  ImgKernelSize = *(UINT32 *)((EFI_PHYSICAL_ADDRESS)(UINTN)ImgKernel + KERNEL_IMAGE_STEXT_OFFSET) +
+                  *(UINT32 *)((EFI_PHYSICAL_ADDRESS)(UINTN)ImgKernel + KERNEL_IMAGE_RAW_SIZE_OFFSET);
+  NewKernelArg = AllocateZeroPool (BOOTIMG_KERNEL_ARGS_SIZE);
+  if (NewKernelArg == NULL) {
+    DEBUG ((DEBUG_ERROR, "Fail to allocate memory\n"));
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  /* FDT is at the end of kernel image */
+  ImgFdtBase = (EFI_PHYSICAL_ADDRESS)(UINTN)ImgKernel + ImgKernelSize;
+  Status = AbootimgInstallFdt (ImgBuffer, ImgFdtBase, NewKernelArg);
+  if (EFI_ERROR (Status)) {
+    FreePool (NewKernelArg);
+    return EFI_INVALID_PARAMETER;
+  }
+
+  CopyMem (&KernelDevicePath, &MemoryDevicePathTemplate, sizeof (MemoryDevicePathTemplate));
+
+  // Have to cast to UINTN before casting to EFI_PHYSICAL_ADDRESS in order to
+  // appease GCC.
+  KernelDevicePath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN) DwnldKernel;
+  KernelDevicePath.Node1.EndingAddress   = (EFI_PHYSICAL_ADDRESS)(UINTN) DwnldKernel + DwnldKernelSize;
+
+  Status = gBS->LoadImage (TRUE, gImageHandle, (EFI_DEVICE_PATH *)&KernelDevicePath, (VOID*)(UINTN)DwnldKernel, DwnldKernelSize, &ImageHandle);
+
+  // 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, 0x0000, 0x00, NULL);
+  // Start the image
+  Status = gBS->StartImage (ImageHandle, NULL, NULL);
+  // Clear the Watchdog Timer after the image returns
+  gBS->SetWatchdogTimer (0x0000, 0x0000, 0x0000, NULL);
+  return EFI_SUCCESS;
+}