diff --git a/Include/Library/OcAppleBootCompatLib.h b/Include/Library/OcAppleBootCompatLib.h new file mode 100644 index 00000000..80e3b4df --- /dev/null +++ b/Include/Library/OcAppleBootCompatLib.h @@ -0,0 +1,67 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef OC_APPLE_BOOT_COMPAT_LIB_H +#define OC_APPLE_BOOT_COMPAT_LIB_H + +/** + Apple Boot Compatibility layer configuration. +**/ +typedef struct OC_ABC_SETTINGS_ { + /// + /// Protect from boot.efi from defragmenting runtime memory and setup virtual memory + /// early mapping. This fixes NVRAM support on many firmwares. + /// + BOOLEAN SetupAppleMap; + /// + /// Provide custom Apple KASLR slide calculation for firmwares with polluted low memory ranges. + /// + BOOLEAN SetupAppleSlide; + /// + /// Discard UEFI memory map after waking from hibernation and preserve the original mapping. + /// + BOOLEAN DiscardAppleS4Map; + /// + /// Try to patch Apple bootloader to have KASLR enabled even in SafeMode. + /// + BOOLEAN EnableAppleSmSlide; + /// + /// Attempt to protect certain CSM memory regions from being used by the kernel . + /// On older firmwares this caused wake issues. + /// + BOOLEAN ProtectCsmRegion; + /// + /// Attempt to reduce memory map entries through grouping to fit into Apple kernel. + /// + BOOLEAN ShrinkMemoryMap; + /// + /// Ensure that ExitBootServices call succeeds even with outdated MemoryMap key. + /// + BOOLEAN ForceExitBootServices; +} OC_ABC_SETTINGS; + +/** + Initialize Apple Boot Compatibility layer. This layer is needed on partially + incompatible firmwares to prevent boot failure and UEFI services breakage. + + @param[in] Settings Compatibility layer configuration. + + @retval EFI_SUCCESS on success. +**/ +EFI_STATUS +OcAbcInitialize ( + IN OC_ABC_SETTINGS *Settings + ); + +#endif // OC_APPLE_BOOT_COMPAT_LIB_H diff --git a/Include/Library/OcBootManagementLib.h b/Include/Library/OcBootManagementLib.h index 5ebd3fd4..99715a81 100755 --- a/Include/Library/OcBootManagementLib.h +++ b/Include/Library/OcBootManagementLib.h @@ -469,6 +469,16 @@ OcActivateHibernateWake ( IN UINT32 HibernateMask ); +/** + Check if active hibernation is happening. + + @retval TRUE on waking from hibernation. +**/ +BOOLEAN +OcIsAppleHibernateWake ( + VOID + ); + /** Install missing boot policy, scan, and show simple boot menu. @@ -547,6 +557,24 @@ OcParseBootArgs ( IN VOID *BootArgs ); +/** + Check if boot argument is currently passed (via image options or NVRAM). + + @param[in] LoadImage UEFI loaded image protocol instance, optional. + @param[in] GetVariable Preferred UEFI NVRAM reader, optional. + @param[in] Argument Argument, e.g. -v, slide=, debug=, etc. + @param[in] ArgumentLength Argument length, e.g. L_STR_LEN ("-v"). + + @retval TRUE if argument is present. +**/ +BOOLEAN +OcCheckArgumentFromEnv ( + IN EFI_LOADED_IMAGE *LoadedImage OPTIONAL, + IN EFI_GET_VARIABLE GetVariable OPTIONAL, + IN CONST CHAR8 *Argument, + IN CONST UINTN ArgumentLength + ); + /** Get argument value from command line. diff --git a/Include/Library/OcDebugLogLib.h b/Include/Library/OcDebugLogLib.h index 0ea3aa3f..f4680393 100644 --- a/Include/Library/OcDebugLogLib.h +++ b/Include/Library/OcDebugLogLib.h @@ -26,6 +26,11 @@ **/ #define DEBUG_BULK_INFO (DEBUG_VERBOSE|DEBUG_INFO) +/** + This is a place print debug messages when they happen after ExitBootServices. +**/ +#define RUNTIME_DEBUG(x) do { } while (0) + /** Install or update the OcLog protocol with specified options. diff --git a/Include/Library/OcMiscLib.h b/Include/Library/OcMiscLib.h index 6c5eb317..6434dc06 100755 --- a/Include/Library/OcMiscLib.h +++ b/Include/Library/OcMiscLib.h @@ -98,4 +98,13 @@ ReleaseUsbOwnership ( VOID ); +/** + Perform cold reboot directly bypassing UEFI services. Does not return. + Supposed to work in any modern physical or virtual environment. +**/ +VOID +DirectRestCold ( + VOID + ); + #endif // OC_MISC_LIB_H diff --git a/Include/Protocol/OcAppleBootCompat.h b/Include/Protocol/OcAppleBootCompat.h new file mode 100644 index 00000000..1491161d --- /dev/null +++ b/Include/Protocol/OcAppleBootCompat.h @@ -0,0 +1,37 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef OC_APPLE_BOOT_COMPAT_PROTOCOL_H +#define OC_APPLE_BOOT_COMPAT_PROTOCOL_H + +#define OC_APPLE_BOOT_COMPAT_PROTOCOL_REVISION 0x010000 + +// +// OC_APPLE_BOOT_COMPAT_PROTOCOL_GUID +// C7CBA84E-CC77-461D-9E3C-6BE0CB79A7C1 +// +#define OC_APPLE_BOOT_COMPAT_PROTOCOL_GUID \ + { 0xC7CBA84E, 0xCC77, 0x461D, \ + { 0x9E, 0x3C, 0x6B, 0xE0, 0xCB, 0x79, 0xA7, 0xC1 } } + +// +// Includes a revision for debugging reasons +// +typedef struct { + UINTN Revision; +} OC_APPLE_BOOT_COMPAT_PROTOCOL; + +extern EFI_GUID gOcAppleBootCompatProtocolGuid; + +#endif // OC_APPLE_BOOT_COMPAT_PROTOCOL_H diff --git a/Library/OcAppleBootCompatLib/BootCompatInternal.h b/Library/OcAppleBootCompatLib/BootCompatInternal.h new file mode 100644 index 00000000..971c4c46 --- /dev/null +++ b/Library/OcAppleBootCompatLib/BootCompatInternal.h @@ -0,0 +1,335 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef BOOT_COMPAT_INTERNAL_H +#define BOOT_COMPAT_INTERNAL_H + +#include +#include +#include +#include +#include + +#ifdef MDE_CPU_X64 +#include "X64/ContextSwitch.h" +#else +#error "Unsupported architecture!" +#endif + +/** + Maximum number of supported runtime reloc protection areas. + Currently hardocded for simplicity. +**/ +#define RT_RELOC_PROTECT_MAX_NUM ((UINTN) 64) + +/** + Runtime descriptor number to virtualise. + Currently hardocded for simplicity. +**/ +#define RT_DESC_ENTRY_NUM ((UINTN) 64) + +/** + Base kernel address. +**/ +#define BASE_KERNEL_ADDR ((UINTN)0x100000) + +/** + Slide offset per slide entry +**/ +#define SLIDE_GRANULARITY ((UINTN)0x200000) + +/** + Total possible number of KASLR slide offsets. +**/ +#define TOTAL_SLIDE_NUM 256 + +/** + Preserved relocation entry. +**/ +typedef struct RT_RELOC_PROTECT_INFO_ { + /// + /// Physical address of descriptor start. + /// + EFI_PHYSICAL_ADDRESS PhysicalStart; + /// + /// Physical address of descriptor end. + /// + EFI_PHYSICAL_ADDRESS PhysicalEnd; + /// + /// Descriptor original memory type. + /// + EFI_MEMORY_TYPE Type; +} RT_RELOC_PROTECT_INFO; + +/** + Preserved relocation entry list. +**/ +typedef struct RT_RELOC_PROTECT_DATA_ { + /// + /// Number of currently used methods in the table. + /// + UINTN NumEntries; + /// + /// Reloc entries fitted. + /// + RT_RELOC_PROTECT_INFO RelocInfo[RT_RELOC_PROTECT_MAX_NUM]; +} RT_RELOC_PROTECT_DATA; + +/** + UEFI Boot & Runtime Services original pointers. +**/ +typedef struct UEFI_SERVICES_POINTERS_ { + /// + /// Original page allocator. We override it to obtain + /// the location macOS kernel and hibernation images. + /// + EFI_ALLOCATE_PAGES AllocatePages; + /// + /// Original memory map function. We override it to make + /// memory map shrinking and CSM region protection. + /// + EFI_GET_MEMORY_MAP GetMemoryMap; + /// + /// Original exit boot services function. We override it + /// to ensure we always succeed exiting boot services. + /// + EFI_EXIT_BOOT_SERVICES ExitBootServices; + /// + /// Image starting routine. We override to catch boot.efi + /// loading and enable the rest of functions. + /// + EFI_IMAGE_START StartImage; + /// + /// Original get variable function. We override it to alter + /// boot.efi boot arguments for custom KASLR slide. + /// + EFI_GET_VARIABLE GetVariable; + /// + /// Original virtual address mapping function. We override + /// it to perform runtime area protection to prevent boot.efi + /// defragmentation and setup virtual memory for firmwares + /// accessing it after exit boot services. + /// + EFI_SET_VIRTUAL_ADDRESS_MAP SetVirtualAddressMap; +} UEFI_SERVICES_POINTERS; + +/** + UEFI services override internal state. +**/ +typedef struct SERVICES_OVERRIDE_STATE_ { + /// + /// GetVariable arrival event. + /// + EFI_EVENT GetVariableEvent; + /// + /// Minimum address allocated by AlocatePages. + /// + EFI_PHYSICAL_ADDRESS MinAllocatedAddr; + /// + /// Maximum address allocated by AlocatePages. + /// + EFI_PHYSICAL_ADDRESS MaxAllocatedAddr; + /// + /// Apple hibernate image address allocated by AlocatePages. + /// + EFI_PHYSICAL_ADDRESS HibernateImageAddress; + /// + /// Last descriptor size obtained from GetMemoryMap. + /// + UINTN MemoryMapDescriptorSize; + /// + /// Amount of nested boot.efi detected. + /// + UINTN AppleBootNestedCount; + /// + /// TRUE if we are doing boot.efi hibernate wake. + /// + BOOLEAN AppleHibernateWake; + /// + /// TRUE if we are using custom KASLR slide (via boot arg). + /// + BOOLEAN AppleCustomSlide; +} SERVICES_OVERRIDE_STATE; + +/** + Apple kernel support internal state.. +**/ +typedef struct KERNEL_SUPPORT_STATE_ { + /// + /// Assembly support internal state. + /// + ASM_SUPPORT_STATE AsmState; + /// + /// Kernel jump trampoline. + /// + ASM_KERNEL_JUMP KernelJump; + /// + /// Original kernel memory. + /// + UINT8 KernelOrg[sizeof (ASM_KERNEL_JUMP)]; + /// + /// Custom kernel UEFI System Table. + /// + EFI_PHYSICAL_ADDRESS SysTableRtArea; + /// + /// Virtual memory mapper context. + /// + OC_VMEM_CONTEXT VmContext; + /// + /// Virtual memory map containing partial memory map with runtime areas only. + /// Actual number of entries may be less than RT_DESC_ENTRY_NUM due to DescriptorSize + /// being potentially bigger than sizeof (EFI_MEMORY_DESCRIPTOR). + /// + EFI_MEMORY_DESCRIPTOR VmMap[RT_DESC_ENTRY_NUM]; + /// + /// Virtual memory map size in bytes. + /// + UINTN VmMapSize; + /// + /// Virtual memory map descriptor size in bytes. + /// + UINTN VmMapDescSize; +} KERNEL_SUPPORT_STATE; + +/** + Apple Boot Compatibility context. +**/ +typedef struct BOOT_COMPAT_CONTEXT_ { + /// + /// Apple Coot Compatibility settings. + /// + OC_ABC_SETTINGS Settings; + /// + /// Runtime relocations. + /// + RT_RELOC_PROTECT_DATA RtReloc; + /// + /// UEFI Boot & Runtime Services original pointers. + /// + UEFI_SERVICES_POINTERS ServicePtrs; + /// + /// UEFI services override internal state. + /// + SERVICES_OVERRIDE_STATE ServiceState; + /// + /// Apple kernel support internal state. + /// + KERNEL_SUPPORT_STATE KernelState; +} BOOT_COMPAT_CONTEXT; + +/** + Obtain Apple Boot Compatibility context. This function must only + be called from wrapped services, where passing context arguments + is not possible. + + @retval Apple Boot Compatibility context (not null). +**/ +BOOT_COMPAT_CONTEXT * +GetBootCompatContext ( + VOID + ); + +/** + Install UEFI services overrides as necessary. + + @param[in,out] BootCompat Boot compatibility context. +**/ +VOID +InstallServiceOverrides ( + IN OUT BOOT_COMPAT_CONTEXT *ServicePtrs + ); + +/** + Prepare virtual memory management environment for later usage. + + @param[in,out] BootCompat Boot compatibility context. +**/ +VOID +AppleMapPrepareMemoryPool ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat + ); + +/** + Prepare environment for Apple UEFI bootloader. See more details inside. + + @param[in,out] BootCompat Boot compatibility context. + @param[in,out] LoadedImage UEFI loaded image protocol instance. + @param[in] GetMemoryMap Unmodified GetMemoryMap pointer, optional. +**/ +VOID +AppleMapPrepareBooterState ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN OUT EFI_LOADED_IMAGE *LoadedImage, + IN EFI_GET_MEMORY_MAP GetMemoryMap OPTIONAL + ); + +/** + Save UEFI environment state in implementation specific way. + + @param[in,out] AsmState Assembly state to update, can be preserved. + @param[out] KernelJump Kernel jump trampoline to fill. +**/ +VOID +AppleMapPlatformSaveState ( + IN OUT ASM_SUPPORT_STATE *AsmState, + OUT ASM_KERNEL_JUMP *KernelJump + ); + +/** + Patch kernel entry point with KernelJump to later land in AppleMapPrepareKernelState. + + @param[in,out] BootCompat Boot compatibility context. + @param[in] ImageAddress Kernel or hibernation image address. + @param[in] AppleHibernateWake TRUE when ImageAddress points to hibernation image. +**/ +VOID +AppleMapPrepareKernelJump ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN UINTN ImageAddress, + IN BOOLEAN AppleHibernateWake + ); + +/** + Patch kernel entry point with KernelJump to later land in AppleMapPrepareKernelState. + + @param[in,out] BootCompat Boot compatibility context. + @param[in] ImageAddress Kernel or hibernation image address. + @param[in] AppleHibernateWake TRUE when ImageAddress points to hibernation image. +**/ +EFI_STATUS +AppleMapPrepareVmState ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN UINT32 DescriptorVersion, + IN EFI_MEMORY_DESCRIPTOR *MemoryMap + ); + +/** + Prepare environment for Apple kernel bootloader in boot or wake cases. + This callback arrives when boot.efi jumps to kernel. + + @param[in] Args Case-specific kernel argument handle. + @param[in] ModeX64 Debug flag about kernel context type, TRUE when X64. + + @retval Args must be returned with the necessary modifications if any. +**/ +UINTN +EFIAPI +AppleMapPrepareKernelState ( + IN UINTN Args, + IN BOOLEAN ModeX64 + ); + +#endif // BOOT_COMPAT_INTERNAL_H diff --git a/Library/OcAppleBootCompatLib/KernelSupport.c b/Library/OcAppleBootCompatLib/KernelSupport.c new file mode 100644 index 00000000..87b92782 --- /dev/null +++ b/Library/OcAppleBootCompatLib/KernelSupport.c @@ -0,0 +1,655 @@ +/** @file + Copyright (C) 2013, dmazar. All rights reserved. + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#include "BootCompatInternal.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + Protect RT data from boot.efi relocation by marking them MemMapIO. + See more details in the function definition. + + @param[in,out] RtReloc Relocation entry list to store entry types. + @param[in] MemoryMapSize Memory map size. + @param[in] DescriptorSize Memory map descriptor size. + @param[in,out] MemoryMap MemoryMap to protect entries in. + @param[in] SysTableArea Special address that should not be protected. +**/ +STATIC +VOID +ProtectRtMemoryFromRelocation ( + IN OUT RT_RELOC_PROTECT_DATA *RtReloc, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN EFI_PHYSICAL_ADDRESS SysTableArea + ) +{ + // + // We protect RT data & code from relocation by marking them MemMapIO except EFI_SYSTEM_TABLE area. + // + // 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 SMM handler through previously negotiated buffer + // and this buffer must not be relocated. + // Explained and examined in detail by CodeRush and night199uk: + // https://web.archive.org/web/20141025080709/http://www.projectosx.com/forum/lofiversion/index.php/t3298.html + // + // Starting with APTIO V for NVRAM to work not only RT data but RT code too can no longer be moved + // due to the use of commbuffers. This, however, creates a memory protection issue, because + // XNU maps RT data as RW and code as RX, and AMI appears use global variables in some RT drivers. + // For this reason we shim (most?) affected RT services via wrapers that unset the WP bit during + // the UEFI call and set it back on return in a separate driver. + // Explained in detail by Download-Fritz and vit9696: + // http://www.insanelymac.com/forum/topic/331381-aptiomemoryfix (first 2 links in particular). + // + // EFI_SYSTEM_TABLE is passed directly through kernel boot arguments, and thus goes through static + // mapping (ml_static_ptovirt) in efi_set_tables_64 call. This mapping works as PHYS | CONST = VIRT. + // To avoid kernel accessing unmapped virtual address we let boot.efi relocate the page with + // EFI_SYSTEM_TABLE area. While technically it is possible to let the original page to be relocated, + // we pick a safer root by using a private copy. + // + // The primary downside of this approach is that boot.efi will still reserve the contiguous memory + // for runtime services after the kernel: efiRuntimeServicesPageCount pages starting from + // efiRuntimeServicesPageStart within kaddr ~ ksize range. However, unlike Macs, which have reserved + // gaps only for ACPI NVS, MemMapIO and similar regions, with this approach almost no physical memory + // in efiRuntimeServicesPageStart area is used at all. This memory is never reclaimed by XNU, which + // marks it as allocated in i386_vm_init. Expirements show that at least 85 MBs (Z170) are used for + // this process. On server systems the issue is much worse due to many devices in place. + // Ideally boot.efi should only count RT code and RT data pages, but it is not easy to change. + // + + UINTN NumEntries; + UINTN Index; + EFI_MEMORY_DESCRIPTOR *Desc; + RT_RELOC_PROTECT_INFO *RelocInfo; + + RtReloc->NumEntries = 0; + RelocInfo = &RtReloc->RelocInfo[0]; + NumEntries = MemoryMapSize / DescriptorSize; + + for (Index = 0; Index < NumEntries; ++Index) { + if ((Desc->Attribute & EFI_MEMORY_RUNTIME) == 0 + || Desc->NumberOfPages == 0) { + continue; + } + + if (Desc->Type == EfiRuntimeServicesCode + || (Desc->Type == EfiRuntimeServicesData + && Desc->PhysicalStart != SysTableArea)) { + + if (RtReloc->NumEntries == ARRAY_SIZE (RtReloc->RelocInfo)) { + RUNTIME_DEBUG (( + DEBUG_ERROR, + "OCABC: Cannot save mem type for entry: %Lx (type 0x%x)\n", + (UINT64) Desc->PhysicalStart, + (UINT32) Desc->Type + )); + return; + } + + RelocInfo->PhysicalStart = Desc->PhysicalStart; + RelocInfo->PhysicalEnd = Desc->PhysicalStart + (EFI_PAGES_TO_SIZE (Desc->NumberOfPages) - 1); + RelocInfo->Type = Desc->Type; + Desc->Type = EfiMemoryMappedIO; + ++RelocInfo; + ++RtReloc->NumEntries; + } + + Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); + } +} + +/** + Copy RT flagged areas to separate memmap, define virtual to physical address mapping, + and call SetVirtualAddressMap() only with that partial memmap. + + @param[in,out] KernelState Kernel support state. + @param[in] MemoryMapSize Memory map size. + @param[in] DescriptorSize Memory map descriptor size. + @param[in] DescriptorVersion Memor map descriptor version. + @param[in,out] MemoryMap Complete memory map with all entries. + + @retval EFI_SUCCESS on success. +**/ +STATIC +EFI_STATUS +PerformRtMemoryVirtualMapping ( + IN OUT KERNEL_SUPPORT_STATE *KernelState, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN UINT32 DescriptorVersion, + IN EFI_MEMORY_DESCRIPTOR *MemoryMap + ) +{ + // + // 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 physical mappings: + // Also adds virtual to physical 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. + // + + UINTN NumEntries; + UINTN Index; + EFI_MEMORY_DESCRIPTOR *Desc; + EFI_MEMORY_DESCRIPTOR *VirtualDesc; + EFI_STATUS Status; + PAGE_MAP_AND_DIRECTORY_POINTER *PageTable; + UINTN Flags; + + Desc = MemoryMap; + NumEntries = MemoryMapSize / DescriptorSize; + VirtualDesc = KernelState->VmMap; + KernelState->VmMapSize = 0; + KernelState->VmMapDescSize = DescriptorSize; + + // + // Get current VM page table + // + GetCurrentPageTable (&PageTable, &Flags); + + for (Index = 0; Index < NumEntries; ++Index) { + // + // Some UEFIs end up with "reserved" area with EFI_MEMORY_RUNTIME flag set when Intel HD3000 or HD4000 is used. + // For example, on GA-H81N-D2H there is a single 1 GB descriptor: + // 000000009F800000-00000000DF9FFFFF 0000000000040200 8000000000000000 + // + // All known boot.efi starting from at least 10.5.8 properly handle this flag and do not assign virtual addresses + // to reserved descriptors. + // However, the issue was with AptioFix itself, which did not check for EfiReservedMemoryType and replaced + // it by EfiMemoryMappedIO to prevent boot.efi relocations. + // + // The relevant discussion and the original fix can be found here: + // http://web.archive.org/web/20141111124211/http://www.projectosx.com:80/forum/lofiversion/index.php/t2428-450.html + // https://sourceforge.net/p/cloverefiboot/code/605/ + // + // Since it is not the bug in boot.efi, AptioMemoryFix only needs to properly handle EfiReservedMemoryType with + // EFI_MEMORY_RUNTIME attribute set, and there is no reason to mess with the memory map passed to boot.efi. + // + if (Desc->Type != EfiReservedMemoryType && (Desc->Attribute & EFI_MEMORY_RUNTIME) != 0) { + // + // Check if there is enough space in virtual map. + // + if (KernelState->VmMapSize + DescriptorSize > sizeof (KernelState->VmMap)) { + RUNTIME_DEBUG ((DEBUG_ERROR, "OCABC: Too many RT entries to memory map\n")); + return EFI_OUT_OF_RESOURCES; + } + + // + // Copy region with EFI_MEMORY_RUNTIME flag to virtual map. + // + CopyMem (VirtualDesc, Desc, DescriptorSize); + + // + // Define virtual to physical mapping. + // + Status = VmMapVirtualPages ( + &KernelState->VmContext, + PageTable, + Desc->VirtualStart, + Desc->NumberOfPages, + Desc->PhysicalStart + ); + if (EFI_ERROR (Status)) { + RUNTIME_DEBUG ((DEBUG_ERROR, "OCABC: RT mapping failure - %r\n", Status)); + return EFI_OUT_OF_RESOURCES; + } + + // + // Proceed to next virtual map slot. + // + VirtualDesc = NEXT_MEMORY_DESCRIPTOR (VirtualDesc, DescriptorSize); + KernelState->VmMapSize += DescriptorSize; + } + + // + // Proceed to next original map slot. + // + Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); + } + + VmFlushCaches (); + + Status = gRT->SetVirtualAddressMap ( + KernelState->VmMapSize, + DescriptorSize, + DescriptorVersion, + KernelState->VmMap + ); + + return Status; +} + +/** + Revert RT data protected types to let XNU kernel kernel properly map data. + + @param[in] RtReloc Relocated entry list with entry types. + @param[in] MemoryMapSize Memory map size. + @param[in] DescriptorSize Memory map descriptor size. + @param[in,out] MemoryMap MemoryMap to restore protected entries in. +**/ +STATIC +VOID +RestoreProtectedRtMemoryTypes ( + IN RT_RELOC_PROTECT_DATA *RtReloc, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap + ) +{ + UINTN Index; + UINTN Index2; + UINTN NumEntriesLeft; + UINTN NumEntries; + EFI_PHYSICAL_ADDRESS PhysicalStart; + EFI_PHYSICAL_ADDRESS PhysicalEnd; + EFI_MEMORY_DESCRIPTOR *Desc; + BOOLEAN Found; + + NumEntriesLeft = RtReloc->NumEntries; + NumEntries = MemoryMapSize / DescriptorSize; + Desc = MemoryMap; + + for (Index = 0; Index < NumEntries && NumEntriesLeft > 0; ++Index) { + Found = FALSE; + PhysicalStart = Desc->PhysicalStart; + PhysicalEnd = PhysicalStart + (EFI_PAGES_TO_SIZE (Desc->NumberOfPages) - 1); + + for (Index2 = 0; Index2 < RtReloc->NumEntries; ++Index2) { + // + // I would expect PhysicalStart and PhysicalEnd to always match here, but + // the region can be merged with a nearby one, thus we check for overlap + // rather than full match. "Desc contains RtReloc" should also do. + // + if (PhysicalStart <= RtReloc->RelocInfo[Index2].PhysicalEnd + && RtReloc->RelocInfo[Index2].PhysicalStart <= PhysicalEnd) { + Desc->Type = RtReloc->RelocInfo[Index2].Type; + Found = TRUE; + } + } + + if (Found) { + --NumEntriesLeft; + } + + Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); + } + + if (NumEntriesLeft > 0) { + RUNTIME_DEBUG (( + DEBUG_ERROR, + "OCABC: Failed to restore %u entries out of %u\n", + (UINT32) NumEntriesLeft, + (UINT32) RtReloc->NumEntries + )); + } +} + +/** + Prepare environment for normal booting. Called when boot.efi jumps to kernel. + + @param[in,out] BootCompat Boot compatibility context. + @param[in,out] BootArgs Apple kernel boot arguments. +**/ +STATIC +VOID +AppleMapPrepareForBooting ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN OUT VOID *BootArgs + ) +{ + OC_BOOT_ARGUMENTS BA; + UINTN MemoryMapSize; + EFI_MEMORY_DESCRIPTOR *MemoryMap; + UINTN DescriptorSize; + + OcParseBootArgs (&BA, BootArgs); + + // + // FIXME: Restore the variables we tempered with to support custom slides. + // + // RestoreCustomSlideOverrides (&BA); + + MemoryMapSize = *BA.MemoryMapSize; + MemoryMap = (EFI_MEMORY_DESCRIPTOR *)(UINTN) (*BA.MemoryMap); + DescriptorSize = *BA.MemoryMapDescriptorSize; + + // + // We must restore EfiRuntimeServicesCode memory area types, because otherwise + // RuntimeServices won't be mapped. + // + RestoreProtectedRtMemoryTypes ( + &BootCompat->RtReloc, + MemoryMapSize, + DescriptorSize, + MemoryMap + ); +} + +/** + Prepare environment for hibernate wake. Called when boot.efi jumps to kernel. + + @param[in,out] BootCompat Boot compatibility context. + @param[in,out] ImageHeaderPage Apple hibernate image page number. +**/ +STATIC +VOID +AppleMapPrepareForHibernateWake ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN UINTN ImageHeaderPage + ) +{ + IOHibernateImageHeader *ImageHeader; + IOHibernateHandoff *Handoff; + + ImageHeader = (IOHibernateImageHeader *) EFI_PAGES_TO_SIZE (ImageHeaderPage); + + // + // Legacy note. In legacy implementations systemTableOffset was unconditionally overwritten + // with a wrong address due to ImageHeader->runtimePages not being converted from pages to bytes. + // Fortunately systemTableOffset was unused when kIOHibernateHandoffTypeMemoryMap is unspecified. + // systemTableOffset is calculated properly by boot.efi itself starting from 10.6.8 at least, + // and thus this assignment was useless in the first place. + // + + // + // At this step we have two routes. + // + // 1. Remove newly generated memory map from hibernate image to let XNU use the original mapping. + // This is known to work well on most systems primarily because Windows requires UEFI firmwares + // to preserve physical memory consistency at S4 wake. "On a UEFI platform, firmware runtime memory + // must be consistent across S4 sleep state transitions, in both size and location.", see: + // https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-uefi#hibernation-state-s4-transition-requirements + // 2. Recover memory map just as we do for normal booting. This was causing issues on some firmwares, + // which provided very strange memory maps after S4 wake. In other cases this should not immediately + // break things. XNU will entirely remove efiRuntimeServicesPageStart/efiRuntimeServicesPageSize + // mapping, and our new memory map entries will unconditionally overwrite previous ones. In case + // no physical memory changes happened this should work fine. + // + Handoff = (IOHibernateHandoff *) EFI_PAGES_TO_SIZE ((UINTN) ImageHeader->handoffPages); + while (Handoff->type != kIOHibernateHandoffTypeEnd) { + if (Handoff->type == kIOHibernateHandoffTypeMemoryMap) { + if (BootCompat->Settings.DiscardAppleS4Map) { + // + // Route 1. Discard the new memory map here, and let XNU use what it had. + // + Handoff->type = kIOHibernateHandoffType; + } else { + // + // Route 2. Recovery memory protection types just as normal boot. + // + + if (BootCompat->KernelState.VmMapDescSize == 0) { + RUNTIME_DEBUG (("OCABC: Saved descriptor size cannot be 0\n")); + BootCompat->KernelState.VmMapDescSize = sizeof (EFI_MEMORY_DESCRIPTOR); + } + + RestoreProtectedRtMemoryTypes ( + &BootCompat->RtReloc, + Handoff->bytecount, + MIN (BootCompat->KernelState.VmMapDescSize, sizeof (EFI_MEMORY_DESCRIPTOR)), + (EFI_MEMORY_DESCRIPTOR *)(UINTN) Handoff->data + ); + } + + break; + } + + Handoff = (IOHibernateHandoff *) ((UINTN) Handoff + sizeof(Handoff) + Handoff->bytecount); + } +} + +VOID +AppleMapPrepareMemoryPool ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat + ) +{ + EFI_STATUS Status; + + Status = VmAllocateMemoryPool ( + &BootCompat->KernelState.VmContext, + OC_DEFAULT_VMEM_PAGE_COUNT + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "OCABC: Memory pool allocation failure - %r\n", Status)); + } +} + +VOID +AppleMapPrepareBooterState ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN OUT EFI_LOADED_IMAGE *LoadedImage, + IN EFI_GET_MEMORY_MAP GetMemoryMap OPTIONAL + ) +{ + EFI_STATUS Status; + + // + // This function may be called twice, do not redo in this case. + // + if (BootCompat->KernelState.SysTableRtArea == 0) { + AppleMapPlatformSaveState ( + &BootCompat->KernelState.AsmState, + &BootCompat->KernelState.KernelJump + ); + + // + // Allocate 1 RT data page for copy of UEFI system table for kernel. + // This one also has to be 32-bit due to XNU BootArgs structure. + // The reason for this allocation to be required is because XNU uses static + // mapping for directly passed pointers (see ProtectRtMemoryFromRelocation). + // + BootCompat->KernelState.SysTableRtArea = BASE_4GB; + Status = AllocatePagesFromTop ( + EfiRuntimeServicesData, + 1, + &BootCompat->KernelState.SysTableRtArea, + GetMemoryMap, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "OCABC: Failed to allocate system table memory - %r\n", + Status + )); + BootCompat->KernelState.SysTableRtArea = 0; + return; + } + + // + // Copy UEFI system table to the new location. + // + if (gST->Hdr.HeaderSize > EFI_PAGE_SIZE) { + DEBUG (( + DEBUG_ERROR, + "OCABC: EFI_SYSTEM_TABLE is too large - %u bytes\n", + (UINT32) gST->Hdr.HeaderSize + )); + } + CopyMem ( + (VOID *)(UINTN) BootCompat->KernelState.SysTableRtArea, + gST, + MIN (gST->Hdr.HeaderSize, EFI_PAGE_SIZE) + ); + } + + // + // Assign loaded image with custom system table. + // + LoadedImage->SystemTable = + (EFI_SYSTEM_TABLE *)(UINTN) BootCompat->KernelState.SysTableRtArea; +} + +VOID +AppleMapPrepareKernelJump ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN UINTN ImageAddress, + IN BOOLEAN AppleHibernateWake + ) +{ + UINT8 *KernelEntry; + UINTN SlideAddr; + VOID *MachOImage; + IOHibernateImageHeader *ImageHeader; + + if (!AppleHibernateWake) { + // + // Read kernel entry from Mach-O load command and patch it with jump. + // + SlideAddr = ImageAddress - BASE_KERNEL_ADDR; + MachOImage = (VOID*) (SlideAddr + SLIDE_GRANULARITY); + KernelEntry = (UINT8*) MachoRuntimeGetEntryAddress (MachOImage); + if (KernelEntry != 0) { + KernelEntry += SlideAddr; + } + } else { + // + // Read kernel entry from hibernation image and patch it with jump. + // At this stage HIB section is not yet copied from sleep image to it's + // proper memory destination. so we'll patch entry point in sleep image. + // + ImageHeader = (IOHibernateImageHeader *) ImageAddress; + KernelEntry = (UINT8 *) &ImageHeader->fileExtentMap[0] + + ImageHeader->fileExtentMapSize + ImageHeader->restore1CodeOffset; + } + + if (KernelEntry == 0) { + RUNTIME_DEBUG ((DEBUG_ERROR, "KernelEntry must be found!")); + return; + } + + // + // Save original kernel entry code. + // + CopyMem ( + &BootCompat->KernelState.KernelOrg[0], + KernelEntry, + sizeof (BootCompat->KernelState.KernelOrg) + ); + + // + // Copy kernel jump code to kernel entry address. + // + CopyMem ( + KernelEntry, + &BootCompat->KernelState.KernelJump, + sizeof (BootCompat->KernelState.KernelJump) + ); +} + +EFI_STATUS +AppleMapPrepareVmState ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN UINT32 DescriptorVersion, + IN EFI_MEMORY_DESCRIPTOR *MemoryMap + ) +{ + EFI_STATUS Status; + + // + // Protect RT areas from relocation by marking then MemMapIO. + // + ProtectRtMemoryFromRelocation ( + &BootCompat->RtReloc, + MemoryMapSize, + DescriptorSize, + MemoryMap, + BootCompat->KernelState.SysTableRtArea + ); + + // + // Virtualize RT services with all needed fixes. + // + Status = PerformRtMemoryVirtualMapping ( + &BootCompat->KernelState, + MemoryMapSize, + DescriptorSize, + DescriptorVersion, + MemoryMap + ); + + // + // Copy now virtualized UEFI system table for boot.efi to hand it to the kernel. + // + CopyMem ( + (VOID *)(UINTN) BootCompat->KernelState.SysTableRtArea, + gST, + gST->Hdr.HeaderSize + ); + + return Status; +} + +UINTN +EFIAPI +AppleMapPrepareKernelState ( + IN UINTN Args, + IN BOOLEAN ModeX64 + ) +{ + BOOT_COMPAT_CONTEXT *BootCompatContext; + + BootCompatContext = GetBootCompatContext (); + + if (BootCompatContext->ServiceState.AppleHibernateWake) { + AppleMapPrepareForHibernateWake ( + BootCompatContext, + Args + ); + } else { + AppleMapPrepareForBooting ( + BootCompatContext, + (VOID *) Args + ); + } + + // + // Restore original kernel entry code. + // + CopyMem ( + BootCompatContext->KernelState.AsmState.KernelEntry, + &BootCompatContext->KernelState.KernelOrg[0], + sizeof (BootCompatContext->KernelState.KernelOrg) + ); + + return Args; +} diff --git a/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.c b/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.c new file mode 100644 index 00000000..697e1565 --- /dev/null +++ b/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.c @@ -0,0 +1,114 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "BootCompatInternal.h" + +/** + Apple Boot Compatibility protocol instance. Its GUID matches + with legacy AptioMemoryFix protocol, allowing us to avoid + conflicts between the two. +**/ +STATIC OC_APPLE_BOOT_COMPAT_PROTOCOL mOcAppleBootCompatProtocol = { + OC_APPLE_BOOT_COMPAT_PROTOCOL_REVISION +}; + +/** + Apple Boot Compatibility context. This context is used throughout + the library. Must be accessed through GetBootCompatContext (). +**/ +STATIC BOOT_COMPAT_CONTEXT mOcAppleBootCompatContext; + +STATIC +EFI_STATUS +InstallAbcProtocol ( + VOID + ) +{ + EFI_STATUS Status; + VOID *Interface; + EFI_HANDLE Handle; + + Status = gBS->LocateProtocol ( + &gOcAppleBootCompatProtocolGuid, + NULL, + &Interface + ); + + if (!EFI_ERROR (Status)) { + // + // Ensure we do not run with AptioMemoryFix. + // It also checks for attempts to install this protocol twice. + // + DEBUG ((DEBUG_WARN, "OCABC: Found legacy AptioMemoryFix driver!\n")); + return EFI_ALREADY_STARTED; + } + + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gOcAppleBootCompatProtocolGuid, + &mOcAppleBootCompatProtocol, + NULL + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "OCABC: protocol install failure - %r\n", Status)); + return Status; + } + + return EFI_SUCCESS; +} + +BOOT_COMPAT_CONTEXT * +GetBootCompatContext ( + VOID + ) +{ + return &mOcAppleBootCompatContext; +} + +EFI_STATUS +OcAbcInitialize ( + IN OC_ABC_SETTINGS *Settings + ) +{ + EFI_STATUS Status; + + Status = InstallAbcProtocol (); + if (EFI_ERROR (Status)) { + return Status; + } + + InstallServiceOverrides ( + GetBootCompatContext () + ); + + return EFI_SUCCESS; +} diff --git a/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.inf b/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.inf new file mode 100644 index 00000000..2a96218b --- /dev/null +++ b/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.inf @@ -0,0 +1,64 @@ +## @file +# +# Component description file for the library producing the Apple Device property protocol. +# +# Copyright (C) 2019, vit9696. All rights reserved.
+# +# All rights reserved. +# +# This program and the accompanying materials +# are licensed and made available under the terms and conditions of the BSD License +# which accompanies this distribution. The full text of the license may be found at +# http://opensource.org/licenses/bsd-license.php +# +# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = OcAppleBootCompatLib + FILE_GUID = A393F7CF-3966-4C7E-8763-3DD991681C9B + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = OcAppleBootCompatLib|PEIM DXE_DRIVER DXE_RUNTIME_DRIVER UEFI_DRIVER UEFI_APPLICATION DXE_SMM_DRIVER + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 IPF EBC +# + +[Sources] + BootCompatInternal.h + KernelSupport.c + OcAppleBootCompatLib.c + ServiceOverrides.c + +[Sources.X64] + X64/ContextSwitch.nasm + X64/ContextSwitchSupport.c + +[Packages] + OcSupportPkg/OcSupportPkg.dec + MdePkg/MdePkg.dec + EfiPkg/EfiPkg.dec + +[Guids] + gAppleBootVariableGuid ## SOMETIMES_CONSUMES + +[Protocols] + gOcAppleBootCompatProtocolGuid ## PRODUCES + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + DevicePathLib + MemoryAllocationLib + PrintLib + UefiBootServicesTableLib + UefiRuntimeServicesTableLib + OcGuardLib + OcMemoryLib diff --git a/Library/OcAppleBootCompatLib/ServiceOverrides.c b/Library/OcAppleBootCompatLib/ServiceOverrides.c new file mode 100644 index 00000000..117d2d27 --- /dev/null +++ b/Library/OcAppleBootCompatLib/ServiceOverrides.c @@ -0,0 +1,565 @@ +/** @file + Copyright (C) 2013, dmazar. All rights reserved. + Copyright (C) 2017, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#include "BootCompatInternal.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + Helper function to call ExitBootServices that can handle outdated MapKey issues. + + @param[in] ExitBootServices ExitBootServices function pointer, optional. + @param[in] GetMemoryMap GetMemoryMap function pointer, optional. + @param[in] ImageHandle Image handle to call ExitBootServices on. + @param[in] MapKey MapKey to call ExitBootServices on. + + @retval EFI_SUCCESS on success. +**/ +STATIC +EFI_STATUS +ForceExitBootServices ( + IN EFI_HANDLE ImageHandle, + IN UINTN MapKey, + IN EFI_EXIT_BOOT_SERVICES ExitBootServices OPTIONAL, + IN EFI_GET_MEMORY_MAP GetMemoryMap OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_MEMORY_DESCRIPTOR *MemoryMap; + UINTN MemoryMapSize; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + + if (ExitBootServices == NULL) { + ExitBootServices = gBS->ExitBootServices; + } + + if (GetMemoryMap == NULL) { + GetMemoryMap = gBS->GetMemoryMap; + } + + // + // Firstly try the easy way. + // + Status = ExitBootServices (ImageHandle, MapKey); + + if (EFI_ERROR (Status)) { + // + // It is too late to free memory map here, but it does not matter, because boot.efi has an old one + // and will freely use the memory. + // It is technically forbidden to allocate pool memory here, but we should not hit this code + // in the first place, and for older firmwares, where it was necessary (?), it worked just fine. + // + Status = GetCurrentMemoryMapAlloc ( + &MemoryMapSize, + &MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion, + GetMemoryMap, + NULL + ); + if (Status == EFI_SUCCESS) { + // + // We have the latest memory map and its key, try again! + // + Status = ExitBootServices (ImageHandle, MapKey); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "OCABC: ExitBootServices failed twice - %r\n", Status)); + } + } else { + DEBUG ((DEBUG_WARN, "OCABC: Failed to get MapKey for ExitBootServices - %r\n", Status)); + Status = EFI_INVALID_PARAMETER; + } + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "OCABC: Waiting 10 secs...\n")); + gBS->Stall (SECONDS_TO_MICROSECONDS (10)); + } + } + + return Status; +} + +/** + UEFI Boot Services StartImage override. Called to start an efi image. + If this is boot.efi, then our overrides are enabled. +**/ +STATIC +EFI_STATUS +EFIAPI +OcStartImage ( + IN EFI_HANDLE ImageHandle, + OUT UINTN *ExitDataSize, + OUT CHAR16 **ExitData OPTIONAL + ) +{ + EFI_STATUS Status; + EFI_LOADED_IMAGE_PROTOCOL *AppleLoadedImage; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + AppleLoadedImage = OcGetAppleBootLoadedImage (ImageHandle); + + // + // Clear monitoring vars + // + BootCompat->ServiceState.MinAllocatedAddr = 0; + BootCompat->ServiceState.MaxAllocatedAddr = 0; + + if (AppleLoadedImage != NULL) { + // + // Report about macOS being loaded. + // + ++BootCompat->ServiceState.AppleBootNestedCount; + BootCompat->ServiceState.AppleHibernateWake = OcIsAppleHibernateWake (); + BootCompat->ServiceState.AppleCustomSlide = OcCheckArgumentFromEnv ( + AppleLoadedImage, + BootCompat->ServicePtrs.GetVariable, + "slide=", + L_STR_LEN ("slide=") + ); + + if (BootCompat->Settings.EnableAppleSmSlide) { + // FIXME: Implement. + //UnlockSlideSupportForSafeMode ( + // (UINT8 *) AppleLoadedImage->ImageBase, + // AppleLoadedImage->ImageSize + // ); + } + + if (BootCompat->Settings.SetupAppleMap) { + AppleMapPrepareBooterState ( + BootCompat, + AppleLoadedImage, + BootCompat->ServicePtrs.GetMemoryMap + ); + } + } + + Status = BootCompat->ServicePtrs.StartImage ( + ImageHandle, + ExitDataSize, + ExitData + ); + + if (AppleLoadedImage != NULL) { + // + // We failed but other operating systems should be loadable. + // + --BootCompat->ServiceState.AppleBootNestedCount; + } + + return Status; +} + +/** + UEFI Boot Services AllocatePages override. + Returns pages from free memory block to boot.efi for kernel boot image. +**/ +STATIC +EFI_STATUS +EFIAPI +OcAllocatePages ( + IN EFI_ALLOCATE_TYPE Type, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN NumberOfPages, + IN OUT EFI_PHYSICAL_ADDRESS *Memory + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS UpperAddr; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + + Status = BootCompat->ServicePtrs.AllocatePages ( + Type, + MemoryType, + NumberOfPages, + Memory + ); + + if (!EFI_ERROR (Status) && BootCompat->ServiceState.AppleBootNestedCount > 0) { + if (Type == AllocateAddress && MemoryType == EfiLoaderData) { + // + // Called from boot.efi + // + UpperAddr = *Memory + EFI_PAGES_TO_SIZE (NumberOfPages); + + // + // Store min and max mem: they can be used later to determine + // start and end of kernel boot or hibernation images. + // + if (BootCompat->ServiceState.MinAllocatedAddr == 0 + || *Memory < BootCompat->ServiceState.MinAllocatedAddr) { + BootCompat->ServiceState.MinAllocatedAddr = *Memory; + } + + if (UpperAddr > BootCompat->ServiceState.MaxAllocatedAddr) { + BootCompat->ServiceState.MaxAllocatedAddr = UpperAddr; + } + } else if (BootCompat->ServiceState.AppleHibernateWake + && Type == AllocateAnyPages && MemoryType == EfiLoaderData + && BootCompat->ServiceState.HibernateImageAddress == 0) { + // + // Called from boot.efi during hibernate wake, + // first such allocation is for hibernate image + // + BootCompat->ServiceState.HibernateImageAddress = *Memory; + } + } + + return Status; +} + +/** + UEFI Boot Services GetMemoryMap override. + Returns shrinked memory map as XNU can handle up to PMAP_MEMORY_REGIONS_SIZE (128) entries. + Also applies any further memory map alterations as necessary. +**/ +STATIC +EFI_STATUS +EFIAPI +OcGetMemoryMap ( + IN OUT UINTN *MemoryMapSize, + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, + OUT UINTN *MapKey, + OUT UINTN *DescriptorSize, + OUT UINT32 *DescriptorVersion + ) +{ + EFI_STATUS Status; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + + Status = BootCompat->ServicePtrs.GetMemoryMap ( + MemoryMapSize, + MemoryMap, + MapKey, + DescriptorSize, + DescriptorVersion + ); + + if (EFI_ERROR (Status)) { + return Status; + } + + if (BootCompat->ServiceState.AppleBootNestedCount > 0) { + if (BootCompat->Settings.ProtectCsmRegion) { + // FIXME: Implement. + //ProtectCsmRegion ( + // *MemoryMapSize, + // MemoryMap, + // *DescriptorSize + // ); + } + + if (BootCompat->Settings.ShrinkMemoryMap) { + ShrinkMemoryMap ( + MemoryMapSize, + MemoryMap, + *DescriptorSize + ); + } + + // + // Remember some descriptor size, since we will not have it later + // during hibernate wake to be able to iterate memory map. + // + BootCompat->ServiceState.MemoryMapDescriptorSize = *DescriptorSize; + } + + return Status; +} + +/** + UEFI Boot Services ExitBootServices override. + Patches kernel entry point with jump to our KernelEntryPatchJumpBack(). +**/ +STATIC +EFI_STATUS +EFIAPI +OcExitBootServices ( + IN EFI_HANDLE ImageHandle, + IN UINTN MapKey + ) +{ + EFI_STATUS Status; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + + // + // For non-macOS operating systems return directly. + // + if (BootCompat->ServiceState.AppleBootNestedCount == 0) { + return BootCompat->ServicePtrs.ExitBootServices ( + ImageHandle, + MapKey + ); + } + + // + // We need hibernate image address for wake, check it quickly before + // killing boot services to be able to print the error. + // + if (BootCompat->Settings.SetupAppleMap + && BootCompat->ServiceState.AppleHibernateWake + && BootCompat->ServiceState.HibernateImageAddress == 0) { + DEBUG ((DEBUG_ERROR, "OCABC: Failed to find hibernate image address\n")); + gBS->Stall (SECONDS_TO_MICROSECONDS (5)); + return EFI_INVALID_PARAMETER; + } + + if (BootCompat->Settings.ForceExitBootServices) { + Status = ForceExitBootServices ( + ImageHandle, + MapKey, + BootCompat->ServicePtrs.ExitBootServices, + BootCompat->ServicePtrs.GetMemoryMap + ); + } else { + Status = BootCompat->ServicePtrs.ExitBootServices ( + ImageHandle, + MapKey + ); + } + + // + // Abort on error or when we are not supposed to do extra mapping. + // + if (EFI_ERROR (Status) || !BootCompat->Settings.SetupAppleMap) { + return Status; + } + + if (!BootCompat->ServiceState.AppleHibernateWake) { + AppleMapPrepareKernelJump ( + BootCompat, + (UINTN) BootCompat->ServiceState.MinAllocatedAddr, + FALSE + ); + } else { + AppleMapPrepareKernelJump ( + BootCompat, + (UINTN) BootCompat->ServiceState.HibernateImageAddress, + TRUE + ); + } + + return Status; +} + +/** + UEFI Runtime Services SetVirtualAddressMap override. + Fixes virtualizing of RT services. +**/ +STATIC +EFI_STATUS +EFIAPI +OcSetVirtualAddressMap ( + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize, + IN UINT32 DescriptorVersion, + IN EFI_MEMORY_DESCRIPTOR *MemoryMap + ) +{ + EFI_STATUS Status; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + + // + // This is the time for us to remove our hacks. + // Make SetVirtualAddressMap useable once again. + // We do not need to recover BS, since they already are invalid. + // + gRT->SetVirtualAddressMap = BootCompat->ServicePtrs.SetVirtualAddressMap; + gRT->Hdr.CRC32 = 0; + gRT->Hdr.CRC32 = CalculateCrc32 (gRT, gRT->Hdr.HeaderSize); + + // + // For non-macOS operating systems return directly. + // Also do nothing for custom mapping. + // + if (BootCompat->ServiceState.AppleBootNestedCount == 0 + || !BootCompat->Settings.SetupAppleMap) { + Status = gRT->SetVirtualAddressMap ( + MemoryMapSize, + DescriptorSize, + DescriptorVersion, + MemoryMap + ); + } else { + Status = AppleMapPrepareVmState ( + BootCompat, + MemoryMapSize, + DescriptorSize, + DescriptorVersion, + MemoryMap + ); + } + + return Status; +} + +/** + UEFI Runtime Services GetVariable override. + Used to return customised values for boot-args and csr-active-config variables. +**/ +STATIC +EFI_STATUS +EFIAPI +OcGetVariable ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + OUT UINT32 *Attributes OPTIONAL, + IN OUT UINTN *DataSize, + OUT VOID *Data + ) +{ + EFI_STATUS Status; + BOOT_COMPAT_CONTEXT *BootCompat; + + BootCompat = GetBootCompatContext (); + + if (BootCompat->Settings.SetupAppleSlide) { + // FIXME: Implement + } + + Status = BootCompat->ServicePtrs.GetVariable ( + VariableName, + VendorGuid, + Attributes, + DataSize, + Data + ); + + return Status; +} + +/** + UEFI Runtime Services GetVariable override event handler. + We do not override GetVariable ourselves but let our runtime do that. + + @param[in] Event Event handle. + @param[in] Context Services pointers context. +**/ +STATIC +VOID +EFIAPI +SetGetVariableHookHandler ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + OC_FIRMWARE_RUNTIME_PROTOCOL *FwRuntime; + UEFI_SERVICES_POINTERS *ServicePtrs; + + ServicePtrs = (UEFI_SERVICES_POINTERS *) Context; + + if (ServicePtrs->GetVariable == NULL) { + Status = gBS->LocateProtocol ( + &gOcFirmwareRuntimeProtocolGuid, + NULL, + (VOID **) &FwRuntime + ); + + if (!EFI_ERROR (Status) && FwRuntime->Revision == OC_FIRMWARE_RUNTIME_REVISION) { + FwRuntime->OnGetVariable (OcGetVariable, &ServicePtrs->GetVariable); + } + } +} + +VOID +InstallServiceOverrides ( + IN OUT BOOT_COMPAT_CONTEXT *BootCompat + ) +{ + EFI_STATUS Status; + VOID *Registration; + UEFI_SERVICES_POINTERS *ServicePtrs; + + ServicePtrs = &BootCompat->ServicePtrs; + + ServicePtrs->AllocatePages = gBS->AllocatePages; + ServicePtrs->GetMemoryMap = gBS->GetMemoryMap; + ServicePtrs->ExitBootServices = gBS->ExitBootServices; + ServicePtrs->StartImage = gBS->StartImage; + ServicePtrs->SetVirtualAddressMap = gRT->SetVirtualAddressMap; + + gBS->AllocatePages = OcAllocatePages; + gBS->GetMemoryMap = OcGetMemoryMap; + gBS->ExitBootServices = OcExitBootServices; + gBS->StartImage = OcStartImage; + gRT->SetVirtualAddressMap = OcSetVirtualAddressMap; + + gBS->Hdr.CRC32 = 0; + gBS->Hdr.CRC32 = CalculateCrc32 (gBS, gBS->Hdr.HeaderSize); + + gRT->Hdr.CRC32 = 0; + gRT->Hdr.CRC32 = CalculateCrc32 (gRT, gRT->Hdr.HeaderSize); + + // + // Allocate memory pool if needed. + // + if (BootCompat->Settings.SetupAppleMap) { + AppleMapPrepareMemoryPool ( + BootCompat + ); + } + + // + // Update GetVariable handle with the help of external runtime services. + // + SetGetVariableHookHandler (NULL, ServicePtrs); + + if (ServicePtrs->GetVariable != NULL) { + return; + } + + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + SetGetVariableHookHandler, + ServicePtrs, + &BootCompat->ServiceState.GetVariableEvent + ); + + if (!EFI_ERROR (Status)) { + Status = gBS->RegisterProtocolNotify ( + &gOcFirmwareRuntimeProtocolGuid, + BootCompat->ServiceState.GetVariableEvent, + &Registration + ); + + if (EFI_ERROR (Status)) { + gBS->CloseEvent (&BootCompat->ServiceState.GetVariableEvent); + } + } +} diff --git a/Library/OcAppleBootCompatLib/X64/ContextSwitch.h b/Library/OcAppleBootCompatLib/X64/ContextSwitch.h new file mode 100644 index 00000000..64ed335b --- /dev/null +++ b/Library/OcAppleBootCompatLib/X64/ContextSwitch.h @@ -0,0 +1,89 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#ifndef CONTEXT_SWITCH_H +#define CONTEXT_SWITCH_H + +// +// Structure definitions shared with ASM code. +// Keep these definitions in sync with ContextSwitch.nasm! +// + +/** + Assembly support state. + This state is used as an intermediate structure to hold UEFI environment + context and kernel environment context for switching between 32-bit + and 64-bit modes during booting as normal XNU boot still happens in 32-bit. +**/ +typedef struct ASM_SUPPORT_STATE_ { + UINT64 SavedGDTR; + UINT16 SavedGDTRLimit; + UINT64 SavedIDTR; + UINT16 SavedIDTRLimit; + UINT64 SavedCR3; + UINT16 SavedCS; + UINT16 SavedDS; + UINT16 SavedES; + UINT16 SavedFS; + UINT16 SavedGS; + UINT16 SavedSS; + + UINT64 SavedGDTR32; + UINT16 SavedGDTR32Limit; + UINT64 SavedIDTR32; + UINT16 SavedIDTR32Limit; + UINT16 SavedCS32; + UINT16 SavedDS32; + UINT16 SavedES32; + UINT16 SavedFS32; + UINT16 SavedGS32; + UINT16 SavedSS32; + UINT32 SavedESP32; + + VOID *KernelEntry; +} ASM_SUPPORT_STATE; + +/** + Assembly kernel trampoline. + This structure contains encoded assembly to jump from kernel + code to UEFI code through AsmAppleMapPlatformPrepareKernelState + intermediate handler. +**/ +typedef struct ASM_KERNEL_JUMP_ { + UINT8 MovInst; + UINT32 Addr; + UINT16 CallInst; +} ASM_KERNEL_JUMP; + +VOID +EFIAPI +AsmAppleMapPlatformSaveState ( + IN OUT ASM_SUPPORT_STATE *AsmState + ); + +/** + Assembly interface for backjump from kernel code. + Takes kernel arguments through RAX or EAX register. +**/ +VOID +AsmAppleMapPlatformPrepareKernelState ( + ); + +/** + Assembly global variable containing ASM_SUPPORT_STATE address. + Must fit into lower 32-bit bytes due to 32-bit . +**/ +extern UINT32 gOcAbcAsmStateAddr32; + +#endif // CONTEXT_SWITCH_H diff --git a/Library/OcAppleBootCompatLib/X64/ContextSwitch.nasm b/Library/OcAppleBootCompatLib/X64/ContextSwitch.nasm new file mode 100755 index 00000000..c2e89606 --- /dev/null +++ b/Library/OcAppleBootCompatLib/X64/ContextSwitch.nasm @@ -0,0 +1,336 @@ +;------------------------------------------------------------------------------ +; @file +; Copyright (C) 2013, dmazar. All rights reserved. +; Copyright (C) 2019, vit9696. All rights reserved. +; +; All rights reserved. +; +; This program and the accompanying materials +; are licensed and made available under the terms and conditions of the BSD License +; which accompanies this distribution. The full text of the license may be found at +; http://opensource.org/licenses/bsd-license.php +; +; THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +; WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +;------------------------------------------------------------------------------ + +BITS 64 +DEFAULT REL + +;------------------------------------------------------------------------------ +; Structure definitions shared with C code. +; Keep these definitions in sync with ContextSwitch.h! +;------------------------------------------------------------------------------ + +struc ASM_SUPPORT_STATE + ;------------------------------------------------------------------------------ + ; 64-bit state + ;------------------------------------------------------------------------------ + + .SavedGDTR resq 1 + .SavedGDTRLimit resw 1 + .SavedIDTR resq 1 + .SavedIDTRLimit resw 1 + .SavedCR3 resq 1 + .SavedCS resw 1 + .SavedDS resw 1 + .SavedES resw 1 + .SavedFS resw 1 + .SavedGS resw 1 + .SavedSS resw 1 + + ;------------------------------------------------------------------------------ + ; 32-bit state + ;------------------------------------------------------------------------------ + + .SavedGDTR32 resq 1 + .SavedGDTR32Limit resw 1 + .SavedIDTR32 resq 1 + .SavedIDTR32Limit resw 1 + .SavedCS32 resw 1 + .SavedDS32 resw 1 + .SavedES32 resw 1 + .SavedFS32 resw 1 + .SavedGS32 resw 1 + .SavedSS32 resw 1 + .SavedESP32 resd 1 + + ;------------------------------------------------------------------------------ + ; Kernel entry address. + ;------------------------------------------------------------------------------ + + .KernelEntry resq 1 + + .Size: +endstruc + +struc ASM_KERNEL_JUMP + .MovInst resb 1 + .Addr resd 1 + .CallInst resw 1 + + .Size: +endstruc + +;------------------------------------------------------------------------------ +; C callback method called on jump to kernel after boot.efi finishes. +;------------------------------------------------------------------------------ + +extern ASM_PFX(AppleMapPrepareKernelState) + +SECTION .text + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; AsmAppleMapPlatformSaveState ( +; OUT ASM_SUPPORT_STATE *AsmState +; ); +;------------------------------------------------------------------------------ +align 8 +global ASM_PFX(AsmAppleMapSaveState) +ASM_PFX(AsmAppleMapSaveState): +BITS 64 + sgdt [rcx + ASM_SUPPORT_STATE.SavedGDTR] + sidt [rcx + ASM_SUPPORT_STATE.SavedIDTR] + mov rax, cr3 + mov [rcx + ASM_SUPPORT_STATE.SavedCR3], rax + mov word [rcx + ASM_SUPPORT_STATE.SavedCS], cs + mov word [rcx + ASM_SUPPORT_STATE.SavedDS], ds + mov word [rcx + ASM_SUPPORT_STATE.SavedES], es + mov word [rcx + ASM_SUPPORT_STATE.SavedFS], fs + mov word [rcx + ASM_SUPPORT_STATE.SavedGS], gs + mov word [rcx + ASM_SUPPORT_STATE.SavedSS], ss + ret + +;------------------------------------------------------------------------------ +; Long (far) return. +; retfq (lretq) - 64-bit encoding 48 CB +; retf (lret) - 32-bit encoding CB +;------------------------------------------------------------------------------ +LONG_RET64: + db 048h +LONG_RET32: + db 0CBh + +;------------------------------------------------------------------------------ +; AsmAppleMapPlatformPrepareKernelState +; +; Callback from boot.efi - this is where we jump when boot.efi jumps to kernel. +; eax register contains boot arguments for the kernel. +; +; - test if we are in 32 bit or in 64 bit +; - if 64 bit, then jump to AsmJumpFromKernel64 +; - else just continue with AsmJumpFromKernel32 +;------------------------------------------------------------------------------ +global ASM_PFX(AsmAppleMapPlatformPrepareKernelState) +ASM_PFX(AsmAppleMapPlatformPrepareKernelState): +BITS 32 + push eax ; save bootArgs pointer to stack + mov dword ecx, 0C0000080h ; EFER MSR number. + rdmsr ; Read EFER. + bt eax, 8 ; Check if LME==1 -> CF=1. + pop eax + jc AsmJumpFromKernel64 ; LME==1 -> jump to 64 bit code + ; otherwise, continue with AsmJumpFromKernel32 + +; Above 32-bit code must give the opcodes equivalent to following in 64-bit. +;BITS 64 +; push rax ; save bootArgs pointer to stack +; mov ecx, C0000080h ; EFER MSR number. +; rdmsr ; Read EFER. +; bt eax, 8 ; Check if LME==1 -> CF=1. +; pop rax +; jc AsmJumpFromKernel64 ; LME==1 -> jump to 64 bit code + +;------------------------------------------------------------------------------ +; AsmJumpFromKernel32 +; +; Callback from boot.efi in 32 bit mode. +;------------------------------------------------------------------------------ +AsmJumpFromKernel32: +BITS 32 + ; Save bootArgs pointer to edi. + mov edi, eax + + ; Load ebx with AsmState - we'll access our saved data with it. + db 0BBh ; mov ebx, OFFSET DataBase + +;------------------------------------------------------------------------------ +; 32-bit pointer to AsmState used to reduce global variable access. +; Defined here becuase 32-bit mode does not support relative addressing. +; As both jumps can happen from 64-bit kernel, the address must fit in 4 bytes. +;------------------------------------------------------------------------------ +global ASM_PFX(gOcAbcAsmStateAddr32) +ASM_PFX(gOcAbcAsmStateAddr32): + dd 0 + + ; Store kernel entery point prior to hunk code. + pop ecx + sub ecx, ASM_KERNEL_JUMP.Size + mov dword [ebx + ASM_SUPPORT_STATE.KernelEntry], ecx + + ; Store 32-bit state to be able to recover it later. + sgdt [ebx + ASM_SUPPORT_STATE.SavedGDTR32] + sidt [ebx + ASM_SUPPORT_STATE.SavedIDTR32] + mov word [ebx + ASM_SUPPORT_STATE.SavedCS32], cs + mov word [ebx + ASM_SUPPORT_STATE.SavedDS32], ds + mov word [ebx + ASM_SUPPORT_STATE.SavedES32], es + mov word [ebx + ASM_SUPPORT_STATE.SavedFS32], fs + mov word [ebx + ASM_SUPPORT_STATE.SavedGS32], gs + mov word [ebx + ASM_SUPPORT_STATE.SavedSS32], ss + mov dword [ebx + ASM_SUPPORT_STATE.SavedESP32], esp + + ; + ; Transition to 64-bit mode... + ; FIXME: we should ensure interrupts are disabled here. + ; + + ; Load saved UEFI GDT and IDT. + ; They will become active after code segment is changed in long jump. + lgdt [ebx + ASM_SUPPORT_STATE.SavedGDTR] + lidt [ebx + ASM_SUPPORT_STATE.SavedIDTR] + + ; Enable the 64-bit page-translation-table entries by setting CR4.PAE=1. + mov eax, cr4 + bts eax, 5 + mov cr4, eax + + ; Set the long-mode page tables by reusing saved UEFI tables. + mov eax, dword [ebx + ASM_SUPPORT_STATE.SavedCR3] + mov cr3, eax + + ; Enable long mode (set EFER.LME=1). + mov ecx, 0C0000080h ; EFER MSR number. + rdmsr ; Read EFER. + bts eax, 8 ; Set LME=1. + wrmsr ; Write EFER. + + ; Enable paging to activate long mode (set CR0.PG=1). + mov eax, cr0 ; Read CR0. + bts eax, 31 ; Set PG=1. + mov cr0, eax ; Write CR0. + + ; Jump to the 64-bit code segment. + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedCS] + push eax + call LONG_RET32 + +BITS 64 + + ; + ; Done transitioning to 64-bit code. + ; + + ; Set other segment selectors. In most firmwares all segment registers but cs + ; point to the same selector, but we must not rely on it. + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedDS] + mov ds, ax + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedES] + mov es, ax + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedFS] + mov fs, ax + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedGS] + mov gs, ax + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedSS] + mov ss, ax + + ; Assume the stack is useasble but potentially misaligned. + and rsp, 0FFFFFFFFFFFFFFF0h + + ; Call AppleMapPrepareKernelState (rcx = rax = bootArgs, rdx = 0 = 32 bit kernel jump). + mov rcx, rdi + xor edx, edx + push rdx + push rdx + push rdx + push rcx + call ASM_PFX(AppleMapPrepareKernelState) + + ; Return value in rax is bootArgs pointer again. + mov rdi, rax + + ; + ; Transition back to 32-bit. + ; + + ; Load saved 32-bit GDT. + lgdt [rbx + ASM_SUPPORT_STATE.SavedGDTR32] + + ; Jump to the 32-bit code segment. + mov ax, word [rbx + ASM_SUPPORT_STATE.SavedCS32] + push rax + call LONG_RET64 + +BITS 32 + + ; + ; Done transitioning to 32-bit code. + ; + + ; Disable paging (set CR0.PG=0). + mov eax, cr0 ; Read CR0. + btr eax, 31 ; Set PG=0. + mov cr0, eax ; Write CR0. + + ; Disable long mode (set EFER.LME=0). + mov ecx, 0C0000080h ; EFER MSR number. + rdmsr ; Read EFER. + btr eax, 8 ; Set LME=0. + wrmsr ; Write EFER. + jmp AsmJumpFromKernel32Compatibility + +AsmJumpFromKernel32Compatibility: + + ; + ; We are in 32-bit protected mode, no paging. + ; + + ; Reload saved 32 bit state data. + lidt [ebx + ASM_SUPPORT_STATE.SavedIDTR32] + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedDS32] + mov ds, ax + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedES32] + mov es, ax + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedFS32] + mov fs, ax + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedGS32] + mov gs, ax + mov ax, word [ebx + ASM_SUPPORT_STATE.SavedSS32] + mov ss, ax ; disables interrupts for 1 instruction to load esp + mov esp, dword [ebx + ASM_SUPPORT_STATE.SavedESP32] + + ; Jump back to the kernel passing boot arguments in eax. + mov eax, edi + mov edx, dword [ebx + ASM_SUPPORT_STATE.KernelEntry] + jmp edx + +;------------------------------------------------------------------------------ +; AsmJumpFromKernel64 +; +; Callback from boot.efi in 64 bit mode. +; State is prepared for kernel: 64 bit, pointer to bootArgs in rax. +;------------------------------------------------------------------------------ +AsmJumpFromKernel64: +BITS 64 + ; Load rbx with AsmState - we'll access our saved data with it. + mov ebx, dword [ASM_PFX(gOcAbcAsmStateAddr32)] + + ; Store kernel entery point prior to hunk code. + pop rcx + sub rcx, ASM_KERNEL_JUMP.Size + mov qword [rbx + ASM_SUPPORT_STATE.KernelEntry], rcx + + ; Call AppleMapPrepareKernelState (rcx = rax = bootArgs, rdx = 1 = 64-bit kernel jump). + mov rcx, rax + xor edx, edx + push rdx + push rdx + push rdx + push rcx + inc edx + call ASM_PFX(AppleMapPrepareKernelState) + + ; Jump back to the kernel passing boot arguments in rax. + mov rdx, [rbx + ASM_SUPPORT_STATE.KernelEntry] + jmp rdx diff --git a/Library/OcAppleBootCompatLib/X64/ContextSwitchSupport.c b/Library/OcAppleBootCompatLib/X64/ContextSwitchSupport.c new file mode 100644 index 00000000..3af7d3c4 --- /dev/null +++ b/Library/OcAppleBootCompatLib/X64/ContextSwitchSupport.c @@ -0,0 +1,48 @@ +/** @file + Copyright (C) 2019, vit9696. All rights reserved. + + All rights reserved. + + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +**/ + +#include "../BootCompatInternal.h" + +#include + +VOID +AppleMapPlatformSaveState ( + IN OUT ASM_SUPPORT_STATE *AsmState, + OUT ASM_KERNEL_JUMP *KernelJump + ) +{ + // + // Save current 64-bit state - will be used later in callback from kernel jump + // to be able to transition to 64-bit in case 32-bit kernel startup code is used. + // + AsmAppleMapPlatformSaveState (AsmState); + + // + // Assembly state must fit into 32-bit address as we may jumo from 32-bit kernel + // startup code. This is used instead of GetBootCompatContext. + // + ASSERT ((UINT32)(UINTN) AsmState == (UINTN) AsmState); + gOcAbcAsmStateAddr32 = (UINT32)(UINTN) AsmState; + + // + // Kernel trampoline for jumping to kernel. + // + ASSERT ( + (UINT32)(UINTN) AsmAppleMapPlatformPrepareKernelState + == (UINTN) AsmAppleMapPlatformPrepareKernelState + ); + KernelJump->MovInst = 0xB9; + KernelJump->Addr = (UINT32)(UINTN) AsmAppleMapPlatformPrepareKernelState; + KernelJump->CallInst = 0xD1FF; +} diff --git a/Library/OcBootManagementLib/BootArguments.c b/Library/OcBootManagementLib/BootArguments.c index 0d0a1178..14bda653 100644 --- a/Library/OcBootManagementLib/BootArguments.c +++ b/Library/OcBootManagementLib/BootArguments.c @@ -12,11 +12,14 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ +#include + #include #include #include #include #include +#include VOID OcParseBootArgs ( @@ -151,3 +154,76 @@ OcAppendArgumentToCmd ( AsciiStrnCpyS (CommandLine, ArgumentLength + 1, Argument, ArgumentLength + 1); return TRUE; } + +BOOLEAN +OcCheckArgumentFromEnv ( + IN EFI_LOADED_IMAGE *LoadedImage OPTIONAL, + IN EFI_GET_VARIABLE GetVariable OPTIONAL, + IN CONST CHAR8 *Argument, + IN CONST UINTN ArgumentLength + ) +{ + CHAR16 *Options; + UINTN OptionsSize; + CHAR8 BootArgsVar[BOOT_LINE_LENGTH]; + UINTN BootArgsVarLen; + EFI_STATUS Status; + UINTN LastIndex; + CHAR16 Last; + BOOLEAN HasArgument; + + HasArgument = FALSE; + + if (LoadedImage != NULL) { + Options = (CHAR16 *) LoadedImage->LoadOptions; + OptionsSize = LoadedImage->LoadOptionsSize / sizeof (CHAR16); + + if (Options != NULL && OptionsSize > 0) { + // + // Just in case we do not have 0-termination. + // This may cut some data with unexpected options, but it is not like we care. + // + LastIndex = OptionsSize - 1; + Last = Options[LastIndex]; + Options[LastIndex] = '\0'; + + UnicodeStrToAsciiStrS (Options, BootArgsVar, BOOT_LINE_LENGTH); + + if (OcGetArgumentFromCmd (BootArgsVar, Argument, ArgumentLength)) { + HasArgument = TRUE; + } + + // + // Options do not belong to us, restore the changed value. + // + Options[LastIndex] = Last; + } + } + + if (!HasArgument) { + // + // Important to avoid triggering boot-args wrapper too early if we have any. + // + BootArgsVarLen = sizeof (BootArgsVar); + Status = (GetVariable != NULL ? GetVariable : gRT->GetVariable) ( + L"boot-args", + &gAppleBootVariableGuid, + NULL, + &BootArgsVarLen, + BootArgsVar + ); + + if (!EFI_ERROR (Status) && BootArgsVarLen > 0) { + // + // Just in case we do not have 0-termination + // + BootArgsVar[BootArgsVarLen-1] = '\0'; + + if (OcGetArgumentFromCmd (BootArgsVar, Argument, ArgumentLength)) { + HasArgument = TRUE; + } + } + } + + return HasArgument; +} diff --git a/Library/OcBootManagementLib/OcBootManagementLib.c b/Library/OcBootManagementLib/OcBootManagementLib.c index c3f50ec3..ecc10e72 100644 --- a/Library/OcBootManagementLib/OcBootManagementLib.c +++ b/Library/OcBootManagementLib/OcBootManagementLib.c @@ -601,6 +601,59 @@ OcActivateHibernateWake ( return EFI_NOT_FOUND; } +BOOLEAN +OcIsAppleHibernateWake ( + VOID + ) +{ + EFI_STATUS Status; + UINTN ValueSize; + + // + // This is reverse engineered from boot.efi. + // To cancel hibernate wake it is enough to delete the variables. + // Starting with 10.13.6 boot-switch-vars is no longer supported. + // + ValueSize = 0; + Status = gRT->GetVariable ( + L"boot-signature", + &gAppleBootVariableGuid, + NULL, + &ValueSize, + NULL + ); + + if (Status == EFI_BUFFER_TOO_SMALL) { + ValueSize = 0; + Status = gRT->GetVariable ( + L"boot-image-key", + &gAppleBootVariableGuid, + NULL, + &ValueSize, + NULL + ); + + if (Status == EFI_BUFFER_TOO_SMALL) { + return TRUE; + } + } else { + ValueSize = 0; + Status = gRT->GetVariable ( + L"boot-switch-vars", + &gAppleBootVariableGuid, + NULL, + &ValueSize, + NULL + ); + + if (Status == EFI_BUFFER_TOO_SMALL) { + return TRUE; + } + } + + return TRUE; +} + EFI_STATUS OcShowSimpleBootMenu ( IN OC_BOOT_ENTRY *BootEntries, diff --git a/Library/OcMemoryLib/VirtualMemory.c b/Library/OcMemoryLib/VirtualMemory.c index 32abc91c..a6642d29 100755 --- a/Library/OcMemoryLib/VirtualMemory.c +++ b/Library/OcMemoryLib/VirtualMemory.c @@ -160,8 +160,6 @@ VmAllocateMemoryPool ( if (!EFI_ERROR (Status)) { Context->MemoryPool = (UINT8 *) Addr; Context->FreePages = NumPages; - } else { - DEBUG ((DEBUG_ERROR, "OCVM: memory pool allocation failure - %r\n", Status)); } return Status; @@ -181,8 +179,6 @@ VmAllocatePages ( AllocatedPages = Context->MemoryPool; Context->MemoryPool += EFI_PAGES_TO_SIZE (NumPages); Context->FreePages -= NumPages; - } else { - DEBUG ((DEBUG_ERROR, "OCVM: memory pool out of free pages\n")); } return AllocatedPages; diff --git a/Library/OcMiscLib/DirectReset.c b/Library/OcMiscLib/DirectReset.c new file mode 100644 index 00000000..71ba2613 --- /dev/null +++ b/Library/OcMiscLib/DirectReset.c @@ -0,0 +1,29 @@ +/** @file + Reset System Library functions for OVMF + + Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include +#include +#include + +VOID +DirectRestCold ( + VOID + ) +{ + volatile UINTN Index; + IoWrite8 (0xCF9, BIT2 | BIT1); // 1st choice: PIIX3 RCR, RCPU|SRST + + for (Index = 0; Index < 100; ++Index) { + ; + } + + IoWrite8 (0x64, 0xfe); // 2nd choice: keyboard controller + CpuDeadLoop (); +} diff --git a/Library/OcMiscLib/OcMiscLib.inf b/Library/OcMiscLib/OcMiscLib.inf index 32be7485..5646be00 100755 --- a/Library/OcMiscLib/OcMiscLib.inf +++ b/Library/OcMiscLib/OcMiscLib.inf @@ -46,6 +46,7 @@ [Sources] Base64Decode.c DataPatcher.c + DirectReset.c ReleaseUsbOwnership.c NullTextOutput.c UninstallAllProtocolInterfaces.c diff --git a/OcSupportPkg.dec b/OcSupportPkg.dec index ab8c7c02..6bffc785 100644 --- a/OcSupportPkg.dec +++ b/OcSupportPkg.dec @@ -41,6 +41,9 @@ ## Include/Protocol/OcLog.h gOcLogProtocolGuid = { 0xDBB6008F, 0x89E4, 0x4272, { 0x98, 0x81, 0xCE, 0x3A, 0xFD, 0x97, 0x24, 0xD0 }} + # Include/Protocol/OcAppleBootCompat.h + gOcAppleBootCompatProtocolGuid = { 0xC7CBA84E, 0xCC77, 0x461D, { 0x9E, 0x3C, 0x6B, 0xE0, 0xCB, 0x79, 0xA7, 0xC1 } } + ## Include/Protocol/OcFirmwareRuntime.h gOcFirmwareRuntimeProtocolGuid = { 0x9C820F96, 0xF16C, 0x4FFD, { 0xB2, 0x66, 0xDF, 0x0A, 0x8F, 0xDF, 0xC4, 0x55 }} @@ -74,6 +77,9 @@ ## @libraryclass OcAcpiLib|Include/Library/OcAcpiLib.h + ## @libraryclass + OcAppleBootCompatLib|Include/Library/OcAppleBootCompatLib.h + ## @libraryclass OcAppleBootPolicyLib|Include/Library/OcAppleBootPolicyLib.h diff --git a/OcSupportPkg.dsc b/OcSupportPkg.dsc index 781094a0..ffb992f8 100644 --- a/OcSupportPkg.dsc +++ b/OcSupportPkg.dsc @@ -51,6 +51,7 @@ HobLib|MdePkg/Library/DxeHobLib/DxeHobLib.inf FileHandleLib|MdePkg/Library/UefiFileHandleLib/UefiFileHandleLib.inf OcAcpiLib|OcSupportPkg/Library/OcAcpiLib/OcAcpiLib.inf + OcAppleBootCompatLib|OcSupportPkg/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.inf OcAppleBootPolicyLib|OcSupportPkg/Library/OcAppleBootPolicyLib/OcAppleBootPolicyLib.inf OcAppleChunklistLib|OcSupportPkg/Library/OcAppleChunklistLib/OcAppleChunklistLib.inf OcAppleDiskImageLib|OcSupportPkg/Library/OcAppleDiskImageLib/OcAppleDiskImageLib.inf @@ -85,6 +86,7 @@ [Components] OcSupportPkg/Library/OcAcpiLib/OcAcpiLib.inf + OcSupportPkg/Library/OcAppleBootCompatLib/OcAppleBootCompatLib.inf OcSupportPkg/Library/OcAppleBootPolicyLib/OcAppleBootPolicyLib.inf OcSupportPkg/Library/OcAppleChunklistLib/OcAppleChunklistLib.inf OcSupportPkg/Library/OcAppleDiskImageLib/OcAppleDiskImageLib.inf