Skip to content

Commit

Permalink
Fix issue with wakeup time: tsc sync is performed too early, so wakeu…
Browse files Browse the repository at this point in the history
…p time can be incorrect, some app can crash with assertion failure: "currentTime >= wakeUpTime"
  • Loading branch information
lvs1974 committed Sep 19, 2021
1 parent 889d371 commit c25ae85
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 12 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
CpuTscSync Changelog
===================
#### v1.0.5
- Fix issue with wakeup time: tsc sync is performed too early, so wakeup time can be incorrect, some app can crash with assertion failure: "currentTime >= wakeUpTime"
- Revert back tsc sync in VoodooTSCSync::setPowerState as a fallback for older systems

#### v1.0.4
- Added constants for macOS 12 support
- Added macOS 12 compatibility for CPUs with `MSR_IA32_TSC_ADJUST` (03Bh)
Expand Down
54 changes: 46 additions & 8 deletions CpuTscSync/CpuTscSync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
#include <i386/proc_reg.h>
#include <IOKit/IOTimerEventSource.h>


#include "CpuTscSync.hpp"

static CpuTscSyncPlugin *callbackCpuf = nullptr;
_Atomic(bool) CpuTscSyncPlugin::tsc_synced = false;
_Atomic(bool) CpuTscSyncPlugin::clock_get_calendar_called_after_wake = false;
_Atomic(uint16_t) CpuTscSyncPlugin::cores_ready = 0;
_Atomic(uint64_t) CpuTscSyncPlugin::tsc_frequency = 0;
_Atomic(uint64_t) CpuTscSyncPlugin::xnu_thread_tid = -1UL;



Expand Down Expand Up @@ -62,6 +65,17 @@ void CpuTscSyncPlugin::tsc_adjust_or_reset()
tsc_synced = true;
}

void CpuTscSyncPlugin::reset_sync_flag()
{
tsc_synced = false;
xnu_thread_tid = -1UL;
}

bool CpuTscSyncPlugin::is_clock_get_calendar_called_after_wake()
{
return clock_get_calendar_called_after_wake;
}

void CpuTscSyncPlugin::init()
{
callbackCpuf = this;
Expand All @@ -86,34 +100,58 @@ void CpuTscSyncPlugin::xcpm_urgency(int urgency, uint64_t rt_period, uint64_t rt
IOReturn CpuTscSyncPlugin::IOHibernateSystemHasSlept()
{
tsc_synced = false;
xnu_thread_tid = -1UL;
return FunctionCast(IOHibernateSystemHasSlept, callbackCpuf->orgIOHibernateSystemHasSlept)();
}

IOReturn CpuTscSyncPlugin::IOHibernateSystemWake()
{
tsc_adjust_or_reset();
// post pone tsc sync in IOPMrootDomain::powerChangeDone,
// it will be executed later, after calculating wakeup time
tsc_synced = false;
xnu_thread_tid = thread_tid(current_thread());
DBGLOG("cputs", "IOHibernateSystemWake is called");
return FunctionCast(IOHibernateSystemWake, callbackCpuf->orgIOHibernateSystemWake)();
}

void CpuTscSyncPlugin::clock_get_calendar_microtime(clock_sec_t *secs, clock_usec_t *microsecs)
{
FunctionCast(clock_get_calendar_microtime, callbackCpuf->org_clock_get_calendar_microtime)(secs, microsecs);

if (xnu_thread_tid == thread_tid(current_thread())) {
xnu_thread_tid = -1UL;
DBGLOG("cputs", "clock_get_calendar_microtime is called after wake");
tsc_adjust_or_reset();
}
}

void CpuTscSyncPlugin::processKernel(KernelPatcher &patcher)
{
if (!kernel_routed)
{
clock_get_calendar_called_after_wake = (getKernelVersion() >= KernelVersion::ElCapitan);
if (clock_get_calendar_called_after_wake) {
DBGLOG("cputs", "_clock_get_calendar_microtime will be used to sync tsc after wake");
}

KernelPatcher::RouteRequest requests_for_long_jump[] {
{"_IOHibernateSystemWake", IOHibernateSystemWake, orgIOHibernateSystemWake},
{"_IOHibernateSystemHasSlept", IOHibernateSystemHasSlept, orgIOHibernateSystemHasSlept}
{"_IOHibernateSystemHasSlept", IOHibernateSystemHasSlept, orgIOHibernateSystemHasSlept},
{"_IOHibernateSystemWake", IOHibernateSystemWake, orgIOHibernateSystemWake}
};

if (!patcher.routeMultipleLong(KernelPatcher::KernelID, requests_for_long_jump))

size_t size = arrsize(requests_for_long_jump) - (clock_get_calendar_called_after_wake ? 0 : 1);
if (!patcher.routeMultipleLong(KernelPatcher::KernelID, requests_for_long_jump, size))
SYSLOG("cputs", "patcher.routeMultiple for %s is failed with error %d", requests_for_long_jump[0].symbol, patcher.getError());

patcher.clearError();

KernelPatcher::RouteRequest requests[] {
{"_xcpm_urgency", xcpm_urgency, org_xcpm_urgency}
{"_xcpm_urgency", xcpm_urgency, org_xcpm_urgency},
{"_clock_get_calendar_microtime", clock_get_calendar_microtime, org_clock_get_calendar_microtime }
};

if (!patcher.routeMultiple(KernelPatcher::KernelID, requests))

size = arrsize(requests) - (clock_get_calendar_called_after_wake ? 0 : 1);
if (!patcher.routeMultiple(KernelPatcher::KernelID, requests, size))
SYSLOG("cputs", "patcher.routeMultiple for %s is failed with error %d", requests[0].symbol, patcher.getError());
kernel_routed = true;
}
Expand Down
12 changes: 9 additions & 3 deletions CpuTscSync/CpuTscSync.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,27 @@ class CpuTscSyncPlugin {
private:
_Atomic(bool) kernel_routed = false;
static _Atomic(bool) tsc_synced;
static _Atomic(bool) clock_get_calendar_called_after_wake;
static _Atomic(uint16_t) cores_ready;
static _Atomic(uint64_t) tsc_frequency;

static _Atomic(uint64_t) xnu_thread_tid;

private:
/**
* Trampolines for original resource load callback
*/
mach_vm_address_t org_xcpm_urgency {0};
mach_vm_address_t orgIOHibernateSystemHasSlept {0};
mach_vm_address_t orgIOHibernateSystemWake {0};

mach_vm_address_t org_clock_get_calendar_microtime {0};

/**
* Hooked functions
*/
static void xcpm_urgency(int urgency, uint64_t rt_period, uint64_t rt_deadline);
static void xcpm_urgency(int urgency, uint64_t rt_period, uint64_t rt_deadline);
static IOReturn IOHibernateSystemHasSlept(void);
static IOReturn IOHibernateSystemWake();
static void clock_get_calendar_microtime(clock_sec_t *secs, clock_usec_t *microsecs);

/**
* Patch kernel
Expand All @@ -56,6 +60,8 @@ class CpuTscSyncPlugin {

public:
static void tsc_adjust_or_reset();
static void reset_sync_flag();
static bool is_clock_get_calendar_called_after_wake();
};

#endif /* kern_cputs_hpp */
27 changes: 26 additions & 1 deletion CpuTscSync/VoodooTSCSync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ OSDefineMetaClassAndStructors(VoodooTSCSync, IOService)

IOService* VoodooTSCSync::probe(IOService* provider, SInt32* score)
{
if (!super::probe(provider, score)) return NULL;
if (!provider) return NULL;
if (!super::probe(provider, score)) return NULL;

OSNumber* cpuNumber = OSDynamicCast(OSNumber, provider->getProperty("IOCPUNumber"));
if (!cpuNumber) return NULL;
Expand All @@ -26,3 +26,28 @@ IOService* VoodooTSCSync::probe(IOService* provider, SInt32* score)

return this;
}

bool VoodooTSCSync::start(IOService *provider) {
if (!IOService::start(provider)) {
SYSLOG("cputs", "failed to start the parent");
return false;
}

PMinit();
provider->joinPMtree(this);
registerPowerDriver(this, powerStates, arrsize(powerStates));

return true;
}

IOReturn VoodooTSCSync::setPowerState(unsigned long state, IOService *whatDevice){
DBGLOG("cputs", "changing power state to %lu", state);
if (!CpuTscSyncPlugin::is_clock_get_calendar_called_after_wake()) {
if (state == PowerStateOff)
CpuTscSyncPlugin::reset_sync_flag();
if (state == PowerStateOn)
CpuTscSyncPlugin::tsc_adjust_or_reset();
}

return kIOPMAckImplied;
}
44 changes: 44 additions & 0 deletions CpuTscSync/VoodooTSCSync.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,56 @@
*/

#include <IOKit/IOService.h>
#include <IOKit/pwr_mgt/IOPMPowerSource.h>

class VoodooTSCSync : public IOService
{
typedef IOService super;
OSDeclareDefaultStructors(VoodooTSCSync)

/**
* Power state name indexes
*/
enum PowerState {
PowerStateOff,
PowerStateOn,
PowerStateMax
};

/**
* Power states we monitor
*/
IOPMPowerState powerStates[PowerStateMax] {
{kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{kIOPMPowerStateVersion1, kIOPMPowerOn | kIOPMDeviceUsable, kIOPMPowerOn, kIOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0}
};

public:
/**
* Decide on whether to load or not by checking the processor compatibility.
*
* @param provider parent IOService object
* @param score probing score
*
* @return self if we could load anyhow
*/
virtual IOService* probe(IOService* provider, SInt32* score) override;

/**
* Add VirtualSMC listening notification.
*
* @param provider parent IOService object
*
* @return true on success
*/
bool start(IOService *provider) override;

/**
* Update power state with the new one, here we catch sleep/wake/boot/shutdown calls
* New power state could be the reason for keystore to be saved to NVRAM, for example
*
* @param state power state index (must be below PowerStateMax)
* @param whatDevice power state device
*/
IOReturn setPowerState(unsigned long state, IOService *whatDevice) override;
};

0 comments on commit c25ae85

Please sign in to comment.