246 lines
7.5 KiB
Objective-C
246 lines
7.5 KiB
Objective-C
#import "MMAppleSMCClient.h"
|
|
#import <IOKit/IOKitLib.h>
|
|
#import <os/lock.h>
|
|
|
|
@interface MMAppleSMCClient () {
|
|
os_unfair_lock _lock;
|
|
io_connect_t _connection;
|
|
NSMutableDictionary<NSString *, NSValue *> *_keyInfoCache;
|
|
BOOL _opened;
|
|
NSInteger _totalKeyCount;
|
|
}
|
|
@end
|
|
|
|
@implementation MMAppleSMCClient
|
|
|
|
+ (instancetype)sharedClient {
|
|
static MMAppleSMCClient *instance = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
instance = [[self alloc] init];
|
|
});
|
|
return instance;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_lock = OS_UNFAIR_LOCK_INIT;
|
|
_keyInfoCache = [NSMutableDictionary dictionary];
|
|
[self openWithError:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self close];
|
|
}
|
|
|
|
- (BOOL)isAvailable {
|
|
os_unfair_lock_lock(&_lock);
|
|
BOOL avail = (_connection != IO_OBJECT_NULL && _opened);
|
|
os_unfair_lock_unlock(&_lock);
|
|
return avail;
|
|
}
|
|
|
|
- (NSInteger)totalKeyCount {
|
|
os_unfair_lock_lock(&_lock);
|
|
NSInteger count = _totalKeyCount;
|
|
os_unfair_lock_unlock(&_lock);
|
|
return count;
|
|
}
|
|
|
|
- (BOOL)openWithError:(NSError **)error {
|
|
os_unfair_lock_lock(&_lock);
|
|
if (_opened && _connection != IO_OBJECT_NULL) {
|
|
os_unfair_lock_unlock(&_lock);
|
|
return YES;
|
|
}
|
|
|
|
CFMutableDictionaryRef matching = IOServiceMatching("AppleSMCClient");
|
|
if (!matching) {
|
|
os_unfair_lock_unlock(&_lock);
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-1
|
|
userInfo:@{NSLocalizedDescriptionKey: @"Failed to create AppleSMCClient matching dictionary"}];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, matching);
|
|
if (service == IO_OBJECT_NULL) {
|
|
os_unfair_lock_unlock(&_lock);
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-2
|
|
userInfo:@{NSLocalizedDescriptionKey: @"AppleSMCClient service not found (running on non-Mac or unsupported VM)"}];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &_connection);
|
|
IOObjectRelease(service);
|
|
|
|
if (kr != KERN_SUCCESS || _connection == IO_OBJECT_NULL) {
|
|
_connection = IO_OBJECT_NULL;
|
|
_opened = NO;
|
|
os_unfair_lock_unlock(&_lock);
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:kr
|
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"IOServiceOpen failed with code: 0x%x", kr]}];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
_opened = YES;
|
|
os_unfair_lock_unlock(&_lock);
|
|
|
|
// Probe total keys count
|
|
NSNumber *keysCount = [self readNumericValueForKey:@"#KEY" error:nil];
|
|
if (keysCount) {
|
|
os_unfair_lock_lock(&_lock);
|
|
_totalKeyCount = [keysCount integerValue];
|
|
os_unfair_lock_unlock(&_lock);
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)close {
|
|
os_unfair_lock_lock(&_lock);
|
|
if (_connection != IO_OBJECT_NULL) {
|
|
IOServiceClose(_connection);
|
|
_connection = IO_OBJECT_NULL;
|
|
}
|
|
_opened = NO;
|
|
[_keyInfoCache removeAllObjects];
|
|
os_unfair_lock_unlock(&_lock);
|
|
}
|
|
|
|
- (BOOL)callSMCWithInput:(const SMCKeyData_t *)input output:(SMCKeyData_t *)output {
|
|
if (_connection == IO_OBJECT_NULL) return NO;
|
|
|
|
size_t inputSize = sizeof(SMCKeyData_t);
|
|
size_t outputSize = sizeof(SMCKeyData_t);
|
|
|
|
kern_return_t kr = IOConnectCallStructMethod(
|
|
_connection,
|
|
kSMCHandleYPCEvent,
|
|
input,
|
|
inputSize,
|
|
output,
|
|
&outputSize
|
|
);
|
|
|
|
return (kr == KERN_SUCCESS && output->result == 0);
|
|
}
|
|
|
|
- (BOOL)fetchKeyInfo:(NSString *)key info:(SMCKeyInfoData *)outInfo {
|
|
if (key.length != 4) return NO;
|
|
|
|
os_unfair_lock_lock(&_lock);
|
|
NSValue *cached = _keyInfoCache[key];
|
|
if (cached) {
|
|
[cached getValue:outInfo size:sizeof(SMCKeyInfoData)];
|
|
os_unfair_lock_unlock(&_lock);
|
|
return YES;
|
|
}
|
|
|
|
SMCKeyData_t input = {0};
|
|
SMCKeyData_t output = {0};
|
|
|
|
input.key = MMSMCToFourCharCode([key UTF8String]);
|
|
input.data8 = kSMCGetKeyInfo;
|
|
|
|
BOOL success = [self callSMCWithInput:&input output:&output];
|
|
if (success) {
|
|
*outInfo = output.keyInfo;
|
|
_keyInfoCache[key] = [NSValue value:outInfo withObjCType:@encode(SMCKeyInfoData)];
|
|
}
|
|
|
|
os_unfair_lock_unlock(&_lock);
|
|
return success;
|
|
}
|
|
|
|
- (nullable NSData *)readBytesForKey:(NSString *)key error:(NSError **)error {
|
|
if (![self isAvailable]) {
|
|
if (![self openWithError:error]) return nil;
|
|
}
|
|
|
|
SMCKeyInfoData info = {0};
|
|
if (![self fetchKeyInfo:key info:&info]) {
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-3
|
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Failed to retrieve key info for '%@'", key]}];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
if (info.dataSize > 32) {
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-4
|
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Data size %lu exceeds 32 bytes for key '%@'", (unsigned long)info.dataSize, key]}];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
os_unfair_lock_lock(&_lock);
|
|
SMCKeyData_t input = {0};
|
|
SMCKeyData_t output = {0};
|
|
|
|
input.key = MMSMCToFourCharCode([key UTF8String]);
|
|
input.keyInfo.dataSize = info.dataSize;
|
|
input.data8 = kSMCReadKey;
|
|
|
|
BOOL success = [self callSMCWithInput:&input output:&output];
|
|
os_unfair_lock_unlock(&_lock);
|
|
|
|
if (!success) {
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-5
|
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"SMC read command failed for key '%@'", key]}];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
return [NSData dataWithBytes:output.bytes length:info.dataSize];
|
|
}
|
|
|
|
- (nullable NSString *)readDataTypeForKey:(NSString *)key error:(NSError **)error {
|
|
SMCKeyInfoData info = {0};
|
|
if (![self fetchKeyInfo:key info:&info]) {
|
|
if (error) {
|
|
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
|
|
code:-3
|
|
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Failed to retrieve key info for '%@'", key]}];
|
|
}
|
|
return nil;
|
|
}
|
|
char typeStr[5] = {0};
|
|
MMSMCFromFourCharCode(info.dataType, typeStr);
|
|
return [NSString stringWithUTF8String:typeStr];
|
|
}
|
|
|
|
- (nullable NSNumber *)readNumericValueForKey:(NSString *)key error:(NSError **)error {
|
|
SMCKeyInfoData info = {0};
|
|
if (![self fetchKeyInfo:key info:&info]) {
|
|
return nil;
|
|
}
|
|
|
|
NSData *data = [self readBytesForKey:key error:error];
|
|
if (!data) return nil;
|
|
|
|
char typeStr[5] = {0};
|
|
MMSMCFromFourCharCode(info.dataType, typeStr);
|
|
NSString *dataType = [NSString stringWithUTF8String:typeStr];
|
|
|
|
return [MMSMCParser decodeValueWithDataType:dataType bytes:data.bytes size:data.length];
|
|
}
|
|
|
|
@end
|