CloverBootloader/MemoryFix/OsxAptioFixDrv/BootFixes.c

903 lines
31 KiB
C
Raw Normal View History

/**
Methods for setting callback jump from kernel entry point, callback, fixes to kernel boot image.
by dmazar
**/
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/OcDeviceTreeLib.h>
#include "BootFixes.h"
#include "AsmFuncs.h"
#include "BootArgs.h"
#include "VMem.h"
#include "Lib.h"
//#include "FlatDevTree/device_tree.h"
#include "Mach-O/Mach-O.h"
#include "Hibernate.h"
#include "NVRAMDebug.h"
// DBG_TO: 0=no debug, 1=serial, 2=console
// serial requires
// [PcdsFixedAtBuild]
// gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x07
// gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0xFFFFFFFF
// in package DSC file
#define DBG_TO 0
#if DBG_TO == 2
#define DBG(...) AsciiPrint(__VA_ARGS__);
#elif DBG_TO == 1
#define DBG(...) DebugPrint(1, __VA_ARGS__);
#else
#define DBG(...)
#endif
// buffer and size for original kernel entry code
UINT8 gOrigKernelCode[32];
UINTN gOrigKernelCodeSize = 0;
// buffer for virtual address map - only for RT areas
// note: DescriptorSize is usually > sizeof(EFI_MEMORY_DESCRIPTOR)
// so this buffer can hold less then 64 descriptors
EFI_MEMORY_DESCRIPTOR gVirtualMemoryMap[64];
UINTN gVirtualMapSize = 0;
UINTN gVirtualMapDescriptorSize = 0;
EFI_PHYSICAL_ADDRESS gSysTableRtArea;
EFI_PHYSICAL_ADDRESS gRelocatedSysTableRtArea;
void PrintSample2(unsigned char *sample, int size) {
int i;
for (i = 0; i < size; i++) {
DBG(" %02x", *sample);
sample++;
}
}
//
// Kernel entry patching
//
/** Saves current 64 bit state and copies MyAsmCopyAndJumpToKernel32 function to higher mem
* (for copying kernel back to proper place and jumping back to it).
*/
EFI_STATUS
PrepareJumpFromKernel(VOID)
{
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS HigherMem;
UINTN Size;
EFI_SYSTEM_TABLE *Src;
EFI_SYSTEM_TABLE *Dest;
//
// chek if already prepared
//
if (MyAsmCopyAndJumpToKernel32Addr != 0) {
DBG("PrepareJumpFromKernel() - already prepared\n");
return EFI_SUCCESS;
}
//
// save current 64bit state - will be restored later in callback from kernel jump
//
MyAsmPrepareJumpFromKernel();
//
// allocate higher memory for MyAsmCopyAndJumpToKernel code
//
HigherMem = 0x100000000;
Status = AllocatePagesFromTop(EfiBootServicesCode, 1, &HigherMem);
if (Status != EFI_SUCCESS) {
Print(L"OsxAptioFixDrv: PrepareJumpFromKernel(): can not allocate mem for MyAsmCopyAndJumpToKernel (0x%x pages on mem top): %r\n",
1, Status);
return Status;
}
//
// and relocate it to higher mem
//
MyAsmCopyAndJumpToKernel32Addr = HigherMem + ( (UINT8*)(UINTN)&MyAsmCopyAndJumpToKernel32 - (UINT8*)(UINTN)&MyAsmCopyAndJumpToKernel );
MyAsmCopyAndJumpToKernel64Addr = HigherMem + ( (UINT8*)(UINTN)&MyAsmCopyAndJumpToKernel64 - (UINT8*)(UINTN)&MyAsmCopyAndJumpToKernel );
Size = (UINT8*)&MyAsmCopyAndJumpToKernelEnd - (UINT8*)&MyAsmCopyAndJumpToKernel;
if (Size > EFI_PAGES_TO_SIZE(1)) {
Print(L"Size of MyAsmCopyAndJumpToKernel32 code is too big\n");
return EFI_BUFFER_TOO_SMALL;
}
CopyMem((VOID *)(UINTN)HigherMem, (VOID *)&MyAsmCopyAndJumpToKernel, Size);
DBG("PrepareJumpFromKernel(): MyAsmCopyAndJumpToKernel relocated from %p, to %x, size = %x\n",
&MyAsmCopyAndJumpToKernel, HigherMem, Size);
DBG(" MyAsmCopyAndJumpToKernel32 relocated from %p, to %x\n",
&MyAsmCopyAndJumpToKernel32, MyAsmCopyAndJumpToKernel32Addr);
DBG(" MyAsmCopyAndJumpToKernel64 relocated from %p, to %x\n",
&MyAsmCopyAndJumpToKernel64, MyAsmCopyAndJumpToKernel64Addr);
DBG("SavedCR3 = %x, SavedGDTR = %x, SavedIDTR = %x\n", SavedCR3, SavedGDTR, SavedIDTR);
DBGnvr("PrepareJumpFromKernel(): MyAsmCopyAndJumpToKernel relocated from %p, to %x, size = %x\n",
&MyAsmCopyAndJumpToKernel, HigherMem, Size);
// Allocate 1 RT data page for copy of EFI system table for kernel
gSysTableRtArea = 0x100000000;
Status = AllocatePagesFromTop(EfiRuntimeServicesData, 1, &gSysTableRtArea);
if (Status != EFI_SUCCESS) {
Print(L"OsxAptioFixDrv: PrepareJumpFromKernel(): can not allocate mem for RT area for EFI system table: %r\n",
1, Status);
return Status;
}
DBG("gSysTableRtArea = %lx\n", gSysTableRtArea);
// Copy sys table to our location
Src = (EFI_SYSTEM_TABLE*)(UINTN)gST;
Dest = (EFI_SYSTEM_TABLE*)(UINTN)gSysTableRtArea;
DBG("-Copy %p <- %p, size=0x%lx\n", Dest, Src, Src->Hdr.HeaderSize);
CopyMem(Dest, Src, Src->Hdr.HeaderSize);
return Status;
}
/** Patches kernel entry point with jump to MyAsmJumpFromKernel (AsmFuncsX64). This will then call KernelEntryPatchJumpBack. */
EFI_STATUS
KernelEntryPatchJump(UINT32 KernelEntry)
{
EFI_STATUS Status;
Status = EFI_SUCCESS;
DBG("KernelEntryPatchJump KernelEntry (reloc): %lx (%lx)\n", KernelEntry, KernelEntry + gRelocBase);
// Size of MyAsmEntryPatchCode code
gOrigKernelCodeSize = (UINT8*)&MyAsmEntryPatchCodeEnd - (UINT8*)&MyAsmEntryPatchCode;
if (gOrigKernelCodeSize > sizeof(gOrigKernelCode)) {
DBG("KernelEntryPatchJump: not enough space for orig. kernel entry code: size needed: %d\n", gOrigKernelCodeSize);
return EFI_NOT_FOUND;
}
DBG("MyAsmEntryPatchCode: %p, Size: %d, MyAsmJumpFromKernel: %p\n", &MyAsmEntryPatchCode, gOrigKernelCodeSize, &MyAsmJumpFromKernel);
// Save original kernel entry code
CopyMem((VOID *)gOrigKernelCode, (VOID *)(UINTN)KernelEntry, gOrigKernelCodeSize);
// Copy MyAsmEntryPatchCode code to kernel entry address
CopyMem((VOID *)(UINTN)KernelEntry, (VOID *)&MyAsmEntryPatchCode, gOrigKernelCodeSize);
DBG("Entry point %x is now: ", KernelEntry);
PrintSample2((UINT8 *)(UINTN) KernelEntry, 12);
DBG("\n");
// pass KernelEntry to assembler funcs
// this is not needed really, since asm code will determine
// kernel entry address from the stack
AsmKernelEntry = KernelEntry;
return Status;
}
/** Reads kernel entry from Mach-O load command and patches it with jump to MyAsmJumpFromKernel. */
EFI_STATUS
KernelEntryFromMachOPatchJump(VOID *MachOImage, UINTN SlideAddr)
{
UINTN KernelEntry;
DBG("KernelEntryFromMachOPatchJump: MachOImage = %p, SlideAddr = %x\n", MachOImage, SlideAddr);
KernelEntry = MachOGetEntryAddress(MachOImage);
DBG("KernelEntryFromMachOPatchJump: KernelEntry = %x\n", KernelEntry);
if (KernelEntry == 0) {
return EFI_NOT_FOUND;
}
if (SlideAddr > 0) {
KernelEntry += SlideAddr;
DBG("KernelEntryFromMachOPatchJump: Slided KernelEntry = %x\n", KernelEntry);
}
return KernelEntryPatchJump((UINT32)KernelEntry);
}
/** Patches kernel entry point with HLT - used for testing to cause system halt. */
EFI_STATUS
KernelEntryPatchHalt(UINT32 KernelEntry)
{
EFI_STATUS Status;
unsigned char *p;
Status = EFI_SUCCESS;
DBG("KernelEntryPatchHalt KernelEntry (reloc): %lx (%lx)", KernelEntry, KernelEntry + gRelocBase);
p = (UINT8 *)(UINTN) KernelEntry;
*p= 0xf4; // HLT instruction
PrintSample2(p, 4);
DBG("\n");
return Status;
}
/** Patches kernel entry point with zeros - used for testing to cause restart. */
EFI_STATUS
KernelEntryPatchZero (UINT32 KernelEntry)
{
EFI_STATUS Status;
unsigned char *p;
Status = EFI_SUCCESS;
DBG("KernelEntryPatchZero KernelEntry (reloc): %lx (%lx)", KernelEntry, KernelEntry + gRelocBase);
p = (UINT8 *)(UINTN) KernelEntry;
//*p= 0xf4;
p[0]= 0; p[1]= 0; p[2]= 0; p[3]= 0; // invalid instruction
PrintSample2(p, 4);
DBG("\n");
return Status;
}
//
// Boot fixes
//
/** Copies RT flagged areas to separate memmap, defines virtual to phisycal address mapping
* and calls SetVirtualAddressMap() only with that partial memmap.
*
* About partial memmap:
* Some UEFIs are converting pointers to virtual addresses even if they do not
* point to regions with RT flag. This means that those UEFIs are using
* Desc->VirtualStart even for non-RT regions. Linux had issues with this:
* http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=7cb00b72876ea2451eb79d468da0e8fb9134aa8a
* They are doing it Windows way now - copying RT descriptors to separate
* mem map and passing that stripped map to SetVirtualAddressMap().
* We'll do the same, although it seems that just assigning
* VirtualStart = PhysicalStart for non-RT areas also does the job.
*
* About virtual to phisycal mappings:
* Also adds virtual to phisycal address mappings for RT areas. This is needed since
* SetVirtualAddressMap() does not work on my Aptio without that. Probably because some driver
* has a bug and is trying to access new virtual addresses during the change.
* Linux and Windows are doing the same thing and problem is
* not visible there.
*/
EFI_STATUS
ExecSetVirtualAddressesToMemMap(
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize,
IN UINT32 DescriptorVersion,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap
)
{
UINTN NumEntries;
UINTN Index;
EFI_MEMORY_DESCRIPTOR *Desc;
EFI_MEMORY_DESCRIPTOR *VirtualDesc;
EFI_STATUS Status;
PAGE_MAP_AND_DIRECTORY_POINTER *PageTable;
UINTN Flags;
UINTN BlockSize;
Desc = MemoryMap;
NumEntries = MemoryMapSize / DescriptorSize;
VirtualDesc = gVirtualMemoryMap;
gVirtualMapSize = 0;
gVirtualMapDescriptorSize = DescriptorSize;
DBG("ExecSetVirtualAddressesToMemMap: Size=%d, Addr=%p, DescSize=%d\n", MemoryMapSize, MemoryMap, DescriptorSize);
// get current VM page table
GetCurrentPageTable(&PageTable, &Flags);
for (Index = 0; Index < NumEntries; Index++) {
if ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
// check if there is enough space in gVirtualMemoryMap
if (gVirtualMapSize + DescriptorSize > sizeof(gVirtualMemoryMap)) {
DBGnvr("ERROR: too much mem map RT areas\n");
return EFI_OUT_OF_RESOURCES;
}
// copy region with EFI_MEMORY_RUNTIME flag to gVirtualMemoryMap
//DBGnvr(" %lx (%lx)\n", Desc->PhysicalStart, Desc->NumberOfPages);
CopyMem((VOID*)VirtualDesc, (VOID*)Desc, DescriptorSize);
// define virtual to phisical mapping
DBG("Map pages: %lx (%x) -> %lx\n", Desc->VirtualStart, Desc->NumberOfPages, Desc->PhysicalStart);
DBGnvr("Map pages: %lx (%x) -> %lx\n", Desc->VirtualStart, Desc->NumberOfPages, Desc->PhysicalStart);
VmMapVirtualPages(PageTable, Desc->VirtualStart, Desc->NumberOfPages, Desc->PhysicalStart);
// next gVirtualMemoryMap slot
VirtualDesc = NEXT_MEMORY_DESCRIPTOR(VirtualDesc, DescriptorSize);
gVirtualMapSize += DescriptorSize;
// Remember future physical address for our special relocated
// efi system table
BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
if (Desc->PhysicalStart <= gSysTableRtArea && gSysTableRtArea < (Desc->PhysicalStart + BlockSize)) {
// block contains our future sys table - remember new address
// future physical = VirtualStart & 0x7FFFFFFFFF
gRelocatedSysTableRtArea = (Desc->VirtualStart & 0x7FFFFFFFFF) + (gSysTableRtArea - Desc->PhysicalStart);
}
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
}
VmFlashCaches();
DBGnvr("ExecSetVirtualAddressesToMemMap: Size=%d, Addr=%p, DescSize=%d\nSetVirtualAddressMap ... ",
gVirtualMapSize, MemoryMap, DescriptorSize);
Status = gRT->SetVirtualAddressMap(gVirtualMapSize, DescriptorSize, DescriptorVersion, gVirtualMemoryMap);
DBGnvr("%r\n", Status);
return Status;
}
VOID
CopyEfiSysTableToSeparateRtDataArea(
IN OUT UINT32 *EfiSystemTable
)
{
EFI_SYSTEM_TABLE *Src;
EFI_SYSTEM_TABLE *Dest;
Src = (EFI_SYSTEM_TABLE*)(UINTN)*EfiSystemTable;
Dest = (EFI_SYSTEM_TABLE*)(UINTN)gSysTableRtArea;
DBG("-Copy %p <- %p, size=0x%lx\n", Dest, Src, Src->Hdr.HeaderSize);
CopyMem(Dest, Src, Src->Hdr.HeaderSize);
*EfiSystemTable = (UINT32)(UINTN)Dest;
}
/** Protect RT data from relocation by marking them MemMapIO. Except area with EFI system table.
* This one must be relocated into kernel boot image or kernel will crash (kernel accesses it
* before RT areas are mapped into vm).
* This fixes NVRAM issues on some boards where access to nvram after boot services is possible
* only in SMM mode. RT driver passes data to SM handler through previously negotiated buffer
* and this buffer must not be relocated.
* Explained and examined in detail by CodeRush and night199uk:
* http://www.projectosx.com/forum/index.php?showtopic=3298
*
* It seems this does not do any harm to others where this is not needed,
* so it's added as standard fix for all.
*/
VOID
ProtectRtDataFromRelocation(
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize,
IN UINT32 DescriptorVersion,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap
)
{
UINTN NumEntries;
UINTN Index;
EFI_MEMORY_DESCRIPTOR *Desc;
// UINTN BlockSize;
Desc = MemoryMap;
NumEntries = MemoryMapSize / DescriptorSize;
DBG("FixNvramRelocation\n");
DBGnvr("FixNvramRelocation\n");
for (Index = 0; Index < NumEntries; Index++) {
// BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
if ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
if (Desc->Type == EfiRuntimeServicesData && Desc->PhysicalStart != gSysTableRtArea)
{
DBG(" RT data %lx (0x%x) -> MemMapIO\n", Desc->PhysicalStart, Desc->NumberOfPages);
Desc->Type = EfiMemoryMappedIO;
}
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
}
}
/** Assignes OSX virtual addresses to runtime areas in memory map. */
VOID
AssignVirtualAddressesToMemMap(
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize,
IN UINT32 DescriptorVersion,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap,
IN EFI_PHYSICAL_ADDRESS KernelRTAddress
)
{
UINTN NumEntries;
UINTN Index;
EFI_MEMORY_DESCRIPTOR *Desc;
UINTN BlockSize;
Desc = MemoryMap;
NumEntries = MemoryMapSize / DescriptorSize;
DBG("AssignVirtualAddressesToMemMap: Size=%d, Addr=%p, DescSize=%d\n", MemoryMapSize, MemoryMap, DescriptorSize);
DBGnvr("AssignVirtualAddressesToMemMap: Size=%d, Addr=%p, DescSize=%d\n", MemoryMapSize, MemoryMap, DescriptorSize);
for (Index = 0; Index < NumEntries; Index++) {
BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
// assign virtual addresses to all EFI_MEMORY_RUNTIME marked pages (including MMIO)
if ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
// BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
if (Desc->Type == EfiRuntimeServicesCode || Desc->Type == EfiRuntimeServicesData) {
// for RT block - assign from kernel block
Desc->VirtualStart = KernelRTAddress + 0xffffff8000000000;
// next kernel block
KernelRTAddress += BlockSize;
} else if (Desc->Type == EfiMemoryMappedIO || Desc->Type == EfiMemoryMappedIOPortSpace) {
// for MMIO block - assign from kernel block
Desc->VirtualStart = KernelRTAddress + 0xffffff8000000000;
// next kernel block
KernelRTAddress += BlockSize;
} else {
// runtime flag, but not RT and not MMIO - what now???
DBG(" %s with RT flag: %lx (0x%x) - ???\n", EfiMemoryTypeDesc[Desc->Type], Desc->PhysicalStart, Desc->NumberOfPages);
DBGnvr(" %s with RT flag: %lx (0x%x) - ???\n", EfiMemoryTypeDesc[Desc->Type], Desc->PhysicalStart, Desc->NumberOfPages);
}
//DBGnvr("=> 0x%lx -> 0x%lx\n", Desc->PhysicalStart, Desc->VirtualStart);
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
}
}
/** Copies RT code and data blocks to reserved area inside kernel boot image. */
VOID
DefragmentRuntimeServices(
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize,
IN UINT32 DescriptorVersion,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap,
IN OUT UINT32 *EfiSystemTable,
IN BOOLEAN SkipOurSysTableRtArea
)
{
UINTN NumEntries;
UINTN Index;
EFI_MEMORY_DESCRIPTOR *Desc;
UINT8 *KernelRTBlock;
UINTN BlockSize;
Desc = MemoryMap;
NumEntries = MemoryMapSize / DescriptorSize;
DBG("DefragmentRuntimeServices: pBootArgs->efiSystemTable = %x\n", *EfiSystemTable);
DBGnvr("DefragmentRuntimeServices: pBootArgs->efiSystemTable = %x\n", *EfiSystemTable);
for (Index = 0; Index < NumEntries; Index++) {
// defragment only RT blocks
if (Desc->Type == EfiRuntimeServicesCode || Desc->Type == EfiRuntimeServicesData) {
// skip our block with sys table copy if required
if (SkipOurSysTableRtArea && Desc->PhysicalStart == gSysTableRtArea) {
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
continue;
}
// physical addr from virtual
KernelRTBlock = (UINT8*)(UINTN)(Desc->VirtualStart & 0x7FFFFFFFFF);
BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
DBG("-Copy %p <- %p, size=0x%lx\n", KernelRTBlock + gRelocBase, (VOID*)(UINTN)Desc->PhysicalStart, BlockSize);
CopyMem(KernelRTBlock + gRelocBase, (VOID*)(UINTN)Desc->PhysicalStart, BlockSize);
// boot.efi zeros old RT areas, but we must not do that because that brakes sleep
// on some UEFIs. why?
//SetMem((VOID*)(UINTN)Desc->PhysicalStart, BlockSize, 0);
if (EfiSystemTable != NULL && Desc->PhysicalStart <= *EfiSystemTable && *EfiSystemTable < (Desc->PhysicalStart + BlockSize)) {
// block contains sys table - update bootArgs with new address
*EfiSystemTable = (UINT32)((UINTN)KernelRTBlock + (*EfiSystemTable - Desc->PhysicalStart));
DBG("new pBootArgs->efiSystemTable = %x\n", *EfiSystemTable);
DBGnvr("new pBootArgs->efiSystemTable = %x\n", *EfiSystemTable);
}
// mark old RT block in MemMap as free mem
//Desc->Type = EfiConventionalMemory;
// mark old RT block in MemMap as ACPI NVS
// if sleep is broken if if those areas are zeroed, maybe
// it's safer to mark it ACPI NVS then make it free
Desc->Type = EfiACPIMemoryNVS;
// and remove RT attribute
Desc->Attribute = Desc->Attribute & (~EFI_MEMORY_RUNTIME);
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
}
}
/** Fixes RT vars in bootArgs, virtualizes and defragments RT blocks. */
VOID
RuntimeServicesFix(BootArgs *BA)
{
EFI_STATUS Status;
UINT32 gRelocBasePage = (UINT32)EFI_SIZE_TO_PAGES(gRelocBase);
UINTN MemoryMapSize;
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
MemoryMapSize = *BA->MemoryMapSize;
MemoryMap = (EFI_MEMORY_DESCRIPTOR*)(UINTN)(*BA->MemoryMap);
DescriptorSize = *BA->MemoryMapDescriptorSize;
DescriptorVersion = *BA->MemoryMapDescriptorVersion;
DBGnvr("RuntimeServicesFix ...\n");
DBG("RuntimeServicesFix: efiRSPageStart=%x, efiRSPageCount=%x, efiRSVirtualPageStart=%lx\n",
*BA->efiRuntimeServicesPageStart, *BA->efiRuntimeServicesPageCount, *BA->efiRuntimeServicesVirtualPageStart);
// fix runtime entries
*BA->efiRuntimeServicesPageStart -= gRelocBasePage;
// VirtualPageStart is ok in boot args (a miracle!), but we'll do it anyway
*BA->efiRuntimeServicesVirtualPageStart = 0x000ffffff8000000 + *BA->efiRuntimeServicesPageStart;
DBG("RuntimeServicesFix: efiRSPageStart=%x, efiRSPageCount=%x, efiRSVirtualPageStart=%lx\n",
*BA->efiRuntimeServicesPageStart, *BA->efiRuntimeServicesPageCount, *BA->efiRuntimeServicesVirtualPageStart);
// Protect RT data areas from relocation by marking then MemMapIO
ProtectRtDataFromRelocation(MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap);
// assign virtual addresses
AssignVirtualAddressesToMemMap(MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap, EFI_PAGES_TO_SIZE(*BA->efiRuntimeServicesPageStart));
//PrintMemMap(MemoryMapSize, MemoryMap, DescriptorSize, DescriptorVersion);
//PrintSystemTable(gST);
// virtualize RT services with all needed fixes
Status = ExecSetVirtualAddressesToMemMap(MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap);
//DBG("SetVirtualAddressMap() = Status: %r\n", Status);
if (EFI_ERROR(Status)) {
CpuDeadLoop();
}
CopyEfiSysTableToSeparateRtDataArea(BA->efiSystemTable);
//PrintSystemTable(gST);
// and defragment
DefragmentRuntimeServices(MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap, BA->efiSystemTable, FALSE);
}
/** DevTree contains /chosen/memory-map with properties with 8 byte values
* (DTMemMapEntry: UINT32 Address, UINT32 Length):
* "name" = this is exception - not DTMemMapEntry
* "BootCLUT" = 8bit boot time colour lookup table
* "Pict-FailedBoot" = picture shown if booting fails
* "RAMDisk" = ramdisk
* "Driver-<hex addr of BooterKextFileInfo>" = Kext, UINT32 Address points to BooterKextFileInfo
* "DriversPackage-..." = MKext, UINT32 Address points to mkext_header (libkern/libkern/mkext.h), UINT32 length
*
* We are fixing here DTMemMapEntry.Address for all those entries.
* Plus, for every loaded kext, Address points to BooterKextFileInfo,
* and we are fixing it's pointers also.
*/
VOID
DevTreeFix(BootArgs *BA)
{
DTEntry DevTree;
DTEntry MemMap;
CHAR8 *PropName;
DTMemMapEntry *PropValue;
BooterKextFileInfo *KextInfo;
OpaqueDTPropertyIterator OPropIter;
DTPropertyIterator PropIter = &OPropIter;
DevTree = (DTEntry)(UINTN)(*BA->deviceTreeP);
DBG("Fixing DevTree at %p\n", DevTree);
DBGnvr("Fixing DevTree at %p\n", DevTree);
DTInit(DevTree, BA->deviceTreeLength);
if (!EFI_ERROR(DTLookupEntry(NULL, "/chosen/memory-map", &MemMap))) {
DBG("Found /chosen/memory-map\n");
if (!EFI_ERROR(DTCreatePropertyIterator(MemMap, &OPropIter))) {
DBG("DTCreatePropertyIterator OK\n");
while (!EFI_ERROR(DTIterateProperties(PropIter, &PropName))) {
DBG("= %a, val len=%d: ", PropName, PropIter->CurrentProperty->Length);
// all /chosen/memory-map props have DTMemMapEntry (address, length)
// values. we need to correct the address
// basic check that value is 2 * UINT32
if (PropIter->CurrentProperty->Length != 2 * sizeof(UINT32)) {
// not DTMemMapEntry, usually "name" property
DBG("NOT DTMemMapEntry\n");
continue;
}
// get value (Address and Length)
PropValue = (DTMemMapEntry*)(((UINT8*)PropIter->CurrentProperty) + sizeof(DTProperty));
DBG("MM Addr = %x, Len = %x ", PropValue->Address, PropValue->Length);
// second check - Address is in our reloc block
// (note: *BA->kaddr is not fixed yet and points to reloc block)
if ((PropValue->Address < *BA->kaddr)
|| (PropValue->Address >= *BA->kaddr + *BA->ksize))
{
DBG("DTMemMapEntry->Address is not in reloc block, skipping\n");
continue;
}
// check if this is Kext entry
if (AsciiStrnCmp(PropName, BOOTER_KEXT_PREFIX, AsciiStrLen(BOOTER_KEXT_PREFIX)) == 0) {
// yes - fix kext pointers
KextInfo = (BooterKextFileInfo*)(UINTN)PropValue->Address;
DBG(" = KEXT %a at %x ", (CHAR8*)(UINTN)KextInfo->bundlePathPhysAddr, KextInfo->infoDictPhysAddr);
KextInfo->infoDictPhysAddr -= (UINT32)gRelocBase;
KextInfo->executablePhysAddr -= (UINT32)gRelocBase;
KextInfo->bundlePathPhysAddr -= (UINT32)gRelocBase;
DBG("-> %x ", KextInfo->infoDictPhysAddr);
}
// fix address in mem map entry
PropValue->Address -= (UINT32)gRelocBase;
DBG("=> Fixed MM Addr = %x\n", PropValue->Address);
}
}
}
}
/** boot.efi zerod original RT areas after they were relocated to new place.
* This breaks sleep on some UEFIs and we'll return the content back.
* We'll find previous RT areas by reusing gVirtualMemoryMap.
*
* If MemoryMap is passed also (it is in regular boot), then we'll
* mark original RT areas as ACPI NVS. Without that I can not do more
* then one hibernate/wake cycle.
* It seems that my UEFI contains something needed for sleep in those
* RT areas and system needs it to stay on that place. It would be good to know
* what is happening here.
*/
VOID
ReturnPreviousRTAreasContent(
IN UINTN MemoryMapSize,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap
)
{
UINTN NumEntries;
UINTN NumEntries2;
UINTN Index;
UINTN Index2;
UINTN BlockSize;
EFI_MEMORY_DESCRIPTOR *Desc;
EFI_MEMORY_DESCRIPTOR *Desc2;
EFI_PHYSICAL_ADDRESS NewPhysicalStart;
Desc = gVirtualMemoryMap;
NumEntries = gVirtualMapSize / gVirtualMapDescriptorSize;
NumEntries2 = MemoryMapSize / gVirtualMapDescriptorSize;
for (Index = 0; Index < NumEntries; Index++) {
if ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
if (Desc->Type == EfiRuntimeServicesCode || Desc->Type == EfiRuntimeServicesData) {
// Desc->VirtualStart contains virtual address of new area
// and physical address for new area can be find from it.
NewPhysicalStart = Desc->VirtualStart & 0x7FFFFFFFFF;
BlockSize = EFI_PAGES_TO_SIZE((UINTN)Desc->NumberOfPages);
DBG("-Copy %p <- %p, size=0x%lx\n", (VOID*)(UINTN)Desc->PhysicalStart, (VOID*)(UINTN)NewPhysicalStart, BlockSize);
CopyMem((VOID*)(UINTN)Desc->PhysicalStart, (VOID*)(UINTN)NewPhysicalStart, BlockSize);
// if full memory map is passed then mark old RT block in OSX MemMap as ACPI NVS in it
if (MemoryMap) {
Desc2 = MemoryMap;
for (Index2 = 0; Index2 < NumEntries2; Index2++) {
if (Desc->PhysicalStart == Desc2->PhysicalStart) {
Desc2->Type = EfiACPIMemoryNVS;
//Desc2->Type = EfiReservedMemoryType;
}
Desc2 = NEXT_MEMORY_DESCRIPTOR(Desc2, gVirtualMapDescriptorSize);
}
}
}
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, gVirtualMapDescriptorSize);
}
}
/** Marks RT_code and RT_data as normal memory.
* Used to avoid OSX marking RT_code regions as non-writable.
* Needed because some buggy UEFIs RT drivers uses static variables instead of
* runtime pool memory and then writing to such variables causes GPT in OSX.
*/
VOID
RemoveRTFlagMappings(
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize,
IN UINT32 DescriptorVersion,
IN EFI_MEMORY_DESCRIPTOR *MemoryMap
)
{
UINTN NumEntries;
UINTN Index;
EFI_MEMORY_DESCRIPTOR *Desc;
Desc = MemoryMap;
NumEntries = MemoryMapSize / DescriptorSize;
for (Index = 0; Index < NumEntries; Index++) {
// assign virtual addresses to all EFI_MEMORY_RUNTIME marked pages (including MMIO)
if ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) {
if (Desc->Type == EfiRuntimeServicesCode || Desc->Type == EfiRuntimeServicesData) {
DBG("RemoveRTFlagMappings: %lx (%x) -> %lx\n", Desc->VirtualStart, Desc->NumberOfPages, Desc->PhysicalStart);
Desc->Attribute = Desc->Attribute & (~EFI_MEMORY_RUNTIME);
Desc->Type = EfiConventionalMemory;
}
}
Desc = NEXT_MEMORY_DESCRIPTOR(Desc, DescriptorSize);
}
}
/** Fixes stuff when booting with relocation block. Called when boot.efi jumps to kernel. */
UINTN
FixBootingWithRelocBlock(UINTN bootArgs, BOOLEAN ModeX64)
{
VOID *pBootArgs = (VOID*)bootArgs;
BootArgs *BA;
UINTN MemoryMapSize;
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
BootArgsPrint(pBootArgs);
BA = GetBootArgs(pBootArgs);
MemoryMapSize = *BA->MemoryMapSize;
MemoryMap = (EFI_MEMORY_DESCRIPTOR*)(UINTN)(*BA->MemoryMap);
DescriptorSize = *BA->MemoryMapDescriptorSize;
DescriptorVersion = *BA->MemoryMapDescriptorVersion;
// make memmap smaller
DBGnvr("ShrinkMemMap: Size 0x%lx", MemoryMapSize);
ShrinkMemMap(&MemoryMapSize, MemoryMap, DescriptorSize, DescriptorVersion);
*BA->MemoryMapSize = (UINT32)MemoryMapSize;
DBGnvr(" -> 0x%lx\n", MemoryMapSize);
// fix runtime stuff
RuntimeServicesFix(BA);
// fix some values in dev tree
DevTreeFix(BA);
// fix boot args
DBGnvr("BootArgsFix ...\n");
BootArgsFix(BA, gRelocBase);
BootArgsPrint(pBootArgs);
bootArgs = bootArgs - gRelocBase;
// pBootArgs = (VOID*)bootArgs;
// set vars for copying kernel
// note: *BA->kaddr is fixed in BootArgsFix() and points to real kaddr
AsmKernelImageStartReloc = *BA->kaddr + (UINT32)gRelocBase;
AsmKernelImageStart = *BA->kaddr;
AsmKernelImageSize = *BA->ksize;
return bootArgs;
}
/** Fixes stuff when booting without relocation block. Called when boot.efi jumps to kernel. */
UINTN
FixBootingWithoutRelocBlock(UINTN bootArgs, BOOLEAN ModeX64)
{
VOID *pBootArgs = (VOID*)bootArgs;
BootArgs *BA;
/*
UINTN MemoryMapSize;
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
*/
DBG("FixBootingWithoutRelocBlock:\n");
BootArgsPrint(pBootArgs);
BA = GetBootArgs(pBootArgs);
/*
// Set boot args efi system table to our copied system table
DBG(" old BA->efiSystemTable = %x:\n", *BA->efiSystemTable);
*BA->efiSystemTable = (UINT32)gRelocatedSysTableRtArea;
DBG(" new BA->efiSystemTable = %x:\n", *BA->efiSystemTable);
// instead of looking into boot args, we'll use
// last taken memmap with MOGetMemoryMap().
// this makes this kind of booting not dependent on boot args format.
MemoryMapSize = gLastMemoryMapSize;
MemoryMap = gLastMemoryMap;
DescriptorSize = gLastDescriptorSize;
DescriptorVersion = gLastDescriptorVersion;
// boot.efi zeroed original RT areas, but we need to return them back
// to fix sleep on some UEFIs
ReturnPreviousRTAreasContent(MemoryMapSize, MemoryMap);
// we need to remove RT_code and RT_data flags since they causes GPF on some UEFIs.
// OSX maps RT_code as Read+Exec only while faulty frivers writes to their
// static vars which are in RT_code
RemoveRTFlagMappings(MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap);
*/
// Restore original kernel entry code
CopyMem((VOID *)(UINTN)AsmKernelEntry, (VOID *)gOrigKernelCode, gOrigKernelCodeSize);
// no need to copy anything here
AsmKernelImageStartReloc = 0x100000;
AsmKernelImageStart = 0x100000;
AsmKernelImageSize = 0;
return bootArgs;
}
/** Fixes stuff when waking from hibernate without relocation block. Called when boot.efi jumps to kernel. */
UINTN
FixHibernateWakeWithoutRelocBlock(UINTN imageHeaderPage, BOOLEAN ModeX64)
{
IOHibernateImageHeader *ImageHeader;
IOHibernateHandoff *Handoff;
ImageHeader = (IOHibernateImageHeader *)(UINTN)(imageHeaderPage << EFI_PAGE_SHIFT);
// Pass our relocated copy of system table
ImageHeader->systemTableOffset = (UINT32)(UINTN)(gRelocatedSysTableRtArea - ImageHeader->runtimePages);
// we need to remove memory map handoff. my system restarts if we leave it there
// if mem map handoff is not present, then kernel will not map those new rt pages
// and that is what we need on our faulty UEFIs.
// it's the equivalent to RemoveRTFlagMappings() in normal boot.
Handoff = (IOHibernateHandoff *)(UINTN)(ImageHeader->handoffPages << EFI_PAGE_SHIFT);
while (Handoff->type != kIOHibernateHandoffTypeEnd) {
if (Handoff->type == kIOHibernateHandoffTypeMemoryMap) {
Handoff->type = kIOHibernateHandoffType;
break;
}
Handoff = (IOHibernateHandoff *)(UINTN)((UINTN)Handoff + sizeof(Handoff) + Handoff->bytecount);
}
// boot.efi zeroed original RT areas, but we need to return them back
// to fix sleep on some UEFIs
//ReturnPreviousRTAreasContent(0, NULL);
// Restore original kernel entry code
CopyMem((VOID *)(UINTN)AsmKernelEntry, (VOID *)gOrigKernelCode, gOrigKernelCodeSize);
// no need to copy anything here
AsmKernelImageStartReloc = 0x100000;
AsmKernelImageStart = 0x100000;
AsmKernelImageSize = 0;
return imageHeaderPage;
}