Merge "QcomModulePkg: Implement Scm Mode switch protocol"
diff --git a/QcomModulePkg/Include/Library/UpdateDeviceTree.h b/QcomModulePkg/Include/Library/UpdateDeviceTree.h
index dbe4cba..a6d8262 100644
--- a/QcomModulePkg/Include/Library/UpdateDeviceTree.h
+++ b/QcomModulePkg/Include/Library/UpdateDeviceTree.h
@@ -44,6 +44,7 @@
 
 #define MSMCOBALT_PGOOD_FUSE		0x78013C
 #define MSMCOBALT_PGOOD_SUBBIN_FUSE	0x780324
+#define DTB_OFFSET_LOCATION_IN_ARCH32_KERNEL_HDR   0x2C
 
 #define PARTIAL_GOOD_GOLD_DISABLE 0x1
 
diff --git a/QcomModulePkg/Library/BootLib/BootLib.inf b/QcomModulePkg/Library/BootLib/BootLib.inf
index 1feca8e..34ceaab 100644
--- a/QcomModulePkg/Library/BootLib/BootLib.inf
+++ b/QcomModulePkg/Library/BootLib/BootLib.inf
@@ -130,6 +130,7 @@
 	gQcomChargerExProtocolGuid
 	gEfiLimitsProtocolGuid
 	gEfiQcomVerifiedBootProtocolGuid
+	gQcomScmModeSwithProtocolGuid
 
 [FixedPcd]
 	gArmTokenSpaceGuid.PcdSystemMemoryBase
@@ -140,6 +141,7 @@
 	gQcomTokenSpaceGuid.RamdiskEndAddress
 	gQcomTokenSpaceGuid.EnablePartialGoods
 	gQcomTokenSpaceGuid.EnableDisplayMenu
+	gQcomTokenSpaceGuid.KernelLoadAddress32
 
 [Depex]
 	TRUE
diff --git a/QcomModulePkg/Library/BootLib/BootLinux.c b/QcomModulePkg/Library/BootLib/BootLinux.c
index e97733f..77a5087 100644
--- a/QcomModulePkg/Library/BootLib/BootLinux.c
+++ b/QcomModulePkg/Library/BootLib/BootLinux.c
@@ -32,11 +32,33 @@
 
 #include <Library/VerifiedBootMenu.h>
 #include <Library/DrawUI.h>
+#include <Protocol/EFIScmModeSwitch.h>
 
 #include "BootLinux.h"
 #include "BootStats.h"
+#include "BootImage.h"
+#include "UpdateDeviceTree.h"
 
 STATIC BOOLEAN VerifiedBootEnbled();
+STATIC QCOM_SCM_MODE_SWITCH_PROTOCOL *pQcomScmModeSwitchProtocol = NULL;
+
+STATIC EFI_STATUS SwitchTo32bitModeBooting(UINT64 KernelLoadAddr, UINT64 DeviceTreeLoadAddr) {
+	EFI_STATUS Status;
+	EFI_HLOS_BOOT_ARGS HlosBootArgs;
+
+	SetMem((VOID*)&HlosBootArgs, sizeof(HlosBootArgs), 0);
+	HlosBootArgs.el1_x2 = DeviceTreeLoadAddr;
+	/* Write 0 into el1_x4 to switch to 32bit mode */
+	HlosBootArgs.el1_x4 = 0;
+	HlosBootArgs.el1_elr = KernelLoadAddr;
+	Status = pQcomScmModeSwitchProtocol->SwitchTo32bitMode(HlosBootArgs);
+	if (EFI_ERROR(Status)) {
+		DEBUG((EFI_D_ERROR, "ERROR: Failed to switch to 32 bit mode.Status= %r\n",Status));
+		return Status;
+	}
+	/*Return Unsupported if the execution ever reaches here*/
+	return EFI_NOT_STARTED;
+}
 
 EFI_STATUS BootLinux (VOID *ImageBuffer, UINT32 ImageSize, DeviceInfo *DevInfo, CHAR8 *pname, BOOLEAN Recovery)
 {
@@ -49,6 +71,7 @@
 	STATIC UINT32 KernelSizeActual;
 	STATIC UINT32 RamdiskSizeActual;
 	STATIC UINT32 SecondSizeActual;
+	struct kernel64_hdr* Kptr = NULL;
 
 	/*Boot Image header information variables*/
 	STATIC UINT32 KernelSize;
@@ -71,6 +94,7 @@
 	QCOM_VERIFIEDBOOT_PROTOCOL *VbIntf;
 	device_info_vb_t DevInfo_vb;
 	STATIC CHAR8 StrPartition[MAX_PNAME_LENGTH];
+	BOOLEAN BootingWith32BitKernel = FALSE;
 
 	if (VerifiedBootEnbled())
 	{
@@ -168,6 +192,22 @@
 		}
 
 		DEBUG((EFI_D_INFO, "Decompressing kernel image done: %u ms\n", GetTimerCountms()));
+		Kptr = KernelLoadAddr;
+	} else {
+		if (CHECK_ADD64(ImageBuffer, PageSize)) {
+			DEBUG((EFI_D_ERROR, "Integer Overflow: in Kernel header fields addition\n"));
+			return EFI_BAD_BUFFER_SIZE;
+		}
+		Kptr = ImageBuffer + PageSize;
+	}
+	if (Kptr->magic_64 != KERNEL64_HDR_MAGIC) {
+		BootingWith32BitKernel = TRUE;
+		KernelLoadAddr = (EFI_PHYSICAL_ADDRESS)(BaseMemory | PcdGet32(KernelLoadAddress32));
+		if (CHECK_ADD64((VOID*)Kptr, DTB_OFFSET_LOCATION_IN_ARCH32_KERNEL_HDR)) {
+			DEBUG((EFI_D_ERROR, "Integer Overflow: in DTB offset addition\n"));
+			return EFI_BAD_BUFFER_SIZE;
+		}
+		CopyMem((VOID*)&DtbOffset, ((VOID*)Kptr + DTB_OFFSET_LOCATION_IN_ARCH32_KERNEL_HDR), sizeof(DtbOffset));
 	}
 
 	/*Finds out the location of device tree image and ramdisk image within the boot image
@@ -228,6 +268,18 @@
 	}
 	CopyMem (RamdiskLoadAddr, ImageBuffer + RamdiskOffset, RamdiskSize);
 
+	if (BootingWith32BitKernel) {
+		if (CHECK_ADD64(KernelLoadAddr, KernelSizeActual)) {
+			DEBUG((EFI_D_ERROR, "Integer Overflow: while Kernel image copy\n"));
+			return EFI_BAD_BUFFER_SIZE;
+		}
+		if (KernelLoadAddr + KernelSizeActual > DeviceTreeLoadAddr) {
+			DEBUG((EFI_D_ERROR, "Kernel size is over the limit\n"));
+			return EFI_INVALID_PARAMETER;
+		}
+		CopyMem(KernelLoadAddr, ImageBuffer + PageSize, KernelSizeActual);
+	}
+
 	if (FixedPcdGetBool(EnablePartialGoods))
 	{
 		Status = UpdatePartialGoodsNode((VOID*)DeviceTreeLoadAddr);
@@ -247,11 +299,18 @@
 			return Status;
 		}
 	}
-	DEBUG((EFI_D_INFO, "\nShutting Down UEFI Boot Services: %u ms\n\n", GetTimerCountms()));
 
 	/* Free the boot logo blt buffer before starting kernel */
 	FreeBootLogoBltBuffer();
+	if (BootingWith32BitKernel) {
+		Status = gBS->LocateProtocol(&gQcomScmModeSwithProtocolGuid, NULL, (VOID**)&pQcomScmModeSwitchProtocol);
+		if(EFI_ERROR(Status)) {
+			DEBUG((EFI_D_ERROR,"ERROR: Unable to Locate Protocol handle for ScmModeSwicthProtocol Status=%r\n", Status));
+			return Status;
+		}
+	}
 
+	DEBUG((EFI_D_INFO, "\nShutting Down UEFI Boot Services: %u ms\n", GetTimerCountms()));
 	/*Shut down UEFI boot services*/
 	Status = ShutdownUefiBootServices ();
 	if(EFI_ERROR(Status)) {
@@ -270,6 +329,11 @@
 	//
 	// Start the Linux Kernel
 	//
+	if (BootingWith32BitKernel) {
+		Status = SwitchTo32bitModeBooting((UINT64)KernelLoadAddr, (UINT64)DeviceTreeLoadAddr);
+		return Status;
+	}
+
 	LinuxKernel = (LINUX_KERNEL)(UINTN)KernelLoadAddr;
 	LinuxKernel ((UINTN)DeviceTreeLoadAddr, 0, 0, 0);
 
diff --git a/QcomModulePkg/QcomModulePkg.dec b/QcomModulePkg/QcomModulePkg.dec
index e44838f..f6d921b 100644
--- a/QcomModulePkg/QcomModulePkg.dec
+++ b/QcomModulePkg/QcomModulePkg.dec
@@ -107,6 +107,8 @@
   gEfiLimitsProtocolGuid =              { 0x79d6c879, 0x725e, 0x489e, { 0xa0, 0xa9, 0x27, 0xef, 0xa5, 0xdf, 0xcb, 0x35 } }
   # VerifiedBoot Protocol
   gEfiQcomVerifiedBootProtocolGuid =    { 0x8e5eff91, 0x21b6, 0x47d3, { 0xaf, 0x2b, 0xc1, 0x5a, 0x1, 0xe0, 0x20, 0xec } }
+  # Scm mode switch Protocol
+  gQcomScmModeSwithProtocolGuid =       { 0xf57f73ed, 0x0afc, 0x4723, { 0x93, 0x74, 0x2c, 0xeb, 0xc0, 0x19, 0x8e, 0xf9 } }
 
 [PcdsFixedAtBuild.common]
   # LinuxLoaderCommon
@@ -116,3 +118,4 @@
   gQcomTokenSpaceGuid.RamdiskEndAddress|0x05800000|UINT32|0x00015003
   gQcomTokenSpaceGuid.EnablePartialGoods|TRUE|BOOLEAN|0x00015004
   gQcomTokenSpaceGuid.EnableDisplayMenu|TRUE|BOOLEAN|0x00015005
+  gQcomTokenSpaceGuid.KernelLoadAddress32|0x00008000|UINT32|0x00015006