mirror of
https://github.com/darlinghq/darling-xnu.git
synced 2024-11-23 12:39:55 +00:00
2763 lines
91 KiB
C++
2763 lines
91 KiB
C++
/*
|
|
* Copyright (c) 2004-2016 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* Sleep:
|
|
*
|
|
* - PMRootDomain calls IOHibernateSystemSleep() before system sleep
|
|
* (devices awake, normal execution context)
|
|
* - IOHibernateSystemSleep opens the hibernation file (or partition) at the bsd level,
|
|
* grabs its extents and searches for a polling driver willing to work with that IOMedia.
|
|
* The BSD code makes an ioctl to the storage driver to get the partition base offset to
|
|
* the disk, and other ioctls to get the transfer constraints
|
|
* If successful, the file is written to make sure its initially not bootable (in case of
|
|
* later failure) and nvram set to point to the first block of the file. (Has to be done
|
|
* here so blocking is possible in nvram support).
|
|
* hibernate_setup() in osfmk is called to allocate page bitmaps for all dram, and
|
|
* page out any pages it wants to (currently zero, but probably some percentage of memory).
|
|
* Its assumed just allocating pages will cause the VM system to naturally select the best
|
|
* pages for eviction. It also copies processor flags needed for the restore path and sets
|
|
* a flag in the boot processor proc info.
|
|
* gIOHibernateState = kIOHibernateStateHibernating.
|
|
* - Regular sleep progresses - some drivers may inspect the root domain property
|
|
* kIOHibernateStateKey to modify behavior. The platform driver saves state to memory
|
|
* as usual but leaves motherboard I/O on.
|
|
* - Eventually the platform calls ml_ppc_sleep() in the shutdown context on the last cpu,
|
|
* at which point memory is ready to be saved. mapping_hibernate_flush() is called to get
|
|
* all ppc RC bits out of the hash table and caches into the mapping structures.
|
|
* - hibernate_write_image() is called (still in shutdown context, no blocking or preemption).
|
|
* hibernate_page_list_setall() is called to get a bitmap of dram pages that need to be saved.
|
|
* All pages are assumed to be saved (as part of the wired image) unless explicitly subtracted
|
|
* by hibernate_page_list_setall(), avoiding having to find arch dependent low level bits.
|
|
* The image header and block list are written. The header includes the second file extent so
|
|
* only the header block is needed to read the file, regardless of filesystem.
|
|
* The kernel segment "__HIB" is written uncompressed to the image. This segment of code and data
|
|
* (only) is used to decompress the image during wake/boot.
|
|
* Some additional pages are removed from the bitmaps - the buffers used for hibernation.
|
|
* The bitmaps are written to the image.
|
|
* More areas are removed from the bitmaps (after they have been written to the image) - the
|
|
* segment "__HIB" pages and interrupt stack.
|
|
* Each wired page is compressed and written and then each non-wired page. Compression and
|
|
* disk writes are in parallel.
|
|
* The image header is written to the start of the file and the polling driver closed.
|
|
* The machine powers down (or sleeps).
|
|
*
|
|
* Boot/Wake:
|
|
*
|
|
* - BootX sees the boot-image nvram variable containing the device and block number of the image,
|
|
* reads the header and if the signature is correct proceeds. The boot-image variable is cleared.
|
|
* - BootX reads the portion of the image used for wired pages, to memory. Its assumed this will fit
|
|
* in the OF memory environment, and the image is decrypted. There is no decompression in BootX,
|
|
* that is in the kernel's __HIB section.
|
|
* - BootX copies the "__HIB" section to its correct position in memory, quiesces and calls its entry
|
|
* hibernate_kernel_entrypoint(), passing the location of the image in memory. Translation is off,
|
|
* only code & data in that section is safe to call since all the other wired pages are still
|
|
* compressed in the image.
|
|
* - hibernate_kernel_entrypoint() removes pages occupied by the raw image from the page bitmaps.
|
|
* It uses the bitmaps to work out which pages can be uncompressed from the image to their final
|
|
* location directly, and copies those that can't to interim free pages. When the image has been
|
|
* completed, the copies are uncompressed, overwriting the wired image pages.
|
|
* hibernate_restore_phys_page() (in osfmk since its arch dependent, but part of the "__HIB" section)
|
|
* is used to get pages into place for 64bit.
|
|
* - the reset vector is called (at least on ppc), the kernel proceeds on a normal wake, with some
|
|
* changes conditional on the per proc flag - before VM is turned on the boot cpu, all mappings
|
|
* are removed from the software strutures, and the hash table is reinitialized.
|
|
* - After the platform CPU init code is called, hibernate_machine_init() is called to restore the rest
|
|
* of memory, using the polled mode driver, before other threads can run or any devices are turned on.
|
|
* This reduces the memory usage for BootX and allows decompression in parallel with disk reads,
|
|
* for the remaining non wired pages.
|
|
* - The polling driver is closed down and regular wake proceeds. When the kernel calls iokit to wake
|
|
* (normal execution context) hibernate_teardown() in osmfk is called to release any memory, the file
|
|
* is closed via bsd.
|
|
*
|
|
* Polled Mode I/O:
|
|
*
|
|
* IOHibernateSystemSleep() finds a polled mode interface to the ATA controller via a property in the
|
|
* registry, specifying an object of calls IOPolledInterface.
|
|
*
|
|
* Before the system goes to sleep it searches from the IOMedia object (could be a filesystem or
|
|
* partition) that the image is going to live, looking for polled interface properties. If it finds
|
|
* one the IOMedia object is passed to a "probe" call for the interface to accept or reject. All the
|
|
* interfaces found are kept in an ordered list.
|
|
*
|
|
* There is an Open/Close pair of calls made to each of the interfaces at various stages since there are
|
|
* few different contexts things happen in:
|
|
*
|
|
* - there is an Open/Close (Preflight) made before any part of the system has slept (I/O is all
|
|
* up and running) and after wake - this is safe to allocate memory and do anything. The device
|
|
* ignores sleep requests from that point since its a waste of time if it goes to sleep and
|
|
* immediately wakes back up for the image write.
|
|
*
|
|
* - there is an Open/Close (BeforeSleep) pair made around the image write operations that happen
|
|
* immediately before sleep. These can't block or allocate memory - the I/O system is asleep apart
|
|
* from the low level bits (motherboard I/O etc). There is only one thread running. The close can be
|
|
* used to flush and set the disk to sleep.
|
|
*
|
|
* - there is an Open/Close (AfterSleep) pair made around the image read operations that happen
|
|
* immediately after sleep. These can't block or allocate memory. This is happening after the platform
|
|
* expert has woken the low level bits of the system, but most of the I/O system has not. There is only
|
|
* one thread running.
|
|
*
|
|
* For the actual I/O, all the ops are with respect to a single IOMemoryDescriptor that was passed
|
|
* (prepared) to the Preflight Open() call. There is a read/write op, buffer offset to the IOMD for
|
|
* the data, an offset to the disk and length (block aligned 64 bit numbers), and completion callback.
|
|
* Each I/O is async but only one is ever outstanding. The polled interface has a checkForWork call
|
|
* that is called for the hardware to check for events, and complete the I/O via the callback.
|
|
* The hibernate path uses the same transfer constraints the regular cluster I/O path in BSD uses
|
|
* to restrict I/O ops.
|
|
*/
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <IOKit/IOWorkLoop.h>
|
|
#include <IOKit/IOCommandGate.h>
|
|
#include <IOKit/IOTimerEventSource.h>
|
|
#include <IOKit/IOPlatformExpert.h>
|
|
#include <IOKit/IOKitDebug.h>
|
|
#include <IOKit/IOTimeStamp.h>
|
|
#include <IOKit/pwr_mgt/RootDomain.h>
|
|
#include <IOKit/pwr_mgt/IOPMPrivate.h>
|
|
#include <IOKit/IOMessage.h>
|
|
#include <IOKit/IODeviceTreeSupport.h>
|
|
#include <IOKit/IOBSD.h>
|
|
#include <IOKit/IOKitKeysPrivate.h>
|
|
#include "RootDomainUserClient.h"
|
|
#include <IOKit/pwr_mgt/IOPowerConnection.h>
|
|
#include "IOPMPowerStateQueue.h"
|
|
#include <IOKit/IOBufferMemoryDescriptor.h>
|
|
#include <IOKit/AppleKeyStoreInterface.h>
|
|
#include <libkern/crypto/aes.h>
|
|
|
|
#include <sys/uio.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/fcntl.h> // (FWRITE, ...)
|
|
#include <sys/sysctl.h>
|
|
#include <sys/kdebug.h>
|
|
#include <stdint.h>
|
|
|
|
#include <IOKit/IOHibernatePrivate.h>
|
|
#include <IOKit/IOPolledInterface.h>
|
|
#include <IOKit/IONVRAM.h>
|
|
#include "IOHibernateInternal.h"
|
|
#include <vm/vm_protos.h>
|
|
#include "IOKitKernelInternal.h"
|
|
#include <pexpert/device_tree.h>
|
|
|
|
#include <machine/pal_routines.h>
|
|
#include <machine/pal_hibernate.h>
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
#include <i386/tsc.h>
|
|
#include <i386/cpuid.h>
|
|
#include <vm/WKdm_new.h>
|
|
#elif defined(__arm64__)
|
|
#include <arm64/amcc_rorgn.h>
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
#include <san/kasan.h>
|
|
|
|
|
|
extern "C" addr64_t kvtophys(vm_offset_t va);
|
|
extern "C" ppnum_t pmap_find_phys(pmap_t pmap, addr64_t va);
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#define DISABLE_TRIM 0
|
|
#define TRIM_DELAY 25000
|
|
|
|
extern unsigned int save_kdebug_enable;
|
|
extern uint32_t gIOHibernateState;
|
|
uint32_t gIOHibernateMode;
|
|
static char gIOHibernateBootSignature[256 + 1];
|
|
static char gIOHibernateFilename[MAXPATHLEN + 1];
|
|
uint32_t gIOHibernateCount;
|
|
|
|
static uuid_string_t gIOHibernateBridgeBootSessionUUIDString;
|
|
|
|
static uint32_t gIOHibernateFreeRatio = 0; // free page target (percent)
|
|
uint32_t gIOHibernateFreeTime = 0 * 1000; // max time to spend freeing pages (ms)
|
|
|
|
enum {
|
|
HIB_COMPR_RATIO_ARM64 = (0xa5), // compression ~65%. Since we don't support retries we start higher.
|
|
HIB_COMPR_RATIO_INTEL = (0x80) // compression 50%
|
|
};
|
|
|
|
#if defined(__arm64__)
|
|
static uint64_t gIOHibernateCompression = HIB_COMPR_RATIO_ARM64;
|
|
#else
|
|
static uint64_t gIOHibernateCompression = HIB_COMPR_RATIO_INTEL;
|
|
#endif /* __arm64__ */
|
|
boolean_t gIOHibernateStandbyDisabled;
|
|
|
|
static IODTNVRAM * gIOOptionsEntry;
|
|
static IORegistryEntry * gIOChosenEntry;
|
|
|
|
static const OSSymbol * gIOHibernateBootImageKey;
|
|
static const OSSymbol * gIOHibernateBootSignatureKey;
|
|
static const OSSymbol * gIOBridgeBootSessionUUIDKey;
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
|
|
static const OSSymbol * gIOHibernateRTCVariablesKey;
|
|
static const OSSymbol * gIOHibernateBoot0082Key;
|
|
static const OSSymbol * gIOHibernateBootNextKey;
|
|
static OSData * gIOHibernateBoot0082Data;
|
|
static OSData * gIOHibernateBootNextData;
|
|
static OSObject * gIOHibernateBootNextSave;
|
|
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
|
|
static IOLock * gFSLock;
|
|
uint32_t gFSState;
|
|
static thread_call_t gIOHibernateTrimCalloutEntry;
|
|
static IOPolledFileIOVars gFileVars;
|
|
static IOHibernateVars gIOHibernateVars;
|
|
static IOPolledFileCryptVars gIOHibernateCryptWakeContext;
|
|
static hibernate_graphics_t _hibernateGraphics;
|
|
static hibernate_graphics_t * gIOHibernateGraphicsInfo = &_hibernateGraphics;
|
|
static hibernate_statistics_t _hibernateStats;
|
|
static hibernate_statistics_t * gIOHibernateStats = &_hibernateStats;
|
|
|
|
enum{
|
|
kFSIdle = 0,
|
|
kFSOpening = 2,
|
|
kFSOpened = 3,
|
|
kFSTimedOut = 4,
|
|
kFSTrimDelay = 5
|
|
};
|
|
|
|
static IOReturn IOHibernateDone(IOHibernateVars * vars);
|
|
static IOReturn IOWriteExtentsToFile(IOPolledFileIOVars * vars, uint32_t signature);
|
|
static void IOSetBootImageNVRAM(OSData * data);
|
|
static void IOHibernateSystemPostWakeTrim(void * p1, void * p2);
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
enum { kDefaultIOSize = 128 * 1024 };
|
|
enum { kVideoMapSize = 80 * 1024 * 1024 };
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
// copy from phys addr to MD
|
|
|
|
static IOReturn
|
|
IOMemoryDescriptorWriteFromPhysical(IOMemoryDescriptor * md,
|
|
IOByteCount offset, addr64_t bytes, IOByteCount length)
|
|
{
|
|
addr64_t srcAddr = bytes;
|
|
IOByteCount remaining;
|
|
|
|
remaining = length = min(length, md->getLength() - offset);
|
|
while (remaining) { // (process another target segment?)
|
|
addr64_t dstAddr64;
|
|
IOByteCount dstLen;
|
|
|
|
dstAddr64 = md->getPhysicalSegment(offset, &dstLen, kIOMemoryMapperNone);
|
|
if (!dstAddr64) {
|
|
break;
|
|
}
|
|
|
|
// Clip segment length to remaining
|
|
if (dstLen > remaining) {
|
|
dstLen = remaining;
|
|
}
|
|
|
|
#if 1
|
|
bcopy_phys(srcAddr, dstAddr64, dstLen);
|
|
#else
|
|
copypv(srcAddr, dstAddr64, dstLen,
|
|
cppvPsnk | cppvFsnk | cppvNoRefSrc | cppvNoModSnk | cppvKmap);
|
|
#endif
|
|
srcAddr += dstLen;
|
|
offset += dstLen;
|
|
remaining -= dstLen;
|
|
}
|
|
|
|
assert(!remaining);
|
|
|
|
return remaining ? kIOReturnUnderrun : kIOReturnSuccess;
|
|
}
|
|
|
|
// copy from MD to phys addr
|
|
|
|
static IOReturn
|
|
IOMemoryDescriptorReadToPhysical(IOMemoryDescriptor * md,
|
|
IOByteCount offset, addr64_t bytes, IOByteCount length)
|
|
{
|
|
addr64_t dstAddr = bytes;
|
|
IOByteCount remaining;
|
|
|
|
remaining = length = min(length, md->getLength() - offset);
|
|
while (remaining) { // (process another target segment?)
|
|
addr64_t srcAddr64;
|
|
IOByteCount dstLen;
|
|
|
|
srcAddr64 = md->getPhysicalSegment(offset, &dstLen, kIOMemoryMapperNone);
|
|
if (!srcAddr64) {
|
|
break;
|
|
}
|
|
|
|
// Clip segment length to remaining
|
|
if (dstLen > remaining) {
|
|
dstLen = remaining;
|
|
}
|
|
|
|
#if 1
|
|
bcopy_phys(srcAddr64, dstAddr, dstLen);
|
|
#else
|
|
copypv(srcAddr, dstAddr64, dstLen,
|
|
cppvPsnk | cppvFsnk | cppvNoRefSrc | cppvNoModSnk | cppvKmap);
|
|
#endif
|
|
dstAddr += dstLen;
|
|
offset += dstLen;
|
|
remaining -= dstLen;
|
|
}
|
|
|
|
assert(!remaining);
|
|
|
|
return remaining ? kIOReturnUnderrun : kIOReturnSuccess;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
void
|
|
hibernate_set_page_state(hibernate_page_list_t * page_list, hibernate_page_list_t * page_list_wired,
|
|
vm_offset_t ppnum, vm_offset_t count, uint32_t kind)
|
|
{
|
|
count += ppnum;
|
|
|
|
if (count > UINT_MAX) {
|
|
panic("hibernate_set_page_state ppnum");
|
|
}
|
|
|
|
switch (kind) {
|
|
case kIOHibernatePageStateUnwiredSave:
|
|
// unwired save
|
|
for (; ppnum < count; ppnum++) {
|
|
hibernate_page_bitset(page_list, FALSE, (uint32_t) ppnum);
|
|
hibernate_page_bitset(page_list_wired, TRUE, (uint32_t) ppnum);
|
|
}
|
|
break;
|
|
case kIOHibernatePageStateWiredSave:
|
|
// wired save
|
|
for (; ppnum < count; ppnum++) {
|
|
hibernate_page_bitset(page_list, FALSE, (uint32_t) ppnum);
|
|
hibernate_page_bitset(page_list_wired, FALSE, (uint32_t) ppnum);
|
|
}
|
|
break;
|
|
case kIOHibernatePageStateFree:
|
|
// free page
|
|
for (; ppnum < count; ppnum++) {
|
|
hibernate_page_bitset(page_list, TRUE, (uint32_t) ppnum);
|
|
hibernate_page_bitset(page_list_wired, TRUE, (uint32_t) ppnum);
|
|
}
|
|
break;
|
|
default:
|
|
panic("hibernate_set_page_state");
|
|
}
|
|
}
|
|
|
|
static void
|
|
hibernate_set_descriptor_page_state(IOHibernateVars *vars,
|
|
IOMemoryDescriptor *descriptor,
|
|
uint32_t kind,
|
|
uint32_t *pageCount)
|
|
{
|
|
IOItemCount count;
|
|
addr64_t phys64;
|
|
IOByteCount segLen;
|
|
if (descriptor) {
|
|
for (count = 0;
|
|
(phys64 = descriptor->getPhysicalSegment(count, &segLen, kIOMemoryMapperNone));
|
|
count += segLen) {
|
|
hibernate_set_page_state(vars->page_list, vars->page_list_wired,
|
|
atop_64(phys64), atop_32(segLen),
|
|
kind);
|
|
*pageCount -= atop_32(segLen);
|
|
}
|
|
}
|
|
}
|
|
|
|
static vm_offset_t
|
|
hibernate_page_list_iterate(hibernate_page_list_t * list, ppnum_t * pPage)
|
|
{
|
|
uint32_t page = ((typeof(page)) * pPage);
|
|
uint32_t count;
|
|
hibernate_bitmap_t * bitmap;
|
|
|
|
while ((bitmap = hibernate_page_bitmap_pin(list, &page))) {
|
|
count = hibernate_page_bitmap_count(bitmap, TRUE, page);
|
|
if (!count) {
|
|
break;
|
|
}
|
|
page += count;
|
|
if (page <= bitmap->last_page) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
*pPage = page;
|
|
if (bitmap) {
|
|
count = hibernate_page_bitmap_count(bitmap, FALSE, page);
|
|
} else {
|
|
count = 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
IOReturn
|
|
IOHibernateSystemSleep(void)
|
|
{
|
|
IOReturn err;
|
|
OSData * nvramData;
|
|
OSObject * obj;
|
|
OSString * str;
|
|
OSNumber * num;
|
|
bool dsSSD, vmflush, swapPinned;
|
|
IOHibernateVars * vars;
|
|
uint64_t setFileSize = 0;
|
|
|
|
gIOHibernateState = kIOHibernateStateInactive;
|
|
|
|
gIOHibernateDebugFlags = 0;
|
|
if (kIOLogHibernate & gIOKitDebug) {
|
|
gIOHibernateDebugFlags |= kIOHibernateDebugRestoreLogs;
|
|
}
|
|
|
|
if (IOService::getPMRootDomain()->getHibernateSettings(
|
|
&gIOHibernateMode, &gIOHibernateFreeRatio, &gIOHibernateFreeTime)) {
|
|
if (kIOHibernateModeSleep & gIOHibernateMode) {
|
|
// default to discard clean for safe sleep
|
|
gIOHibernateMode ^= (kIOHibernateModeDiscardCleanInactive
|
|
| kIOHibernateModeDiscardCleanActive);
|
|
}
|
|
}
|
|
|
|
if ((obj = IOService::getPMRootDomain()->copyProperty(kIOHibernateFileKey))) {
|
|
if ((str = OSDynamicCast(OSString, obj))) {
|
|
strlcpy(gIOHibernateFilename, str->getCStringNoCopy(),
|
|
sizeof(gIOHibernateFilename));
|
|
}
|
|
obj->release();
|
|
}
|
|
|
|
if (!gIOHibernateMode || !gIOHibernateFilename[0]) {
|
|
return kIOReturnUnsupported;
|
|
}
|
|
|
|
HIBLOG("hibernate image path: %s\n", gIOHibernateFilename);
|
|
|
|
vars = IONew(IOHibernateVars, 1);
|
|
if (!vars) {
|
|
return kIOReturnNoMemory;
|
|
}
|
|
bzero(vars, sizeof(*vars));
|
|
|
|
IOLockLock(gFSLock);
|
|
if (!gIOHibernateTrimCalloutEntry) {
|
|
gIOHibernateTrimCalloutEntry = thread_call_allocate(&IOHibernateSystemPostWakeTrim, &gFSLock);
|
|
}
|
|
IOHibernateSystemPostWakeTrim(NULL, NULL);
|
|
thread_call_cancel(gIOHibernateTrimCalloutEntry);
|
|
if (kFSIdle != gFSState) {
|
|
HIBLOG("hibernate file busy\n");
|
|
IOLockUnlock(gFSLock);
|
|
IODelete(vars, IOHibernateVars, 1);
|
|
return kIOReturnBusy;
|
|
}
|
|
gFSState = kFSOpening;
|
|
IOLockUnlock(gFSLock);
|
|
|
|
swapPinned = false;
|
|
do{
|
|
vars->srcBuffer = IOBufferMemoryDescriptor::withOptions(kIODirectionOutIn,
|
|
HIBERNATION_SRC_BUFFER_SIZE, page_size);
|
|
|
|
vars->handoffBuffer = IOBufferMemoryDescriptor::withOptions(kIODirectionOutIn,
|
|
ptoa_64(gIOHibernateHandoffPageCount), page_size);
|
|
|
|
if (!vars->srcBuffer || !vars->handoffBuffer) {
|
|
err = kIOReturnNoMemory;
|
|
break;
|
|
}
|
|
|
|
if ((obj = IOService::getPMRootDomain()->copyProperty(kIOHibernateFileMinSizeKey))) {
|
|
if ((num = OSDynamicCast(OSNumber, obj))) {
|
|
vars->fileMinSize = num->unsigned64BitValue();
|
|
}
|
|
obj->release();
|
|
}
|
|
if ((obj = IOService::getPMRootDomain()->copyProperty(kIOHibernateFileMaxSizeKey))) {
|
|
if ((num = OSDynamicCast(OSNumber, obj))) {
|
|
vars->fileMaxSize = num->unsigned64BitValue();
|
|
}
|
|
obj->release();
|
|
}
|
|
|
|
boolean_t encryptedswap = true;
|
|
uint32_t pageCount;
|
|
AbsoluteTime startTime, endTime;
|
|
uint64_t nsec;
|
|
|
|
bzero(gIOHibernateCurrentHeader, sizeof(IOHibernateImageHeader));
|
|
gIOHibernateCurrentHeader->debugFlags = gIOHibernateDebugFlags;
|
|
gIOHibernateCurrentHeader->signature = kIOHibernateHeaderInvalidSignature;
|
|
|
|
vmflush = ((kOSBooleanTrue == IOService::getPMRootDomain()->getProperty(kIOPMDeepSleepEnabledKey)));
|
|
err = hibernate_alloc_page_lists(&vars->page_list,
|
|
&vars->page_list_wired,
|
|
&vars->page_list_pal);
|
|
if (KERN_SUCCESS != err) {
|
|
HIBLOG("%s err, hibernate_alloc_page_lists return 0x%x\n", __FUNCTION__, err);
|
|
break;
|
|
}
|
|
|
|
err = hibernate_pin_swap(TRUE);
|
|
if (KERN_SUCCESS != err) {
|
|
HIBLOG("%s error, hibernate_pin_swap return 0x%x\n", __FUNCTION__, err);
|
|
break;
|
|
}
|
|
swapPinned = true;
|
|
|
|
if (vars->fileMinSize || (kIOHibernateModeFileResize & gIOHibernateMode)) {
|
|
hibernate_page_list_setall(vars->page_list,
|
|
vars->page_list_wired,
|
|
vars->page_list_pal,
|
|
true /* preflight */,
|
|
vmflush /* discard */,
|
|
&pageCount);
|
|
PE_Video consoleInfo;
|
|
bzero(&consoleInfo, sizeof(consoleInfo));
|
|
IOService::getPlatform()->getConsoleInfo(&consoleInfo);
|
|
|
|
// estimate: 6% increase in pages compressed
|
|
// screen preview 2 images compressed 0%
|
|
setFileSize = ((ptoa_64((106 * pageCount) / 100) * gIOHibernateCompression) >> 8)
|
|
+ vars->page_list->list_size
|
|
+ (consoleInfo.v_width * consoleInfo.v_height * 8);
|
|
enum { setFileRound = 1024 * 1024ULL };
|
|
setFileSize = ((setFileSize + setFileRound) & ~(setFileRound - 1));
|
|
|
|
HIBLOG("hibernate_page_list_setall preflight pageCount %d est comp %qd setfile %qd min %qd\n",
|
|
pageCount, (100ULL * gIOHibernateCompression) >> 8,
|
|
setFileSize, vars->fileMinSize);
|
|
|
|
if (!(kIOHibernateModeFileResize & gIOHibernateMode)
|
|
&& (setFileSize < vars->fileMinSize)) {
|
|
setFileSize = vars->fileMinSize;
|
|
}
|
|
}
|
|
|
|
vars->volumeCryptKeySize = sizeof(vars->volumeCryptKey);
|
|
err = IOPolledFileOpen(gIOHibernateFilename,
|
|
(kIOPolledFileCreate | kIOPolledFileHibernate),
|
|
setFileSize, 0,
|
|
gIOHibernateCurrentHeader, sizeof(gIOHibernateCurrentHeader),
|
|
&vars->fileVars, &nvramData,
|
|
&vars->volumeCryptKey[0], &vars->volumeCryptKeySize);
|
|
|
|
if (KERN_SUCCESS != err) {
|
|
IOLockLock(gFSLock);
|
|
if (kFSOpening != gFSState) {
|
|
err = kIOReturnTimeout;
|
|
}
|
|
IOLockUnlock(gFSLock);
|
|
}
|
|
|
|
if (KERN_SUCCESS != err) {
|
|
HIBLOG("IOPolledFileOpen(%x)\n", err);
|
|
break;
|
|
}
|
|
|
|
// write extents for debug data usage in EFI
|
|
IOWriteExtentsToFile(vars->fileVars, kIOHibernateHeaderOpenSignature);
|
|
|
|
err = IOPolledFilePollersSetup(vars->fileVars, kIOPolledPreflightState);
|
|
if (KERN_SUCCESS != err) {
|
|
break;
|
|
}
|
|
|
|
clock_get_uptime(&startTime);
|
|
err = hibernate_setup(gIOHibernateCurrentHeader,
|
|
vmflush,
|
|
vars->page_list, vars->page_list_wired, vars->page_list_pal);
|
|
clock_get_uptime(&endTime);
|
|
SUB_ABSOLUTETIME(&endTime, &startTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
|
|
boolean_t haveSwapPin, hibFileSSD;
|
|
haveSwapPin = vm_swap_files_pinned();
|
|
|
|
hibFileSSD = (kIOPolledFileSSD & vars->fileVars->flags);
|
|
|
|
HIBLOG("hibernate_setup(%d) took %qd ms, swapPin(%d) ssd(%d)\n",
|
|
err, nsec / 1000000ULL,
|
|
haveSwapPin, hibFileSSD);
|
|
if (KERN_SUCCESS != err) {
|
|
break;
|
|
}
|
|
|
|
gIOHibernateStandbyDisabled = ((!haveSwapPin || !hibFileSSD));
|
|
|
|
dsSSD = ((0 != (kIOPolledFileSSD & vars->fileVars->flags))
|
|
&& (kOSBooleanTrue == IOService::getPMRootDomain()->getProperty(kIOPMDeepSleepEnabledKey)));
|
|
|
|
if (dsSSD) {
|
|
gIOHibernateCurrentHeader->options |= kIOHibernateOptionSSD | kIOHibernateOptionColor;
|
|
} else {
|
|
gIOHibernateCurrentHeader->options |= kIOHibernateOptionProgress;
|
|
}
|
|
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
if (vars->volumeCryptKeySize &&
|
|
(kOSBooleanTrue != IOService::getPMRootDomain()->getProperty(kIOPMDestroyFVKeyOnStandbyKey))) {
|
|
uintptr_t smcVars[2];
|
|
smcVars[0] = vars->volumeCryptKeySize;
|
|
smcVars[1] = (uintptr_t)(void *) &gIOHibernateVars.volumeCryptKey[0];
|
|
|
|
IOService::getPMRootDomain()->setProperty(kIOHibernateSMCVariablesKey, smcVars, sizeof(smcVars));
|
|
bzero(smcVars, sizeof(smcVars));
|
|
}
|
|
#endif
|
|
|
|
|
|
if (encryptedswap || vars->volumeCryptKeySize) {
|
|
gIOHibernateMode ^= kIOHibernateModeEncrypt;
|
|
}
|
|
|
|
if (kIOHibernateOptionProgress & gIOHibernateCurrentHeader->options) {
|
|
vars->videoAllocSize = kVideoMapSize;
|
|
if (KERN_SUCCESS != kmem_alloc_pageable(kernel_map, &vars->videoMapping, vars->videoAllocSize, VM_KERN_MEMORY_IOKIT)) {
|
|
vars->videoMapping = 0;
|
|
}
|
|
}
|
|
|
|
// generate crypt keys
|
|
for (uint32_t i = 0; i < sizeof(vars->wiredCryptKey); i++) {
|
|
vars->wiredCryptKey[i] = ((uint8_t) random());
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(vars->cryptKey); i++) {
|
|
vars->cryptKey[i] = ((uint8_t) random());
|
|
}
|
|
|
|
// set nvram
|
|
|
|
IOSetBootImageNVRAM(nvramData);
|
|
nvramData->release();
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
{
|
|
struct AppleRTCHibernateVars {
|
|
uint8_t signature[4];
|
|
uint32_t revision;
|
|
uint8_t booterSignature[20];
|
|
uint8_t wiredCryptKey[16];
|
|
};
|
|
AppleRTCHibernateVars rtcVars;
|
|
OSData * data;
|
|
|
|
rtcVars.signature[0] = 'A';
|
|
rtcVars.signature[1] = 'A';
|
|
rtcVars.signature[2] = 'P';
|
|
rtcVars.signature[3] = 'L';
|
|
rtcVars.revision = 1;
|
|
bcopy(&vars->wiredCryptKey[0], &rtcVars.wiredCryptKey[0], sizeof(rtcVars.wiredCryptKey));
|
|
|
|
if (gIOChosenEntry
|
|
&& (data = OSDynamicCast(OSData, gIOChosenEntry->getProperty(gIOHibernateBootSignatureKey)))
|
|
&& (sizeof(rtcVars.booterSignature) <= data->getLength())) {
|
|
bcopy(data->getBytesNoCopy(), &rtcVars.booterSignature[0], sizeof(rtcVars.booterSignature));
|
|
} else if (gIOHibernateBootSignature[0]) {
|
|
char c;
|
|
uint8_t value = 0;
|
|
uint32_t in, out, digits;
|
|
for (in = out = digits = 0;
|
|
(c = gIOHibernateBootSignature[in]) && (in < sizeof(gIOHibernateBootSignature));
|
|
in++) {
|
|
if ((c >= 'a') && (c <= 'f')) {
|
|
c -= 'a' - 10;
|
|
} else if ((c >= 'A') && (c <= 'F')) {
|
|
c -= 'A' - 10;
|
|
} else if ((c >= '0') && (c <= '9')) {
|
|
c -= '0';
|
|
} else {
|
|
if (c == '=') {
|
|
out = digits = value = 0;
|
|
}
|
|
continue;
|
|
}
|
|
value = ((uint8_t) ((value << 4) | c));
|
|
if (digits & 1) {
|
|
rtcVars.booterSignature[out++] = value;
|
|
if (out >= sizeof(rtcVars.booterSignature)) {
|
|
break;
|
|
}
|
|
}
|
|
digits++;
|
|
}
|
|
}
|
|
#if DEBUG || DEVELOPMENT
|
|
if (kIOLogHibernate & gIOKitDebug) {
|
|
IOKitKernelLogBuffer("H> rtc:",
|
|
&rtcVars, sizeof(rtcVars), &kprintf);
|
|
}
|
|
#endif /* DEBUG || DEVELOPMENT */
|
|
|
|
data = OSData::withBytes(&rtcVars, sizeof(rtcVars));
|
|
if (data) {
|
|
if (gIOHibernateRTCVariablesKey) {
|
|
IOService::getPMRootDomain()->setProperty(gIOHibernateRTCVariablesKey, data);
|
|
}
|
|
data->release();
|
|
}
|
|
if (gIOChosenEntry && gIOOptionsEntry) {
|
|
data = OSDynamicCast(OSData, gIOChosenEntry->getProperty(kIOHibernateMachineSignatureKey));
|
|
if (data) {
|
|
gIOHibernateCurrentHeader->machineSignature = *((UInt32 *)data->getBytesNoCopy());
|
|
}
|
|
// set BootNext
|
|
if (!gIOHibernateBoot0082Data) {
|
|
OSData * fileData = NULL;
|
|
data = OSDynamicCast(OSData, gIOChosenEntry->getProperty("boot-device-path"));
|
|
if (data && data->getLength() >= 4) {
|
|
fileData = OSDynamicCast(OSData, gIOChosenEntry->getProperty("boot-file-path"));
|
|
}
|
|
if (data && (data->getLength() <= UINT16_MAX)) {
|
|
// AppleNVRAM_EFI_LOAD_OPTION
|
|
struct {
|
|
uint32_t Attributes;
|
|
uint16_t FilePathLength;
|
|
uint16_t Desc;
|
|
} loadOptionHeader;
|
|
loadOptionHeader.Attributes = 1;
|
|
loadOptionHeader.FilePathLength = ((uint16_t) data->getLength());
|
|
loadOptionHeader.Desc = 0;
|
|
if (fileData) {
|
|
loadOptionHeader.FilePathLength -= 4;
|
|
loadOptionHeader.FilePathLength += fileData->getLength();
|
|
}
|
|
gIOHibernateBoot0082Data = OSData::withCapacity(sizeof(loadOptionHeader) + loadOptionHeader.FilePathLength);
|
|
if (gIOHibernateBoot0082Data) {
|
|
gIOHibernateBoot0082Data->appendBytes(&loadOptionHeader, sizeof(loadOptionHeader));
|
|
if (fileData) {
|
|
gIOHibernateBoot0082Data->appendBytes(data->getBytesNoCopy(), data->getLength() - 4);
|
|
gIOHibernateBoot0082Data->appendBytes(fileData);
|
|
} else {
|
|
gIOHibernateBoot0082Data->appendBytes(data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!gIOHibernateBootNextData) {
|
|
uint16_t bits = 0x0082;
|
|
gIOHibernateBootNextData = OSData::withBytes(&bits, sizeof(bits));
|
|
}
|
|
|
|
#if DEBUG || DEVELOPMENT
|
|
if (kIOLogHibernate & gIOKitDebug) {
|
|
IOKitKernelLogBuffer("H> bootnext:",
|
|
gIOHibernateBoot0082Data->getBytesNoCopy(), gIOHibernateBoot0082Data->getLength(), &kprintf);
|
|
}
|
|
#endif /* DEBUG || DEVELOPMENT */
|
|
if (gIOHibernateBoot0082Key && gIOHibernateBoot0082Data && gIOHibernateBootNextKey && gIOHibernateBootNextData) {
|
|
gIOHibernateBootNextSave = gIOOptionsEntry->copyProperty(gIOHibernateBootNextKey);
|
|
gIOOptionsEntry->setProperty(gIOHibernateBoot0082Key, gIOHibernateBoot0082Data);
|
|
gIOOptionsEntry->setProperty(gIOHibernateBootNextKey, gIOHibernateBootNextData);
|
|
}
|
|
// BootNext
|
|
}
|
|
}
|
|
#endif /* !i386 && !x86_64 */
|
|
}while (false);
|
|
|
|
if (swapPinned) {
|
|
hibernate_pin_swap(FALSE);
|
|
}
|
|
|
|
IOLockLock(gFSLock);
|
|
if ((kIOReturnSuccess == err) && (kFSOpening != gFSState)) {
|
|
HIBLOG("hibernate file close due timeout\n");
|
|
err = kIOReturnTimeout;
|
|
}
|
|
if (kIOReturnSuccess == err) {
|
|
gFSState = kFSOpened;
|
|
gIOHibernateVars = *vars;
|
|
gFileVars = *vars->fileVars;
|
|
gFileVars.allocated = false;
|
|
gIOHibernateVars.fileVars = &gFileVars;
|
|
gIOHibernateCurrentHeader->signature = kIOHibernateHeaderSignature;
|
|
gIOHibernateCurrentHeader->kernVirtSlide = vm_kernel_slide;
|
|
gIOHibernateState = kIOHibernateStateHibernating;
|
|
|
|
#if DEBUG || DEVELOPMENT
|
|
if (kIOLogHibernate & gIOKitDebug) {
|
|
OSData * data = OSDynamicCast(OSData, IOService::getPMRootDomain()->getProperty(kIOHibernateSMCVariablesKey));
|
|
if (data) {
|
|
uintptr_t * smcVars = (typeof(smcVars))data->getBytesNoCopy();
|
|
IOKitKernelLogBuffer("H> smc:",
|
|
(const void *)smcVars[1], smcVars[0], &kprintf);
|
|
}
|
|
}
|
|
#endif /* DEBUG || DEVELOPMENT */
|
|
} else {
|
|
IOPolledFileIOVars * fileVars = vars->fileVars;
|
|
IOHibernateDone(vars);
|
|
IOPolledFileClose(&fileVars,
|
|
#if DISABLE_TRIM
|
|
0, NULL, 0, 0, 0);
|
|
#else
|
|
0, NULL, 0, sizeof(IOHibernateImageHeader), setFileSize);
|
|
#endif
|
|
gFSState = kFSIdle;
|
|
}
|
|
IOLockUnlock(gFSLock);
|
|
|
|
if (vars->fileVars) {
|
|
IODelete(vars->fileVars, IOPolledFileIOVars, 1);
|
|
}
|
|
IODelete(vars, IOHibernateVars, 1);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
static void
|
|
IOSetBootImageNVRAM(OSData * data)
|
|
{
|
|
IORegistryEntry * regEntry;
|
|
|
|
if (!gIOOptionsEntry) {
|
|
regEntry = IORegistryEntry::fromPath("/options", gIODTPlane);
|
|
gIOOptionsEntry = OSDynamicCast(IODTNVRAM, regEntry);
|
|
if (regEntry && !gIOOptionsEntry) {
|
|
regEntry->release();
|
|
}
|
|
}
|
|
if (gIOOptionsEntry && gIOHibernateBootImageKey) {
|
|
if (data) {
|
|
gIOOptionsEntry->setProperty(gIOHibernateBootImageKey, data);
|
|
#if DEBUG || DEVELOPMENT
|
|
if (kIOLogHibernate & gIOKitDebug) {
|
|
IOKitKernelLogBuffer("H> boot-image:",
|
|
data->getBytesNoCopy(), data->getLength(), &kprintf);
|
|
}
|
|
#endif /* DEBUG || DEVELOPMENT */
|
|
} else {
|
|
gIOOptionsEntry->removeProperty(gIOHibernateBootImageKey);
|
|
#if __x86_64__
|
|
gIOOptionsEntry->sync();
|
|
#else
|
|
if (gIOHibernateState == kIOHibernateStateWakingFromHibernate) {
|
|
// if we woke from hibernation, the booter may have changed the state of NVRAM, so force a sync
|
|
gIOOptionsEntry->sync();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
/*
|
|
* Writes header to disk with signature, block size and file extents data.
|
|
* If there are more than 2 extents, then they are written on second block.
|
|
*/
|
|
static IOReturn
|
|
IOWriteExtentsToFile(IOPolledFileIOVars * vars, uint32_t signature)
|
|
{
|
|
IOHibernateImageHeader hdr;
|
|
IOItemCount count;
|
|
IOReturn err = kIOReturnSuccess;
|
|
int rc;
|
|
IOPolledFileExtent * fileExtents;
|
|
|
|
fileExtents = (typeof(fileExtents))vars->fileExtents->getBytesNoCopy();
|
|
|
|
memset(&hdr, 0, sizeof(IOHibernateImageHeader));
|
|
count = vars->fileExtents->getLength();
|
|
if (count > sizeof(hdr.fileExtentMap)) {
|
|
hdr.fileExtentMapSize = count;
|
|
count = sizeof(hdr.fileExtentMap);
|
|
} else {
|
|
hdr.fileExtentMapSize = sizeof(hdr.fileExtentMap);
|
|
}
|
|
|
|
bcopy(fileExtents, &hdr.fileExtentMap[0], count);
|
|
|
|
// copy file block extent list if larger than header
|
|
if (hdr.fileExtentMapSize > sizeof(hdr.fileExtentMap)) {
|
|
count = hdr.fileExtentMapSize - sizeof(hdr.fileExtentMap);
|
|
rc = kern_write_file(vars->fileRef, vars->blockSize,
|
|
(caddr_t)(((uint8_t *)fileExtents) + sizeof(hdr.fileExtentMap)),
|
|
count, IO_SKIP_ENCRYPTION);
|
|
if (rc != 0) {
|
|
HIBLOG("kern_write_file returned %d\n", rc);
|
|
err = kIOReturnIOError;
|
|
goto exit;
|
|
}
|
|
}
|
|
hdr.signature = signature;
|
|
hdr.deviceBlockSize = vars->blockSize;
|
|
|
|
rc = kern_write_file(vars->fileRef, 0, (char *)&hdr, sizeof(hdr), IO_SKIP_ENCRYPTION);
|
|
if (rc != 0) {
|
|
HIBLOG("kern_write_file returned %d\n", rc);
|
|
err = kIOReturnIOError;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
DECLARE_IOHIBERNATEPROGRESSALPHA
|
|
|
|
static void
|
|
ProgressInit(hibernate_graphics_t * display, uint8_t * screen, uint8_t * saveunder, uint32_t savelen)
|
|
{
|
|
uint32_t rowBytes, pixelShift;
|
|
uint32_t x, y;
|
|
int32_t blob;
|
|
uint32_t alpha, color, result;
|
|
uint8_t * out, in;
|
|
uint32_t saveindex[kIOHibernateProgressCount] = { 0 };
|
|
|
|
rowBytes = display->rowBytes;
|
|
pixelShift = display->depth >> 4;
|
|
if (pixelShift < 1) {
|
|
return;
|
|
}
|
|
|
|
screen += ((display->width
|
|
- kIOHibernateProgressCount * (kIOHibernateProgressWidth + kIOHibernateProgressSpacing)) << (pixelShift - 1))
|
|
+ (display->height - kIOHibernateProgressOriginY - kIOHibernateProgressHeight) * rowBytes;
|
|
|
|
for (y = 0; y < kIOHibernateProgressHeight; y++) {
|
|
out = screen + y * rowBytes;
|
|
for (blob = 0; blob < kIOHibernateProgressCount; blob++) {
|
|
color = blob ? kIOHibernateProgressDarkGray : kIOHibernateProgressMidGray;
|
|
for (x = 0; x < kIOHibernateProgressWidth; x++) {
|
|
alpha = gIOHibernateProgressAlpha[y][x];
|
|
result = color;
|
|
if (alpha) {
|
|
if (0xff != alpha) {
|
|
if (1 == pixelShift) {
|
|
in = *((uint16_t *)out) & 0x1f; // 16
|
|
in = ((uint8_t)(in << 3)) | ((uint8_t)(in >> 2));
|
|
} else {
|
|
in = *((uint32_t *)out) & 0xff; // 32
|
|
}
|
|
saveunder[blob * kIOHibernateProgressSaveUnderSize + saveindex[blob]++] = in;
|
|
result = ((255 - alpha) * in + alpha * result + 0xff) >> 8;
|
|
}
|
|
if (1 == pixelShift) {
|
|
result >>= 3;
|
|
*((uint16_t *)out) = ((uint16_t)((result << 10) | (result << 5) | result)); // 16
|
|
} else {
|
|
*((uint32_t *)out) = (result << 16) | (result << 8) | result; // 32
|
|
}
|
|
}
|
|
out += (1 << pixelShift);
|
|
}
|
|
out += (kIOHibernateProgressSpacing << pixelShift);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
ProgressUpdate(hibernate_graphics_t * display, uint8_t * screen, int32_t firstBlob, int32_t select)
|
|
{
|
|
uint32_t rowBytes, pixelShift;
|
|
uint32_t x, y;
|
|
int32_t blob, lastBlob;
|
|
uint32_t alpha, in, color, result;
|
|
uint8_t * out;
|
|
uint32_t saveindex[kIOHibernateProgressCount] = { 0 };
|
|
|
|
pixelShift = display->depth >> 4;
|
|
if (pixelShift < 1) {
|
|
return;
|
|
}
|
|
|
|
rowBytes = display->rowBytes;
|
|
|
|
screen += ((display->width
|
|
- kIOHibernateProgressCount * (kIOHibernateProgressWidth + kIOHibernateProgressSpacing)) << (pixelShift - 1))
|
|
+ (display->height - kIOHibernateProgressOriginY - kIOHibernateProgressHeight) * rowBytes;
|
|
|
|
lastBlob = (select < kIOHibernateProgressCount) ? select : (kIOHibernateProgressCount - 1);
|
|
|
|
screen += (firstBlob * (kIOHibernateProgressWidth + kIOHibernateProgressSpacing)) << pixelShift;
|
|
|
|
for (y = 0; y < kIOHibernateProgressHeight; y++) {
|
|
out = screen + y * rowBytes;
|
|
for (blob = firstBlob; blob <= lastBlob; blob++) {
|
|
color = (blob < select) ? kIOHibernateProgressLightGray : kIOHibernateProgressMidGray;
|
|
for (x = 0; x < kIOHibernateProgressWidth; x++) {
|
|
alpha = gIOHibernateProgressAlpha[y][x];
|
|
result = color;
|
|
if (alpha) {
|
|
if (0xff != alpha) {
|
|
in = display->progressSaveUnder[blob][saveindex[blob]++];
|
|
result = ((255 - alpha) * in + alpha * result + 0xff) / 255;
|
|
}
|
|
if (1 == pixelShift) {
|
|
result >>= 3;
|
|
*((uint16_t *)out) = ((uint16_t)((result << 10) | (result << 5) | result)); // 16
|
|
} else {
|
|
*((uint32_t *)out) = (result << 16) | (result << 8) | result; // 32
|
|
}
|
|
}
|
|
out += (1 << pixelShift);
|
|
}
|
|
out += (kIOHibernateProgressSpacing << pixelShift);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
IOReturn
|
|
IOHibernateIOKitSleep(void)
|
|
{
|
|
IOReturn ret = kIOReturnSuccess;
|
|
IOLockLock(gFSLock);
|
|
if (kFSOpening == gFSState) {
|
|
gFSState = kFSTimedOut;
|
|
HIBLOG("hibernate file open timed out\n");
|
|
ret = kIOReturnTimeout;
|
|
}
|
|
IOLockUnlock(gFSLock);
|
|
return ret;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
IOReturn
|
|
IOHibernateSystemHasSlept(void)
|
|
{
|
|
IOReturn ret = kIOReturnSuccess;
|
|
IOHibernateVars * vars = &gIOHibernateVars;
|
|
OSObject * obj = NULL;
|
|
OSData * data;
|
|
|
|
IOLockLock(gFSLock);
|
|
if ((kFSOpened != gFSState) && gIOHibernateMode) {
|
|
ret = kIOReturnTimeout;
|
|
}
|
|
IOLockUnlock(gFSLock);
|
|
if (kIOReturnSuccess != ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (gIOHibernateMode) {
|
|
obj = IOService::getPMRootDomain()->copyProperty(kIOHibernatePreviewBufferKey);
|
|
}
|
|
vars->previewBuffer = OSDynamicCast(IOMemoryDescriptor, obj);
|
|
if (obj && !vars->previewBuffer) {
|
|
obj->release();
|
|
}
|
|
if (vars->previewBuffer && (vars->previewBuffer->getLength() > UINT_MAX)) {
|
|
OSSafeReleaseNULL(vars->previewBuffer);
|
|
}
|
|
|
|
vars->consoleMapping = NULL;
|
|
if (vars->previewBuffer && (kIOReturnSuccess != vars->previewBuffer->prepare())) {
|
|
vars->previewBuffer->release();
|
|
vars->previewBuffer = NULL;
|
|
}
|
|
|
|
if ((kIOHibernateOptionProgress & gIOHibernateCurrentHeader->options)
|
|
&& vars->previewBuffer
|
|
&& (data = OSDynamicCast(OSData,
|
|
IOService::getPMRootDomain()->getProperty(kIOHibernatePreviewActiveKey)))) {
|
|
UInt32 flags = *((UInt32 *)data->getBytesNoCopy());
|
|
HIBPRINT("kIOHibernatePreviewActiveKey %08lx\n", (long)flags);
|
|
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernatePreviewActiveKey);
|
|
|
|
if (kIOHibernatePreviewUpdates & flags) {
|
|
PE_Video consoleInfo;
|
|
hibernate_graphics_t * graphicsInfo = gIOHibernateGraphicsInfo;
|
|
|
|
IOService::getPlatform()->getConsoleInfo(&consoleInfo);
|
|
|
|
graphicsInfo->width = (uint32_t) consoleInfo.v_width;
|
|
graphicsInfo->height = (uint32_t) consoleInfo.v_height;
|
|
graphicsInfo->rowBytes = (uint32_t) consoleInfo.v_rowBytes;
|
|
graphicsInfo->depth = (uint32_t) consoleInfo.v_depth;
|
|
vars->consoleMapping = (uint8_t *) consoleInfo.v_baseAddr;
|
|
|
|
HIBPRINT("video %p %d %d %d\n",
|
|
vars->consoleMapping, graphicsInfo->depth,
|
|
graphicsInfo->width, graphicsInfo->height);
|
|
if (vars->consoleMapping) {
|
|
ProgressInit(graphicsInfo, vars->consoleMapping,
|
|
&graphicsInfo->progressSaveUnder[0][0], sizeof(graphicsInfo->progressSaveUnder));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gIOOptionsEntry) {
|
|
#if __x86_64__
|
|
gIOOptionsEntry->sync();
|
|
#else
|
|
if (gIOHibernateMode) {
|
|
gIOOptionsEntry->sync();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
static const DeviceTreeNode *
|
|
MergeDeviceTree(const DeviceTreeNode * entry, IORegistryEntry * regEntry, OSSet * entriesToUpdate, vm_offset_t region_start, vm_size_t region_size)
|
|
{
|
|
DeviceTreeNodeProperty * prop;
|
|
const DeviceTreeNode * child;
|
|
IORegistryEntry * childRegEntry;
|
|
const char * nameProp;
|
|
unsigned int propLen, idx;
|
|
|
|
bool updateEntry = true;
|
|
if (!regEntry) {
|
|
updateEntry = false;
|
|
} else if (entriesToUpdate && !entriesToUpdate->containsObject(regEntry)) {
|
|
updateEntry = false;
|
|
}
|
|
|
|
prop = (DeviceTreeNodeProperty *) (entry + 1);
|
|
for (idx = 0; idx < entry->nProperties; idx++) {
|
|
if (updateEntry && (0 != strcmp("name", prop->name))) {
|
|
regEntry->setProperty((const char *) prop->name, (void *) (prop + 1), prop->length);
|
|
// HIBPRINT("%s: %s, %d\n", regEntry->getName(), prop->name, prop->length);
|
|
}
|
|
prop = (DeviceTreeNodeProperty *) (((uintptr_t)(prop + 1)) + ((prop->length + 3) & ~3));
|
|
}
|
|
|
|
if (entriesToUpdate) {
|
|
entriesToUpdate->removeObject(regEntry);
|
|
if (entriesToUpdate->getCount() == 0) {
|
|
// we've updated all the entries we care about so we can stop
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
child = (const DeviceTreeNode *) prop;
|
|
for (idx = 0; idx < entry->nChildren; idx++) {
|
|
if (kSuccess != SecureDTGetPropertyRegion(child, "name", (void const **) &nameProp, &propLen,
|
|
region_start, region_size)) {
|
|
panic("no name");
|
|
}
|
|
childRegEntry = regEntry ? regEntry->childFromPath(nameProp, gIODTPlane) : NULL;
|
|
// HIBPRINT("%s == %p\n", nameProp, childRegEntry);
|
|
child = MergeDeviceTree(child, childRegEntry, entriesToUpdate, region_start, region_size);
|
|
if (!child) {
|
|
// the recursive call updated the last entry we cared about, so we can stop
|
|
break;
|
|
}
|
|
}
|
|
return child;
|
|
}
|
|
|
|
IOReturn
|
|
IOHibernateSystemWake(void)
|
|
{
|
|
if (kFSOpened == gFSState) {
|
|
IOPolledFilePollersClose(gIOHibernateVars.fileVars, kIOPolledPostflightState);
|
|
IOHibernateDone(&gIOHibernateVars);
|
|
} else {
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernateOptionsKey);
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernateGfxStatusKey);
|
|
}
|
|
|
|
if (gIOOptionsEntry && gIOHibernateBootImageKey) {
|
|
// if we got this far, clear boot-image
|
|
// we don't need to sync immediately; the booter should have already removed this entry
|
|
// we just want to make sure that if anyone syncs nvram after this point, we don't re-write
|
|
// a stale boot-image value
|
|
gIOOptionsEntry->removeProperty(gIOHibernateBootImageKey);
|
|
}
|
|
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
static IOReturn
|
|
IOHibernateDone(IOHibernateVars * vars)
|
|
{
|
|
IOReturn err;
|
|
OSData * data;
|
|
|
|
hibernate_teardown(vars->page_list, vars->page_list_wired, vars->page_list_pal);
|
|
|
|
if (vars->videoMapping) {
|
|
if (vars->videoMapSize) {
|
|
// remove mappings
|
|
IOUnmapPages(kernel_map, vars->videoMapping, vars->videoMapSize);
|
|
}
|
|
if (vars->videoAllocSize) {
|
|
// dealloc range
|
|
kmem_free(kernel_map, trunc_page(vars->videoMapping), vars->videoAllocSize);
|
|
}
|
|
}
|
|
|
|
if (vars->previewBuffer) {
|
|
vars->previewBuffer->release();
|
|
vars->previewBuffer = NULL;
|
|
}
|
|
|
|
if (kIOHibernateStateWakingFromHibernate == gIOHibernateState) {
|
|
IOService::getPMRootDomain()->setProperty(kIOHibernateOptionsKey,
|
|
gIOHibernateCurrentHeader->options, 32);
|
|
} else {
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernateOptionsKey);
|
|
}
|
|
|
|
if ((kIOHibernateStateWakingFromHibernate == gIOHibernateState)
|
|
&& (kIOHibernateGfxStatusUnknown != gIOHibernateGraphicsInfo->gfxStatus)) {
|
|
IOService::getPMRootDomain()->setProperty(kIOHibernateGfxStatusKey,
|
|
&gIOHibernateGraphicsInfo->gfxStatus,
|
|
sizeof(gIOHibernateGraphicsInfo->gfxStatus));
|
|
} else {
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernateGfxStatusKey);
|
|
}
|
|
|
|
// invalidate nvram properties - (gIOOptionsEntry != 0) => nvram was touched
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
IOService::getPMRootDomain()->removeProperty(gIOHibernateRTCVariablesKey);
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernateSMCVariablesKey);
|
|
|
|
/*
|
|
* Hibernate variable is written to NVRAM on platforms in which RtcRam
|
|
* is not backed by coin cell. Remove Hibernate data from NVRAM.
|
|
*/
|
|
if (gIOOptionsEntry) {
|
|
if (gIOHibernateRTCVariablesKey) {
|
|
if (gIOOptionsEntry->getProperty(gIOHibernateRTCVariablesKey)) {
|
|
gIOOptionsEntry->removeProperty(gIOHibernateRTCVariablesKey);
|
|
}
|
|
}
|
|
|
|
if (gIOHibernateBootNextKey) {
|
|
if (gIOHibernateBootNextSave) {
|
|
gIOOptionsEntry->setProperty(gIOHibernateBootNextKey, gIOHibernateBootNextSave);
|
|
gIOHibernateBootNextSave->release();
|
|
gIOHibernateBootNextSave = NULL;
|
|
} else {
|
|
gIOOptionsEntry->removeProperty(gIOHibernateBootNextKey);
|
|
}
|
|
}
|
|
if (kIOHibernateStateWakingFromHibernate != gIOHibernateState) {
|
|
gIOOptionsEntry->sync();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (vars->srcBuffer) {
|
|
vars->srcBuffer->release();
|
|
}
|
|
|
|
|
|
bzero(&gIOHibernateHandoffPages[0], gIOHibernateHandoffPageCount * sizeof(gIOHibernateHandoffPages[0]));
|
|
if (vars->handoffBuffer) {
|
|
if (kIOHibernateStateWakingFromHibernate == gIOHibernateState) {
|
|
IOHibernateHandoff * handoff;
|
|
bool done = false;
|
|
for (handoff = (IOHibernateHandoff *) vars->handoffBuffer->getBytesNoCopy();
|
|
!done;
|
|
handoff = (IOHibernateHandoff *) &handoff->data[handoff->bytecount]) {
|
|
HIBPRINT("handoff %p, %x, %x\n", handoff, handoff->type, handoff->bytecount);
|
|
uint8_t * __unused data = &handoff->data[0];
|
|
switch (handoff->type) {
|
|
case kIOHibernateHandoffTypeEnd:
|
|
done = true;
|
|
break;
|
|
|
|
case kIOHibernateHandoffTypeDeviceTree:
|
|
{
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
// On Intel, process the entirety of the passed in device tree
|
|
OSSet * entriesToUpdate = NULL;
|
|
#elif defined(__arm64__)
|
|
// On ARM, only allow hibernation to update specific entries
|
|
const char *mergePaths[] = {
|
|
kIODeviceTreePlane ":/chosen/boot-object-manifests",
|
|
kIODeviceTreePlane ":/chosen/secure-boot-hashes",
|
|
};
|
|
const size_t mergePathCount = sizeof(mergePaths) / sizeof(mergePaths[0]);
|
|
OSSet * entriesToUpdate = OSSet::withCapacity(mergePathCount);
|
|
for (size_t i = 0; i < mergePathCount; i++) {
|
|
IORegistryEntry *entry = IORegistryEntry::fromPath(mergePaths[i]);
|
|
if (!entry) {
|
|
panic("failed to find %s in IORegistry", mergePaths[i]);
|
|
}
|
|
entriesToUpdate->setObject(entry);
|
|
OSSafeReleaseNULL(entry);
|
|
}
|
|
#endif
|
|
MergeDeviceTree((DeviceTreeNode *) data, IOService::getServiceRoot(), entriesToUpdate,
|
|
(vm_offset_t)data, (vm_size_t)handoff->bytecount);
|
|
OSSafeReleaseNULL(entriesToUpdate);
|
|
break;
|
|
}
|
|
|
|
case kIOHibernateHandoffTypeKeyStore:
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
{
|
|
IOBufferMemoryDescriptor *
|
|
md = IOBufferMemoryDescriptor::withBytes(data, handoff->bytecount, kIODirectionOutIn);
|
|
if (md) {
|
|
IOSetKeyStoreData(md);
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
done = (kIOHibernateHandoffType != (handoff->type & 0xFFFF0000));
|
|
break;
|
|
}
|
|
}
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
if (vars->volumeCryptKeySize) {
|
|
IOBufferMemoryDescriptor *
|
|
bmd = IOBufferMemoryDescriptor::withBytes(&vars->volumeCryptKey[0],
|
|
vars->volumeCryptKeySize, kIODirectionOutIn);
|
|
if (!bmd) {
|
|
panic("IOBufferMemoryDescriptor");
|
|
}
|
|
IOSetAPFSKeyStoreData(bmd);
|
|
bzero(&vars->volumeCryptKey[0], sizeof(vars->volumeCryptKey));
|
|
}
|
|
#endif
|
|
}
|
|
vars->handoffBuffer->release();
|
|
}
|
|
|
|
if (gIOChosenEntry
|
|
&& (data = OSDynamicCast(OSData, gIOChosenEntry->getProperty(gIOBridgeBootSessionUUIDKey)))
|
|
&& (sizeof(gIOHibernateBridgeBootSessionUUIDString) <= data->getLength())) {
|
|
bcopy(data->getBytesNoCopy(), &gIOHibernateBridgeBootSessionUUIDString[0],
|
|
sizeof(gIOHibernateBridgeBootSessionUUIDString));
|
|
}
|
|
|
|
if (vars->hwEncrypt) {
|
|
err = IOPolledFilePollersSetEncryptionKey(vars->fileVars, NULL, 0);
|
|
HIBLOG("IOPolledFilePollersSetEncryptionKey(0,%x)\n", err);
|
|
}
|
|
|
|
bzero(vars, sizeof(*vars));
|
|
|
|
// gIOHibernateState = kIOHibernateStateInactive; // leave it for post wake code to see
|
|
gIOHibernateCount++;
|
|
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
static void
|
|
IOHibernateSystemPostWakeTrim(void * p1, void * p2)
|
|
{
|
|
// invalidate & close the image file
|
|
if (p1) {
|
|
IOLockLock(gFSLock);
|
|
}
|
|
if (kFSTrimDelay == gFSState) {
|
|
IOPolledFileIOVars * vars = &gFileVars;
|
|
IOPolledFileClose(&vars,
|
|
#if DISABLE_TRIM
|
|
0, NULL, 0, 0, 0);
|
|
#else
|
|
0, (caddr_t)gIOHibernateCurrentHeader, sizeof(IOHibernateImageHeader),
|
|
sizeof(IOHibernateImageHeader), gIOHibernateCurrentHeader->imageSize);
|
|
#endif
|
|
gFSState = kFSIdle;
|
|
}
|
|
if (p1) {
|
|
IOLockUnlock(gFSLock);
|
|
}
|
|
}
|
|
|
|
IOReturn
|
|
IOHibernateSystemPostWake(bool now)
|
|
{
|
|
gIOHibernateCurrentHeader->signature = kIOHibernateHeaderInvalidSignature;
|
|
IOSetBootImageNVRAM(NULL);
|
|
|
|
IOLockLock(gFSLock);
|
|
if (kFSTrimDelay == gFSState) {
|
|
thread_call_cancel(gIOHibernateTrimCalloutEntry);
|
|
IOHibernateSystemPostWakeTrim(NULL, NULL);
|
|
} else if (kFSOpened != gFSState) {
|
|
gFSState = kFSIdle;
|
|
} else {
|
|
gFSState = kFSTrimDelay;
|
|
if (now) {
|
|
thread_call_cancel(gIOHibernateTrimCalloutEntry);
|
|
IOHibernateSystemPostWakeTrim(NULL, NULL);
|
|
} else {
|
|
AbsoluteTime deadline;
|
|
clock_interval_to_deadline(TRIM_DELAY, kMillisecondScale, &deadline );
|
|
thread_call_enter1_delayed(gIOHibernateTrimCalloutEntry, NULL, deadline);
|
|
}
|
|
}
|
|
IOLockUnlock(gFSLock);
|
|
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
uint32_t
|
|
IOHibernateWasScreenLocked(void)
|
|
{
|
|
uint32_t ret = 0;
|
|
if ((kIOHibernateStateWakingFromHibernate == gIOHibernateState) && gIOChosenEntry) {
|
|
OSData *
|
|
data = OSDynamicCast(OSData, gIOChosenEntry->getProperty(kIOScreenLockStateKey));
|
|
if (data) {
|
|
ret = ((uint32_t *)data->getBytesNoCopy())[0];
|
|
gIOChosenEntry->setProperty(kIOBooterScreenLockStateKey, data);
|
|
}
|
|
} else {
|
|
gIOChosenEntry->removeProperty(kIOBooterScreenLockStateKey);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
SYSCTL_STRING(_kern, OID_AUTO, hibernatefile,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_LOCKED,
|
|
gIOHibernateFilename, sizeof(gIOHibernateFilename), "");
|
|
SYSCTL_STRING(_kern, OID_AUTO, bootsignature,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_LOCKED,
|
|
gIOHibernateBootSignature, sizeof(gIOHibernateBootSignature), "");
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernatemode,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_LOCKED,
|
|
&gIOHibernateMode, 0, "");
|
|
SYSCTL_STRUCT(_kern, OID_AUTO, hibernatestatistics,
|
|
CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_KERN | CTLFLAG_LOCKED,
|
|
&_hibernateStats, hibernate_statistics_t, "");
|
|
SYSCTL_OID_MANUAL(_kern_bridge, OID_AUTO, bootsessionuuid,
|
|
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED,
|
|
gIOHibernateBridgeBootSessionUUIDString, sizeof(gIOHibernateBridgeBootSessionUUIDString),
|
|
sysctl_handle_string, "A", "");
|
|
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernategraphicsready,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_ANYBODY,
|
|
&_hibernateStats.graphicsReadyTime, 0, "");
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernatewakenotification,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_ANYBODY,
|
|
&_hibernateStats.wakeNotificationTime, 0, "");
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernatelockscreenready,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_ANYBODY,
|
|
&_hibernateStats.lockScreenReadyTime, 0, "");
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernatehidready,
|
|
CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_ANYBODY,
|
|
&_hibernateStats.hidReadyTime, 0, "");
|
|
|
|
SYSCTL_UINT(_kern, OID_AUTO, hibernatecount,
|
|
CTLFLAG_RD | CTLFLAG_KERN | CTLFLAG_ANYBODY,
|
|
&gIOHibernateCount, 0, "");
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
static int
|
|
hibernate_set_preview SYSCTL_HANDLER_ARGS
|
|
{
|
|
#pragma unused(oidp, arg1, arg2)
|
|
|
|
if (!IOTaskHasEntitlement(current_task(), kIOHibernateSetPreviewEntitlementKey)) {
|
|
return EPERM;
|
|
}
|
|
|
|
if ((req->newptr == USER_ADDR_NULL) || (!req->newlen)) {
|
|
IOService::getPMRootDomain()->removeProperty(kIOHibernatePreviewBufferKey);
|
|
return 0;
|
|
}
|
|
|
|
size_t rounded_size = round_page(req->newlen);
|
|
IOBufferMemoryDescriptor *md = IOBufferMemoryDescriptor::withOptions(kIODirectionOutIn, rounded_size, page_size);
|
|
if (!md) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
uint8_t *bytes = (uint8_t *)md->getBytesNoCopy();
|
|
int error = SYSCTL_IN(req, bytes, req->newlen);
|
|
if (error) {
|
|
md->release();
|
|
return error;
|
|
}
|
|
|
|
IOService::getPMRootDomain()->setProperty(kIOHibernatePreviewBufferKey, md);
|
|
md->release();
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYSCTL_PROC(_kern, OID_AUTO, hibernatepreview,
|
|
CTLTYPE_OPAQUE | CTLFLAG_WR | CTLFLAG_LOCKED | CTLFLAG_ANYBODY, NULL, 0,
|
|
hibernate_set_preview, "S", "");
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
void
|
|
IOHibernateSystemInit(IOPMrootDomain * rootDomain)
|
|
{
|
|
gIOHibernateBootImageKey = OSSymbol::withCStringNoCopy(kIOHibernateBootImageKey);
|
|
gIOHibernateBootSignatureKey = OSSymbol::withCStringNoCopy(kIOHibernateBootSignatureKey);
|
|
gIOBridgeBootSessionUUIDKey = OSSymbol::withCStringNoCopy(kIOBridgeBootSessionUUIDKey);
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
gIOHibernateRTCVariablesKey = OSSymbol::withCStringNoCopy(kIOHibernateRTCVariablesKey);
|
|
gIOHibernateBoot0082Key = OSSymbol::withCString("8BE4DF61-93CA-11D2-AA0D-00E098032B8C:Boot0082");
|
|
gIOHibernateBootNextKey = OSSymbol::withCString("8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootNext");
|
|
gIOHibernateRTCVariablesKey = OSSymbol::withCStringNoCopy(kIOHibernateRTCVariablesKey);
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
|
|
OSData * data = OSData::withBytesNoCopy(&gIOHibernateState, sizeof(gIOHibernateState));
|
|
if (data) {
|
|
rootDomain->setProperty(kIOHibernateStateKey, data);
|
|
data->release();
|
|
}
|
|
|
|
if (PE_parse_boot_argn("hfile", gIOHibernateFilename, sizeof(gIOHibernateFilename))) {
|
|
gIOHibernateMode = kIOHibernateModeOn;
|
|
} else {
|
|
gIOHibernateFilename[0] = 0;
|
|
}
|
|
|
|
gIOChosenEntry = IORegistryEntry::fromPath("/chosen", gIODTPlane);
|
|
|
|
if (gIOChosenEntry
|
|
&& (data = OSDynamicCast(OSData, gIOChosenEntry->getProperty(gIOBridgeBootSessionUUIDKey)))
|
|
&& (sizeof(gIOHibernateBridgeBootSessionUUIDString) <= data->getLength())) {
|
|
sysctl_register_oid(&sysctl__kern_bridge_bootsessionuuid);
|
|
bcopy(data->getBytesNoCopy(), &gIOHibernateBridgeBootSessionUUIDString[0], sizeof(gIOHibernateBridgeBootSessionUUIDString));
|
|
}
|
|
|
|
gFSLock = IOLockAlloc();
|
|
gIOHibernateCount = 0;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
static IOReturn
|
|
IOHibernatePolledFileWrite(IOHibernateVars * vars,
|
|
const uint8_t * bytes, IOByteCount size,
|
|
IOPolledFileCryptVars * cryptvars)
|
|
{
|
|
IOReturn err;
|
|
|
|
|
|
err = IOPolledFileWrite(vars->fileVars, bytes, size, cryptvars);
|
|
if ((kIOReturnSuccess == err) && hibernate_should_abort()) {
|
|
err = kIOReturnAborted;
|
|
}
|
|
|
|
|
|
return err;
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
extern "C" uint32_t
|
|
hibernate_write_image(void)
|
|
{
|
|
IOHibernateImageHeader * header = gIOHibernateCurrentHeader;
|
|
IOHibernateVars * vars = &gIOHibernateVars;
|
|
IOPolledFileExtent * fileExtents;
|
|
|
|
#if !defined(__arm64__)
|
|
_static_assert_1_arg(sizeof(IOHibernateImageHeader) == 512);
|
|
#endif /* !defined(__arm64__) */
|
|
|
|
uint32_t pageCount, pagesDone;
|
|
IOReturn err;
|
|
ppnum_t ppnum, page;
|
|
vm_offset_t count;
|
|
uint8_t * src;
|
|
uint8_t * data;
|
|
uint8_t * compressed;
|
|
uint8_t * scratch;
|
|
IOByteCount pageCompressedSize;
|
|
uint64_t compressedSize, uncompressedSize;
|
|
uint64_t image1Size = 0;
|
|
uint32_t bitmap_size;
|
|
bool iterDone, pollerOpen, needEncrypt;
|
|
int wkresult;
|
|
uint32_t tag;
|
|
uint32_t pageType;
|
|
uint32_t pageAndCount[2];
|
|
addr64_t phys64;
|
|
IOByteCount segLen;
|
|
uint32_t restore1Sum = 0, sum = 0, sum1 = 0, sum2 = 0;
|
|
uintptr_t hibernateBase;
|
|
uintptr_t hibernateEnd;
|
|
|
|
AbsoluteTime startTime, endTime;
|
|
AbsoluteTime allTime, compTime;
|
|
uint64_t compBytes;
|
|
uint64_t nsec;
|
|
uint64_t lastProgressStamp = 0;
|
|
uint64_t progressStamp;
|
|
uint32_t blob, lastBlob = (uint32_t) -1L;
|
|
|
|
uint32_t wiredPagesEncrypted;
|
|
uint32_t dirtyPagesEncrypted;
|
|
uint32_t wiredPagesClear;
|
|
uint32_t svPageCount;
|
|
uint32_t zvPageCount;
|
|
|
|
IOPolledFileCryptVars _cryptvars;
|
|
IOPolledFileCryptVars * cryptvars = NULL;
|
|
|
|
wiredPagesEncrypted = 0;
|
|
dirtyPagesEncrypted = 0;
|
|
wiredPagesClear = 0;
|
|
svPageCount = 0;
|
|
zvPageCount = 0;
|
|
|
|
if (!vars->fileVars
|
|
|| !vars->fileVars->pollers
|
|
|| !(kIOHibernateModeOn & gIOHibernateMode)) {
|
|
return kIOHibernatePostWriteSleep;
|
|
}
|
|
|
|
|
|
#if !defined(__arm64__)
|
|
if (kIOHibernateModeSleep & gIOHibernateMode) {
|
|
kdebug_enable = save_kdebug_enable;
|
|
}
|
|
#endif /* !defined(__arm64__) */
|
|
|
|
pal_hib_write_hook();
|
|
|
|
KDBG(IOKDBG_CODE(DBG_HIBERNATE, 1) | DBG_FUNC_START);
|
|
IOService::getPMRootDomain()->tracePoint(kIOPMTracePointHibernate);
|
|
|
|
#if CRYPTO
|
|
// encryption data. "iv" is the "initial vector".
|
|
if (kIOHibernateModeEncrypt & gIOHibernateMode) {
|
|
static const unsigned char first_iv[AES_BLOCK_SIZE]
|
|
= { 0xa3, 0x63, 0x65, 0xa9, 0x0b, 0x71, 0x7b, 0x1c,
|
|
0xdf, 0x9e, 0x5f, 0x32, 0xd7, 0x61, 0x63, 0xda };
|
|
|
|
cryptvars = &gIOHibernateCryptWakeContext;
|
|
bzero(cryptvars, sizeof(IOPolledFileCryptVars));
|
|
aes_encrypt_key(vars->cryptKey,
|
|
kIOHibernateAESKeySize,
|
|
&cryptvars->ctx.encrypt);
|
|
aes_decrypt_key(vars->cryptKey,
|
|
kIOHibernateAESKeySize,
|
|
&cryptvars->ctx.decrypt);
|
|
|
|
cryptvars = &_cryptvars;
|
|
bzero(cryptvars, sizeof(IOPolledFileCryptVars));
|
|
for (pageCount = 0; pageCount < sizeof(vars->wiredCryptKey); pageCount++) {
|
|
vars->wiredCryptKey[pageCount] ^= vars->volumeCryptKey[pageCount];
|
|
}
|
|
aes_encrypt_key(vars->wiredCryptKey,
|
|
kIOHibernateAESKeySize,
|
|
&cryptvars->ctx.encrypt);
|
|
|
|
bcopy(&first_iv[0], &cryptvars->aes_iv[0], AES_BLOCK_SIZE);
|
|
bzero(&vars->wiredCryptKey[0], sizeof(vars->wiredCryptKey));
|
|
bzero(&vars->cryptKey[0], sizeof(vars->cryptKey));
|
|
}
|
|
#endif /* CRYPTO */
|
|
|
|
hibernate_page_list_setall(vars->page_list,
|
|
vars->page_list_wired,
|
|
vars->page_list_pal,
|
|
false /* !preflight */,
|
|
/* discard_all */
|
|
((0 == (kIOHibernateModeSleep & gIOHibernateMode))
|
|
&& (0 != ((kIOHibernateModeDiscardCleanActive | kIOHibernateModeDiscardCleanInactive) & gIOHibernateMode))),
|
|
&pageCount);
|
|
|
|
HIBLOG("hibernate_page_list_setall found pageCount %d\n", pageCount);
|
|
|
|
fileExtents = (IOPolledFileExtent *) vars->fileVars->fileExtents->getBytesNoCopy();
|
|
|
|
#if 0
|
|
count = vars->fileExtents->getLength() / sizeof(IOPolledFileExtent);
|
|
for (page = 0; page < count; page++) {
|
|
HIBLOG("fileExtents[%d] %qx, %qx (%qx)\n", page,
|
|
fileExtents[page].start, fileExtents[page].length,
|
|
fileExtents[page].start + fileExtents[page].length);
|
|
}
|
|
#endif
|
|
|
|
needEncrypt = (0 != (kIOHibernateModeEncrypt & gIOHibernateMode));
|
|
AbsoluteTime_to_scalar(&compTime) = 0;
|
|
compBytes = 0;
|
|
|
|
clock_get_uptime(&allTime);
|
|
IOService::getPMRootDomain()->pmStatsRecordEvent(
|
|
kIOPMStatsHibernateImageWrite | kIOPMStatsEventStartFlag, allTime);
|
|
do{
|
|
compressedSize = 0;
|
|
uncompressedSize = 0;
|
|
svPageCount = 0;
|
|
zvPageCount = 0;
|
|
|
|
IOPolledFileSeek(vars->fileVars, vars->fileVars->blockSize);
|
|
|
|
HIBLOG("IOHibernatePollerOpen, ml_get_interrupts_enabled %d\n",
|
|
ml_get_interrupts_enabled());
|
|
err = IOPolledFilePollersOpen(vars->fileVars, kIOPolledBeforeSleepState,
|
|
// abortable if not low battery
|
|
!IOService::getPMRootDomain()->mustHibernate());
|
|
HIBLOG("IOHibernatePollerOpen(%x)\n", err);
|
|
pollerOpen = (kIOReturnSuccess == err);
|
|
if (!pollerOpen) {
|
|
break;
|
|
}
|
|
|
|
if (vars->volumeCryptKeySize) {
|
|
err = IOPolledFilePollersSetEncryptionKey(vars->fileVars, &vars->volumeCryptKey[0], vars->volumeCryptKeySize);
|
|
HIBLOG("IOPolledFilePollersSetEncryptionKey(%x)\n", err);
|
|
vars->hwEncrypt = (kIOReturnSuccess == err);
|
|
bzero(&vars->volumeCryptKey[0], sizeof(vars->volumeCryptKey));
|
|
if (vars->hwEncrypt) {
|
|
header->options |= kIOHibernateOptionHWEncrypt;
|
|
}
|
|
}
|
|
|
|
// copy file block extent list if larger than header
|
|
|
|
count = vars->fileVars->fileExtents->getLength();
|
|
if (count > sizeof(header->fileExtentMap)) {
|
|
count -= sizeof(header->fileExtentMap);
|
|
err = IOHibernatePolledFileWrite(vars,
|
|
((uint8_t *) &fileExtents[0]) + sizeof(header->fileExtentMap), count, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// copy out restore1 code
|
|
|
|
for (count = 0;
|
|
(phys64 = vars->handoffBuffer->getPhysicalSegment(count, &segLen, kIOMemoryMapperNone));
|
|
count += segLen) {
|
|
for (pagesDone = 0; pagesDone < atop_32(segLen); pagesDone++) {
|
|
gIOHibernateHandoffPages[atop_32(count) + pagesDone] = atop_64_ppnum(phys64) + pagesDone;
|
|
}
|
|
}
|
|
|
|
hibernateBase = HIB_BASE; /* Defined in PAL headers */
|
|
hibernateEnd = (segHIBB + segSizeHIB);
|
|
|
|
page = atop_32(kvtophys(hibernateBase));
|
|
count = atop_32(round_page(hibernateEnd) - hibernateBase);
|
|
uintptr_t entrypoint = ((uintptr_t) &hibernate_machine_entrypoint) - hibernateBase;
|
|
uintptr_t stack = ((uintptr_t) &gIOHibernateRestoreStackEnd[0]) - 64 - hibernateBase;
|
|
if ((count > UINT_MAX) || (entrypoint > UINT_MAX) || (stack > UINT_MAX)) {
|
|
panic("malformed kernel layout");
|
|
}
|
|
header->restore1CodePhysPage = (ppnum_t) page;
|
|
header->restore1CodeVirt = hibernateBase;
|
|
header->restore1PageCount = (uint32_t) count;
|
|
header->restore1CodeOffset = (uint32_t) entrypoint;
|
|
header->restore1StackOffset = (uint32_t) stack;
|
|
|
|
if (uuid_parse(&gIOHibernateBridgeBootSessionUUIDString[0], &header->bridgeBootSessionUUID[0])) {
|
|
bzero(&header->bridgeBootSessionUUID[0], sizeof(header->bridgeBootSessionUUID));
|
|
}
|
|
|
|
// sum __HIB seg, with zeros for the stack
|
|
src = (uint8_t *) trunc_page(hibernateBase);
|
|
for (page = 0; page < count; page++) {
|
|
if ((src < &gIOHibernateRestoreStack[0]) || (src >= &gIOHibernateRestoreStackEnd[0])) {
|
|
restore1Sum += hibernate_sum_page(src, (uint32_t) (header->restore1CodeVirt + page));
|
|
} else {
|
|
restore1Sum += 0x00000000;
|
|
}
|
|
src += page_size;
|
|
}
|
|
sum1 = restore1Sum;
|
|
|
|
// write the __HIB seg, with zeros for the stack
|
|
|
|
src = (uint8_t *) trunc_page(hibernateBase);
|
|
count = ((uintptr_t) &gIOHibernateRestoreStack[0]) - trunc_page(hibernateBase);
|
|
if (count) {
|
|
err = IOHibernatePolledFileWrite(vars, src, count, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
err = IOHibernatePolledFileWrite(vars,
|
|
(uint8_t *) NULL,
|
|
&gIOHibernateRestoreStackEnd[0] - &gIOHibernateRestoreStack[0],
|
|
cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
src = &gIOHibernateRestoreStackEnd[0];
|
|
count = round_page(hibernateEnd) - ((uintptr_t) src);
|
|
if (count) {
|
|
err = IOHibernatePolledFileWrite(vars, src, count, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!vars->hwEncrypt && (kIOHibernateModeEncrypt & gIOHibernateMode)) {
|
|
vars->fileVars->encryptStart = (vars->fileVars->position & ~(AES_BLOCK_SIZE - 1));
|
|
vars->fileVars->encryptEnd = UINT64_MAX;
|
|
HIBLOG("encryptStart %qx\n", vars->fileVars->encryptStart);
|
|
}
|
|
|
|
// write the preview buffer
|
|
|
|
if (vars->previewBuffer) {
|
|
ppnum = 0;
|
|
count = 0;
|
|
do{
|
|
phys64 = vars->previewBuffer->getPhysicalSegment(count, &segLen, kIOMemoryMapperNone);
|
|
pageAndCount[0] = atop_64_ppnum(phys64);
|
|
pageAndCount[1] = atop_64_ppnum(segLen);
|
|
err = IOHibernatePolledFileWrite(vars,
|
|
(const uint8_t *) &pageAndCount, sizeof(pageAndCount),
|
|
cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
count += segLen;
|
|
ppnum += sizeof(pageAndCount);
|
|
}while (phys64);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
src = (uint8_t *) vars->previewBuffer->getPhysicalSegment(0, NULL, _kIOMemorySourceSegment);
|
|
|
|
((hibernate_preview_t *)src)->lockTime = gIOConsoleLockTime;
|
|
|
|
count = (uint32_t) vars->previewBuffer->getLength();
|
|
|
|
header->previewPageListSize = ((uint32_t) ppnum);
|
|
header->previewSize = ((uint32_t) (count + ppnum));
|
|
|
|
for (page = 0; page < count; page += page_size) {
|
|
phys64 = vars->previewBuffer->getPhysicalSegment(page, NULL, kIOMemoryMapperNone);
|
|
sum1 += hibernate_sum_page(src + page, atop_64_ppnum(phys64));
|
|
}
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
err = IOHibernatePolledFileWrite(vars, src, count, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// mark areas for no save
|
|
hibernate_set_descriptor_page_state(vars, IOPolledFileGetIOBuffer(vars->fileVars),
|
|
kIOHibernatePageStateFree, &pageCount);
|
|
hibernate_set_descriptor_page_state(vars, vars->srcBuffer,
|
|
kIOHibernatePageStateFree, &pageCount);
|
|
|
|
// copy out bitmap of pages available for trashing during restore
|
|
|
|
bitmap_size = vars->page_list_wired->list_size;
|
|
src = (uint8_t *) vars->page_list_wired;
|
|
err = IOHibernatePolledFileWrite(vars, src, bitmap_size, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
// mark more areas for no save, but these are not available
|
|
// for trashing during restore
|
|
|
|
hibernate_page_list_set_volatile(vars->page_list, vars->page_list_wired, &pageCount);
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
// __HIB is explicitly saved above so we don't have to save it again
|
|
page = atop_32(KERNEL_IMAGE_TO_PHYS(hibernateBase));
|
|
count = atop_32(round_page(KERNEL_IMAGE_TO_PHYS(hibernateEnd))) - page;
|
|
hibernate_set_page_state(vars->page_list, vars->page_list_wired,
|
|
page, count,
|
|
kIOHibernatePageStateFree);
|
|
pageCount -= count;
|
|
#elif defined(__arm64__)
|
|
// the segments described in IOHibernateHibSegInfo are stored directly in the
|
|
// hibernation file, so they don't need to be saved again
|
|
extern unsigned long gPhysBase, gPhysSize;
|
|
for (size_t i = 0; i < NUM_HIBSEGINFO_SEGMENTS; i++) {
|
|
page = segInfo->segments[i].physPage;
|
|
count = segInfo->segments[i].pageCount;
|
|
uint64_t physAddr = ptoa_64(page);
|
|
uint64_t size = ptoa_64(count);
|
|
if (size &&
|
|
(physAddr >= gPhysBase) &&
|
|
(physAddr + size <= gPhysBase + gPhysSize)) {
|
|
hibernate_set_page_state(vars->page_list, vars->page_list_wired,
|
|
page, count,
|
|
kIOHibernatePageStateFree);
|
|
pageCount -= count;
|
|
}
|
|
}
|
|
#else
|
|
#error unimplemented
|
|
#endif
|
|
|
|
hibernate_set_descriptor_page_state(vars, vars->previewBuffer,
|
|
kIOHibernatePageStateFree, &pageCount);
|
|
hibernate_set_descriptor_page_state(vars, vars->handoffBuffer,
|
|
kIOHibernatePageStateFree, &pageCount);
|
|
|
|
#if KASAN
|
|
vm_size_t shadow_pages_free = atop_64(shadow_ptop) - atop_64(shadow_pnext);
|
|
|
|
/* no need to save unused shadow pages */
|
|
hibernate_set_page_state(vars->page_list, vars->page_list_wired,
|
|
atop_64(shadow_pnext),
|
|
shadow_pages_free,
|
|
kIOHibernatePageStateFree);
|
|
#endif
|
|
|
|
src = (uint8_t *) vars->srcBuffer->getBytesNoCopy();
|
|
compressed = src + page_size;
|
|
scratch = compressed + page_size;
|
|
|
|
pagesDone = 0;
|
|
lastBlob = 0;
|
|
|
|
HIBLOG("bitmap_size 0x%x, previewSize 0x%x, writing %d pages @ 0x%llx\n",
|
|
bitmap_size, header->previewSize,
|
|
pageCount, vars->fileVars->position);
|
|
|
|
|
|
enum
|
|
// pageType
|
|
{
|
|
kWired = 0x02,
|
|
kEncrypt = 0x01,
|
|
kWiredEncrypt = kWired | kEncrypt,
|
|
kWiredClear = kWired,
|
|
kUnwiredEncrypt = kEncrypt
|
|
};
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
bool cpuAES = (0 != (CPUID_FEATURE_AES & cpuid_features()));
|
|
#else /* defined(__i386__) || defined(__x86_64__) */
|
|
static const bool cpuAES = true;
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
|
|
for (pageType = kWiredEncrypt; pageType >= kUnwiredEncrypt; pageType--) {
|
|
if (kUnwiredEncrypt == pageType) {
|
|
// start unwired image
|
|
if (!vars->hwEncrypt && (kIOHibernateModeEncrypt & gIOHibernateMode)) {
|
|
vars->fileVars->encryptStart = (vars->fileVars->position & ~(((uint64_t)AES_BLOCK_SIZE) - 1));
|
|
vars->fileVars->encryptEnd = UINT64_MAX;
|
|
HIBLOG("encryptStart %qx\n", vars->fileVars->encryptStart);
|
|
}
|
|
bcopy(&cryptvars->aes_iv[0],
|
|
&gIOHibernateCryptWakeContext.aes_iv[0],
|
|
sizeof(cryptvars->aes_iv));
|
|
cryptvars = &gIOHibernateCryptWakeContext;
|
|
}
|
|
for (iterDone = false, ppnum = 0; !iterDone;) {
|
|
if (cpuAES && (pageType == kWiredClear)) {
|
|
count = 0;
|
|
} else {
|
|
count = hibernate_page_list_iterate((kWired & pageType) ? vars->page_list_wired : vars->page_list,
|
|
&ppnum);
|
|
if (count > UINT_MAX) {
|
|
count = UINT_MAX;
|
|
}
|
|
}
|
|
// kprintf("[%d](%x : %x)\n", pageType, ppnum, count);
|
|
iterDone = !count;
|
|
|
|
if (!cpuAES) {
|
|
if (count && (kWired & pageType) && needEncrypt) {
|
|
uint32_t checkIndex;
|
|
for (checkIndex = 0;
|
|
(checkIndex < count)
|
|
&& (((kEncrypt & pageType) == 0) == pmap_is_noencrypt(((ppnum_t)(ppnum + checkIndex))));
|
|
checkIndex++) {
|
|
}
|
|
if (!checkIndex) {
|
|
ppnum++;
|
|
continue;
|
|
}
|
|
count = checkIndex;
|
|
}
|
|
}
|
|
|
|
switch (pageType) {
|
|
case kWiredEncrypt: wiredPagesEncrypted += count; break;
|
|
case kWiredClear: wiredPagesClear += count; break;
|
|
case kUnwiredEncrypt: dirtyPagesEncrypted += count; break;
|
|
}
|
|
|
|
if (iterDone && (kWiredEncrypt == pageType)) {/* not yet end of wired list */
|
|
} else {
|
|
pageAndCount[0] = (uint32_t) ppnum;
|
|
pageAndCount[1] = (uint32_t) count;
|
|
err = IOHibernatePolledFileWrite(vars,
|
|
(const uint8_t *) &pageAndCount, sizeof(pageAndCount),
|
|
cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (page = ppnum; page < (ppnum + count); page++) {
|
|
err = IOMemoryDescriptorWriteFromPhysical(vars->srcBuffer, 0, ptoa_64(page), page_size);
|
|
if (err) {
|
|
HIBLOG("IOMemoryDescriptorWriteFromPhysical %d [%ld] %x\n", __LINE__, (long)page, err);
|
|
break;
|
|
}
|
|
|
|
sum = hibernate_sum_page(src, (uint32_t) page);
|
|
if (kWired & pageType) {
|
|
sum1 += sum;
|
|
} else {
|
|
sum2 += sum;
|
|
}
|
|
|
|
clock_get_uptime(&startTime);
|
|
wkresult = WKdm_compress_new((const WK_word*) src,
|
|
(WK_word*) compressed,
|
|
(WK_word*) scratch,
|
|
(uint32_t) (page_size - 4));
|
|
|
|
clock_get_uptime(&endTime);
|
|
ADD_ABSOLUTETIME(&compTime, &endTime);
|
|
SUB_ABSOLUTETIME(&compTime, &startTime);
|
|
|
|
compBytes += page_size;
|
|
pageCompressedSize = (-1 == wkresult) ? page_size : wkresult;
|
|
|
|
if (pageCompressedSize == 0) {
|
|
pageCompressedSize = 4;
|
|
data = src;
|
|
|
|
if (*(uint32_t *)src) {
|
|
svPageCount++;
|
|
} else {
|
|
zvPageCount++;
|
|
}
|
|
} else {
|
|
if (pageCompressedSize != page_size) {
|
|
data = compressed;
|
|
} else {
|
|
data = src;
|
|
}
|
|
}
|
|
|
|
assert(pageCompressedSize <= page_size);
|
|
tag = ((uint32_t) pageCompressedSize) | kIOHibernateTagSignature;
|
|
err = IOHibernatePolledFileWrite(vars, (const uint8_t *) &tag, sizeof(tag), cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
err = IOHibernatePolledFileWrite(vars, data, (pageCompressedSize + 3) & ~3, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
compressedSize += pageCompressedSize;
|
|
uncompressedSize += page_size;
|
|
pagesDone++;
|
|
|
|
if (vars->consoleMapping && (0 == (1023 & pagesDone))) {
|
|
blob = ((pagesDone * kIOHibernateProgressCount) / pageCount);
|
|
if (blob != lastBlob) {
|
|
ProgressUpdate(gIOHibernateGraphicsInfo, vars->consoleMapping, lastBlob, blob);
|
|
lastBlob = blob;
|
|
}
|
|
}
|
|
if (0 == (8191 & pagesDone)) {
|
|
clock_get_uptime(&endTime);
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
progressStamp = nsec / 750000000ULL;
|
|
if (progressStamp != lastProgressStamp) {
|
|
lastProgressStamp = progressStamp;
|
|
HIBPRINT("pages %d (%d%%)\n", pagesDone, (100 * pagesDone) / pageCount);
|
|
}
|
|
}
|
|
}
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
ppnum = page;
|
|
}
|
|
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
if ((kEncrypt & pageType) && vars->fileVars->encryptStart) {
|
|
vars->fileVars->encryptEnd = ((vars->fileVars->position + 511) & ~511ULL);
|
|
HIBLOG("encryptEnd %qx\n", vars->fileVars->encryptEnd);
|
|
}
|
|
|
|
if (kWiredEncrypt != pageType) {
|
|
// end of image1/2 - fill to next block
|
|
err = IOHibernatePolledFileWrite(vars, NULL, 0, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
}
|
|
if (kWiredClear == pageType) {
|
|
// enlarge wired image for test
|
|
// err = IOHibernatePolledFileWrite(vars, 0, 0x60000000, cryptvars);
|
|
|
|
// end wired image
|
|
header->encryptStart = vars->fileVars->encryptStart;
|
|
header->encryptEnd = vars->fileVars->encryptEnd;
|
|
image1Size = vars->fileVars->position;
|
|
HIBLOG("image1Size 0x%qx, encryptStart1 0x%qx, End1 0x%qx\n",
|
|
image1Size, header->encryptStart, header->encryptEnd);
|
|
}
|
|
}
|
|
if (kIOReturnSuccess != err) {
|
|
if (kIOReturnOverrun == err) {
|
|
// update actual compression ratio on not enough space (for retry)
|
|
gIOHibernateCompression = (compressedSize << 8) / uncompressedSize;
|
|
}
|
|
|
|
// update partial amount written (for IOPolledFileClose cleanup/unmap)
|
|
header->imageSize = vars->fileVars->position;
|
|
break;
|
|
}
|
|
|
|
|
|
// Header:
|
|
|
|
header->imageSize = vars->fileVars->position;
|
|
header->image1Size = image1Size;
|
|
header->bitmapSize = bitmap_size;
|
|
header->pageCount = pageCount;
|
|
|
|
header->restore1Sum = restore1Sum;
|
|
header->image1Sum = sum1;
|
|
header->image2Sum = sum2;
|
|
header->sleepTime = gIOLastSleepTime.tv_sec;
|
|
|
|
header->compression = ((uint32_t)((compressedSize << 8) / uncompressedSize));
|
|
#if defined(__arm64__)
|
|
/*
|
|
* We don't support retry on hibernation failure and so
|
|
* we don't want to set this value to anything smaller
|
|
* just because we may have been lucky this time around.
|
|
* Though we'll let it go higher.
|
|
*/
|
|
if (header->compression < HIB_COMPR_RATIO_ARM64) {
|
|
header->compression = HIB_COMPR_RATIO_ARM64;
|
|
}
|
|
#endif /* __arm64__ */
|
|
|
|
gIOHibernateCompression = header->compression;
|
|
|
|
count = vars->fileVars->fileExtents->getLength();
|
|
if (count > sizeof(header->fileExtentMap)) {
|
|
header->fileExtentMapSize = ((uint32_t) count);
|
|
count = sizeof(header->fileExtentMap);
|
|
} else {
|
|
header->fileExtentMapSize = sizeof(header->fileExtentMap);
|
|
}
|
|
bcopy(&fileExtents[0], &header->fileExtentMap[0], count);
|
|
|
|
header->deviceBase = vars->fileVars->block0;
|
|
header->deviceBlockSize = vars->fileVars->blockSize;
|
|
header->lastHibAbsTime = mach_absolute_time();
|
|
header->lastHibContTime = mach_continuous_time();
|
|
|
|
|
|
IOPolledFileSeek(vars->fileVars, 0);
|
|
err = IOHibernatePolledFileWrite(vars,
|
|
(uint8_t *) header, sizeof(IOHibernateImageHeader),
|
|
cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
break;
|
|
}
|
|
|
|
err = IOHibernatePolledFileWrite(vars, NULL, 0, cryptvars);
|
|
}while (false);
|
|
|
|
clock_get_uptime(&endTime);
|
|
|
|
IOService::getPMRootDomain()->pmStatsRecordEvent(
|
|
kIOPMStatsHibernateImageWrite | kIOPMStatsEventStopFlag, endTime);
|
|
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
HIBLOG("all time: %qd ms, ", nsec / 1000000ULL);
|
|
|
|
absolutetime_to_nanoseconds(compTime, &nsec);
|
|
HIBLOG("comp bytes: %qd time: %qd ms %qd Mb/s, ",
|
|
compBytes,
|
|
nsec / 1000000ULL,
|
|
nsec ? (((compBytes * 1000000000ULL) / 1024 / 1024) / nsec) : 0);
|
|
|
|
absolutetime_to_nanoseconds(vars->fileVars->cryptTime, &nsec);
|
|
HIBLOG("crypt bytes: %qd time: %qd ms %qd Mb/s, ",
|
|
vars->fileVars->cryptBytes,
|
|
nsec / 1000000ULL,
|
|
nsec ? (((vars->fileVars->cryptBytes * 1000000000ULL) / 1024 / 1024) / nsec) : 0);
|
|
|
|
HIBLOG("\nimage %qd (%lld%%), uncompressed %qd (%d), compressed %qd (%d%%)\n",
|
|
header->imageSize, (header->imageSize * 100) / vars->fileVars->fileSize,
|
|
uncompressedSize, atop_32(uncompressedSize), compressedSize,
|
|
uncompressedSize ? ((int) ((compressedSize * 100ULL) / uncompressedSize)) : 0);
|
|
|
|
HIBLOG("\nsum1 %x, sum2 %x\n", sum1, sum2);
|
|
|
|
HIBLOG("svPageCount %d, zvPageCount %d, wiredPagesEncrypted %d, wiredPagesClear %d, dirtyPagesEncrypted %d\n",
|
|
svPageCount, zvPageCount, wiredPagesEncrypted, wiredPagesClear, dirtyPagesEncrypted);
|
|
|
|
if (pollerOpen) {
|
|
IOPolledFilePollersClose(vars->fileVars, (kIOReturnSuccess == err) ? kIOPolledBeforeSleepState : kIOPolledBeforeSleepStateAborted );
|
|
}
|
|
|
|
if (vars->consoleMapping) {
|
|
ProgressUpdate(gIOHibernateGraphicsInfo,
|
|
vars->consoleMapping, 0, kIOHibernateProgressCount);
|
|
}
|
|
|
|
HIBLOG("hibernate_write_image done(%x)\n", err);
|
|
|
|
// should we come back via regular wake, set the state in memory.
|
|
gIOHibernateState = kIOHibernateStateInactive;
|
|
|
|
KDBG(IOKDBG_CODE(DBG_HIBERNATE, 1) | DBG_FUNC_END, wiredPagesEncrypted,
|
|
wiredPagesClear, dirtyPagesEncrypted);
|
|
|
|
#if defined(__arm64__)
|
|
if (kIOReturnSuccess == err) {
|
|
return kIOHibernatePostWriteHalt;
|
|
} else {
|
|
// on ARM, once ApplePMGR decides we're hibernating, we can't turn back
|
|
// see: <rdar://problem/63848862> Tonga ApplePMGR diff quiesce path support
|
|
panic("hibernate_write_image encountered error 0x%x", err);
|
|
}
|
|
#else
|
|
if (kIOReturnSuccess == err) {
|
|
if (kIOHibernateModeSleep & gIOHibernateMode) {
|
|
return kIOHibernatePostWriteSleep;
|
|
} else if (kIOHibernateModeRestart & gIOHibernateMode) {
|
|
return kIOHibernatePostWriteRestart;
|
|
} else {
|
|
/* by default, power down */
|
|
return kIOHibernatePostWriteHalt;
|
|
}
|
|
} else if (kIOReturnAborted == err) {
|
|
return kIOHibernatePostWriteWake;
|
|
} else {
|
|
/* on error, sleep */
|
|
return kIOHibernatePostWriteSleep;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
extern "C" void
|
|
hibernate_machine_init(void)
|
|
{
|
|
IOReturn err;
|
|
uint32_t sum;
|
|
uint32_t pagesDone;
|
|
uint32_t pagesRead = 0;
|
|
AbsoluteTime startTime, compTime;
|
|
AbsoluteTime allTime, endTime;
|
|
AbsoluteTime startIOTime, endIOTime;
|
|
uint64_t nsec, nsecIO;
|
|
uint64_t compBytes;
|
|
uint64_t lastProgressStamp = 0;
|
|
uint64_t progressStamp;
|
|
IOPolledFileCryptVars * cryptvars = NULL;
|
|
|
|
IOHibernateVars * vars = &gIOHibernateVars;
|
|
bzero(gIOHibernateStats, sizeof(hibernate_statistics_t));
|
|
|
|
if (!vars->fileVars || !vars->fileVars->pollers) {
|
|
return;
|
|
}
|
|
|
|
sum = gIOHibernateCurrentHeader->actualImage1Sum;
|
|
pagesDone = gIOHibernateCurrentHeader->actualUncompressedPages;
|
|
|
|
if (kIOHibernateStateWakingFromHibernate != gIOHibernateState) {
|
|
HIBLOG("regular wake\n");
|
|
return;
|
|
}
|
|
|
|
HIBPRINT("diag %x %x %x %x\n",
|
|
gIOHibernateCurrentHeader->diag[0], gIOHibernateCurrentHeader->diag[1],
|
|
gIOHibernateCurrentHeader->diag[2], gIOHibernateCurrentHeader->diag[3]);
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
#define t40ms(x) ((uint32_t)((tmrCvt((((uint64_t)(x)) << 8), tscFCvtt2n) / 1000000)))
|
|
#else /* defined(__i386__) || defined(__x86_64__) */
|
|
#define t40ms(x) x
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
#define tStat(x, y) gIOHibernateStats->x = t40ms(gIOHibernateCurrentHeader->y);
|
|
tStat(booterStart, booterStart);
|
|
gIOHibernateStats->smcStart = gIOHibernateCurrentHeader->smcStart;
|
|
tStat(booterDuration0, booterTime0);
|
|
tStat(booterDuration1, booterTime1);
|
|
tStat(booterDuration2, booterTime2);
|
|
tStat(booterDuration, booterTime);
|
|
tStat(booterConnectDisplayDuration, connectDisplayTime);
|
|
tStat(booterSplashDuration, splashTime);
|
|
tStat(trampolineDuration, trampolineTime);
|
|
|
|
gIOHibernateStats->image1Size = gIOHibernateCurrentHeader->image1Size;
|
|
gIOHibernateStats->imageSize = gIOHibernateCurrentHeader->imageSize;
|
|
gIOHibernateStats->image1Pages = pagesDone;
|
|
|
|
/* HIBERNATE_stats */
|
|
KDBG(IOKDBG_CODE(DBG_HIBERNATE, 14), gIOHibernateStats->smcStart,
|
|
gIOHibernateStats->booterStart, gIOHibernateStats->booterDuration,
|
|
gIOHibernateStats->trampolineDuration);
|
|
|
|
HIBLOG("booter start at %d ms smc %d ms, [%d, %d, %d] total %d ms, dsply %d, %d ms, tramp %d ms\n",
|
|
gIOHibernateStats->booterStart,
|
|
gIOHibernateStats->smcStart,
|
|
gIOHibernateStats->booterDuration0,
|
|
gIOHibernateStats->booterDuration1,
|
|
gIOHibernateStats->booterDuration2,
|
|
gIOHibernateStats->booterDuration,
|
|
gIOHibernateStats->booterConnectDisplayDuration,
|
|
gIOHibernateStats->booterSplashDuration,
|
|
gIOHibernateStats->trampolineDuration);
|
|
|
|
HIBLOG("hibernate_machine_init: state %d, image pages %d, sum was %x, imageSize 0x%qx, image1Size 0x%qx, conflictCount %d, nextFree %x\n",
|
|
gIOHibernateState, pagesDone, sum, gIOHibernateStats->imageSize, gIOHibernateStats->image1Size,
|
|
gIOHibernateCurrentHeader->conflictCount, gIOHibernateCurrentHeader->nextFree);
|
|
|
|
if ((0 != (kIOHibernateModeSleep & gIOHibernateMode))
|
|
&& (0 != ((kIOHibernateModeDiscardCleanActive | kIOHibernateModeDiscardCleanInactive) & gIOHibernateMode))) {
|
|
hibernate_page_list_discard(vars->page_list);
|
|
}
|
|
|
|
if (vars->hwEncrypt) {
|
|
// if vars->hwEncrypt is true, we don't need cryptvars since we supply the
|
|
// decryption key via IOPolledFilePollersSetEncryptionKey
|
|
cryptvars = NULL;
|
|
} else {
|
|
cryptvars = (kIOHibernateModeEncrypt & gIOHibernateMode) ? &gIOHibernateCryptWakeContext : NULL;
|
|
}
|
|
|
|
if (gIOHibernateCurrentHeader->handoffPageCount > gIOHibernateHandoffPageCount) {
|
|
panic("handoff overflow");
|
|
}
|
|
|
|
IOHibernateHandoff * handoff;
|
|
bool done = false;
|
|
bool foundCryptData = false;
|
|
bool foundVolumeEncryptData = false;
|
|
const uint8_t * handoffStart = (const uint8_t*)vars->handoffBuffer->getBytesNoCopy();
|
|
const uint8_t * handoffEnd = handoffStart + vars->handoffBuffer->getLength();
|
|
|
|
for (handoff = (IOHibernateHandoff *) vars->handoffBuffer->getBytesNoCopy();
|
|
!done;
|
|
handoff = (IOHibernateHandoff *) &handoff->data[handoff->bytecount]) {
|
|
if (((uint8_t*)handoff < handoffStart) ||
|
|
(&handoff->data[handoff->bytecount] > handoffEnd)) {
|
|
panic("handoff out of range");
|
|
}
|
|
// HIBPRINT("handoff %p, %x, %x\n", handoff, handoff->type, handoff->bytecount);
|
|
uint8_t * data = &handoff->data[0];
|
|
switch (handoff->type) {
|
|
case kIOHibernateHandoffTypeEnd:
|
|
done = true;
|
|
break;
|
|
|
|
case kIOHibernateHandoffTypeGraphicsInfo:
|
|
if (handoff->bytecount == sizeof(*gIOHibernateGraphicsInfo)) {
|
|
bcopy(data, gIOHibernateGraphicsInfo, sizeof(*gIOHibernateGraphicsInfo));
|
|
}
|
|
break;
|
|
|
|
case kIOHibernateHandoffTypeCryptVars:
|
|
if (cryptvars) {
|
|
hibernate_cryptwakevars_t *
|
|
wakevars = (hibernate_cryptwakevars_t *) &handoff->data[0];
|
|
if (handoff->bytecount == sizeof(*wakevars)) {
|
|
bcopy(&wakevars->aes_iv[0], &cryptvars->aes_iv[0], sizeof(cryptvars->aes_iv));
|
|
} else {
|
|
panic("kIOHibernateHandoffTypeCryptVars(%d)", handoff->bytecount);
|
|
}
|
|
}
|
|
foundCryptData = true;
|
|
bzero(data, handoff->bytecount);
|
|
break;
|
|
|
|
case kIOHibernateHandoffTypeVolumeCryptKey:
|
|
if (handoff->bytecount == vars->volumeCryptKeySize) {
|
|
bcopy(data, &vars->volumeCryptKey[0], vars->volumeCryptKeySize);
|
|
foundVolumeEncryptData = true;
|
|
} else {
|
|
panic("kIOHibernateHandoffTypeVolumeCryptKey(%d)", handoff->bytecount);
|
|
}
|
|
break;
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
case kIOHibernateHandoffTypeMemoryMap:
|
|
|
|
clock_get_uptime(&allTime);
|
|
|
|
hibernate_newruntime_map(data, handoff->bytecount,
|
|
gIOHibernateCurrentHeader->systemTableOffset);
|
|
|
|
clock_get_uptime(&endTime);
|
|
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
|
|
HIBLOG("hibernate_newruntime_map time: %qd ms, ", nsec / 1000000ULL);
|
|
|
|
break;
|
|
|
|
case kIOHibernateHandoffTypeDeviceTree:
|
|
{
|
|
// DTEntry chosen = NULL;
|
|
// HIBPRINT("SecureDTLookupEntry %d\n", SecureDTLookupEntry((const DTEntry) data, "/chosen", &chosen));
|
|
}
|
|
break;
|
|
#endif /* defined(__i386__) || defined(__x86_64__) */
|
|
|
|
default:
|
|
done = (kIOHibernateHandoffType != (handoff->type & 0xFFFF0000));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (vars->hwEncrypt && !foundVolumeEncryptData) {
|
|
panic("no volumeCryptKey");
|
|
} else if (cryptvars && !foundCryptData) {
|
|
panic("hibernate handoff");
|
|
}
|
|
|
|
HIBPRINT("video 0x%llx %d %d %d status %x\n",
|
|
gIOHibernateGraphicsInfo->physicalAddress, gIOHibernateGraphicsInfo->depth,
|
|
gIOHibernateGraphicsInfo->width, gIOHibernateGraphicsInfo->height, gIOHibernateGraphicsInfo->gfxStatus);
|
|
|
|
if (vars->videoMapping && gIOHibernateGraphicsInfo->physicalAddress) {
|
|
vars->videoMapSize = round_page(gIOHibernateGraphicsInfo->height
|
|
* gIOHibernateGraphicsInfo->rowBytes);
|
|
if (vars->videoMapSize > vars->videoAllocSize) {
|
|
vars->videoMapSize = 0;
|
|
} else {
|
|
IOMapPages(kernel_map,
|
|
vars->videoMapping, gIOHibernateGraphicsInfo->physicalAddress,
|
|
vars->videoMapSize, kIOMapInhibitCache );
|
|
}
|
|
}
|
|
|
|
if (vars->videoMapSize) {
|
|
ProgressUpdate(gIOHibernateGraphicsInfo,
|
|
(uint8_t *) vars->videoMapping, 0, kIOHibernateProgressCount);
|
|
}
|
|
|
|
uint8_t * src = (uint8_t *) vars->srcBuffer->getBytesNoCopy();
|
|
uint8_t * compressed = src + page_size;
|
|
uint8_t * scratch = compressed + page_size;
|
|
uint32_t decoOffset;
|
|
|
|
clock_get_uptime(&allTime);
|
|
AbsoluteTime_to_scalar(&compTime) = 0;
|
|
compBytes = 0;
|
|
|
|
HIBLOG("IOPolledFilePollersOpen(), ml_get_interrupts_enabled %d\n", ml_get_interrupts_enabled());
|
|
err = IOPolledFilePollersOpen(vars->fileVars, kIOPolledAfterSleepState, false);
|
|
clock_get_uptime(&startIOTime);
|
|
endTime = startIOTime;
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
HIBLOG("IOPolledFilePollersOpen(%x) %qd ms\n", err, nsec / 1000000ULL);
|
|
|
|
if (vars->hwEncrypt) {
|
|
err = IOPolledFilePollersSetEncryptionKey(vars->fileVars,
|
|
&vars->volumeCryptKey[0], vars->volumeCryptKeySize);
|
|
HIBLOG("IOPolledFilePollersSetEncryptionKey(%x) %ld\n", err, vars->volumeCryptKeySize);
|
|
if (kIOReturnSuccess != err) {
|
|
panic("IOPolledFilePollersSetEncryptionKey(0x%x)", err);
|
|
}
|
|
cryptvars = NULL;
|
|
}
|
|
|
|
IOPolledFileSeek(vars->fileVars, gIOHibernateCurrentHeader->image1Size);
|
|
|
|
// kick off the read ahead
|
|
vars->fileVars->bufferHalf = 0;
|
|
vars->fileVars->bufferLimit = 0;
|
|
vars->fileVars->lastRead = 0;
|
|
vars->fileVars->readEnd = gIOHibernateCurrentHeader->imageSize;
|
|
vars->fileVars->bufferOffset = vars->fileVars->bufferLimit;
|
|
vars->fileVars->cryptBytes = 0;
|
|
AbsoluteTime_to_scalar(&vars->fileVars->cryptTime) = 0;
|
|
|
|
err = IOPolledFileRead(vars->fileVars, NULL, 0, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
vars->fileVars->bufferOffset = vars->fileVars->bufferLimit;
|
|
// --
|
|
|
|
HIBLOG("hibernate_machine_init reading\n");
|
|
|
|
|
|
uint32_t * header = (uint32_t *) src;
|
|
sum = 0;
|
|
|
|
while (kIOReturnSuccess == err) {
|
|
unsigned int count;
|
|
unsigned int page;
|
|
uint32_t tag;
|
|
vm_offset_t compressedSize;
|
|
ppnum_t ppnum;
|
|
|
|
err = IOPolledFileRead(vars->fileVars, src, 8, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
ppnum = header[0];
|
|
count = header[1];
|
|
|
|
// HIBPRINT("(%x, %x)\n", ppnum, count);
|
|
|
|
if (!count) {
|
|
break;
|
|
}
|
|
|
|
for (page = 0; page < count; page++) {
|
|
err = IOPolledFileRead(vars->fileVars, (uint8_t *) &tag, 4, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
compressedSize = kIOHibernateTagLength & tag;
|
|
if (kIOHibernateTagSignature != (tag & ~kIOHibernateTagLength)) {
|
|
err = kIOReturnIPCError;
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
err = IOPolledFileRead(vars->fileVars, src, (compressedSize + 3) & ~3, cryptvars);
|
|
if (kIOReturnSuccess != err) {
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
if (compressedSize < page_size) {
|
|
decoOffset = ((uint32_t) page_size);
|
|
clock_get_uptime(&startTime);
|
|
|
|
if (compressedSize == 4) {
|
|
int i;
|
|
uint32_t *s, *d;
|
|
|
|
s = (uint32_t *)src;
|
|
d = (uint32_t *)(uintptr_t)compressed;
|
|
|
|
for (i = 0; i < (int)(PAGE_SIZE / sizeof(int32_t)); i++) {
|
|
*d++ = *s;
|
|
}
|
|
} else {
|
|
pal_hib_decompress_page(src, compressed, scratch, ((unsigned int) compressedSize));
|
|
}
|
|
clock_get_uptime(&endTime);
|
|
ADD_ABSOLUTETIME(&compTime, &endTime);
|
|
SUB_ABSOLUTETIME(&compTime, &startTime);
|
|
compBytes += page_size;
|
|
} else {
|
|
decoOffset = 0;
|
|
}
|
|
|
|
sum += hibernate_sum_page((src + decoOffset), ((uint32_t) ppnum));
|
|
err = IOMemoryDescriptorReadToPhysical(vars->srcBuffer, decoOffset, ptoa_64(ppnum), page_size);
|
|
if (err) {
|
|
HIBLOG("IOMemoryDescriptorReadToPhysical [%ld] %x\n", (long)ppnum, err);
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
|
|
ppnum++;
|
|
pagesDone++;
|
|
pagesRead++;
|
|
|
|
if (0 == (8191 & pagesDone)) {
|
|
clock_get_uptime(&endTime);
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
progressStamp = nsec / 750000000ULL;
|
|
if (progressStamp != lastProgressStamp) {
|
|
lastProgressStamp = progressStamp;
|
|
HIBPRINT("pages %d (%d%%)\n", pagesDone,
|
|
(100 * pagesDone) / gIOHibernateCurrentHeader->pageCount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((kIOReturnSuccess == err) && (pagesDone == gIOHibernateCurrentHeader->actualUncompressedPages)) {
|
|
err = kIOReturnLockedRead;
|
|
}
|
|
|
|
if (kIOReturnSuccess != err) {
|
|
panic("Hibernate restore error %x", err);
|
|
}
|
|
|
|
|
|
gIOHibernateCurrentHeader->actualImage2Sum = sum;
|
|
gIOHibernateCompression = gIOHibernateCurrentHeader->compression;
|
|
|
|
clock_get_uptime(&endIOTime);
|
|
|
|
err = IOPolledFilePollersClose(vars->fileVars, kIOPolledAfterSleepState);
|
|
|
|
clock_get_uptime(&endTime);
|
|
|
|
IOService::getPMRootDomain()->pmStatsRecordEvent(
|
|
kIOPMStatsHibernateImageRead | kIOPMStatsEventStartFlag, allTime);
|
|
IOService::getPMRootDomain()->pmStatsRecordEvent(
|
|
kIOPMStatsHibernateImageRead | kIOPMStatsEventStopFlag, endTime);
|
|
|
|
SUB_ABSOLUTETIME(&endTime, &allTime);
|
|
absolutetime_to_nanoseconds(endTime, &nsec);
|
|
|
|
SUB_ABSOLUTETIME(&endIOTime, &startIOTime);
|
|
absolutetime_to_nanoseconds(endIOTime, &nsecIO);
|
|
|
|
gIOHibernateStats->kernelImageReadDuration = ((uint32_t) (nsec / 1000000ULL));
|
|
gIOHibernateStats->imagePages = pagesDone;
|
|
|
|
HIBLOG("hibernate_machine_init pagesDone %d sum2 %x, time: %d ms, disk(0x%x) %qd Mb/s, ",
|
|
pagesDone, sum, gIOHibernateStats->kernelImageReadDuration, kDefaultIOSize,
|
|
nsecIO ? ((((gIOHibernateCurrentHeader->imageSize - gIOHibernateCurrentHeader->image1Size) * 1000000000ULL) / 1024 / 1024) / nsecIO) : 0);
|
|
|
|
absolutetime_to_nanoseconds(compTime, &nsec);
|
|
HIBLOG("comp bytes: %qd time: %qd ms %qd Mb/s, ",
|
|
compBytes,
|
|
nsec / 1000000ULL,
|
|
nsec ? (((compBytes * 1000000000ULL) / 1024 / 1024) / nsec) : 0);
|
|
|
|
absolutetime_to_nanoseconds(vars->fileVars->cryptTime, &nsec);
|
|
HIBLOG("crypt bytes: %qd time: %qd ms %qd Mb/s\n",
|
|
vars->fileVars->cryptBytes,
|
|
nsec / 1000000ULL,
|
|
nsec ? (((vars->fileVars->cryptBytes * 1000000000ULL) / 1024 / 1024) / nsec) : 0);
|
|
|
|
KDBG(IOKDBG_CODE(DBG_HIBERNATE, 2), pagesRead, pagesDone);
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
void
|
|
IOHibernateSetWakeCapabilities(uint32_t capability)
|
|
{
|
|
if (kIOHibernateStateWakingFromHibernate == gIOHibernateState) {
|
|
gIOHibernateStats->wakeCapability = capability;
|
|
|
|
if (kIOPMSystemCapabilityGraphics & capability) {
|
|
vm_compressor_do_warmup();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
void
|
|
IOHibernateSystemRestart(void)
|
|
{
|
|
static uint8_t noteStore[32] __attribute__((aligned(32)));
|
|
IORegistryEntry * regEntry;
|
|
const OSSymbol * sym;
|
|
OSData * noteProp;
|
|
OSData * data;
|
|
uintptr_t * smcVars;
|
|
uint8_t * smcBytes;
|
|
size_t len;
|
|
addr64_t element;
|
|
|
|
data = OSDynamicCast(OSData, IOService::getPMRootDomain()->getProperty(kIOHibernateSMCVariablesKey));
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
smcVars = (typeof(smcVars))data->getBytesNoCopy();
|
|
smcBytes = (typeof(smcBytes))smcVars[1];
|
|
len = smcVars[0];
|
|
if (len > sizeof(noteStore)) {
|
|
len = sizeof(noteStore);
|
|
}
|
|
noteProp = OSData::withCapacity(3 * sizeof(element));
|
|
if (!noteProp) {
|
|
return;
|
|
}
|
|
element = len;
|
|
noteProp->appendBytes(&element, sizeof(element));
|
|
element = crc32(0, smcBytes, len);
|
|
noteProp->appendBytes(&element, sizeof(element));
|
|
|
|
bcopy(smcBytes, noteStore, len);
|
|
element = (addr64_t) ¬eStore[0];
|
|
element = (element & page_mask) | ptoa_64(pmap_find_phys(kernel_pmap, element));
|
|
noteProp->appendBytes(&element, sizeof(element));
|
|
|
|
if (!gIOOptionsEntry) {
|
|
regEntry = IORegistryEntry::fromPath("/options", gIODTPlane);
|
|
gIOOptionsEntry = OSDynamicCast(IODTNVRAM, regEntry);
|
|
if (regEntry && !gIOOptionsEntry) {
|
|
regEntry->release();
|
|
}
|
|
}
|
|
|
|
sym = OSSymbol::withCStringNoCopy(kIOHibernateBootNoteKey);
|
|
if (gIOOptionsEntry && sym) {
|
|
gIOOptionsEntry->setProperty(sym, noteProp);
|
|
}
|
|
if (noteProp) {
|
|
noteProp->release();
|
|
}
|
|
if (sym) {
|
|
sym->release();
|
|
}
|
|
}
|