Files
MacMonitor/Sources/Hardware/AppleSMC/MMAppleSMCClient.m
gronodandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> acf5d03277 fix(smc): correct IOKit service name and SMCKeyData_t layout (fixes #32)
Two latent bugs kept all SMC telemetry dead:
- IOServiceMatching("AppleSMCClient") never matched — the kernel service
  is AppleSMC, so openWithError always failed and every SMC-gated provider
  (thermal, fan, component, power) reported unavailable.
- #pragma pack(1) shrank SMCKeyData_t to 74 bytes; the SMC user client
  requires the canonical 80-byte layout and rejected every
  IOConnectCallStructMethod with kIOReturnBadArgument. Verified live on
  Intel hardware: unpacked calls succeed and return key data.

Also sources the alert engine's batteryLevel from the SMC-independent
batteryHealth path (currentCapacity/maxCapacity), falling back to the
power provider.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-08 15:14:41 +01:00

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("AppleSMC");
if (!matching) {
os_unfair_lock_unlock(&_lock);
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Failed to create AppleSMC 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: @"AppleSMC 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