diff --git a/CMakeLists.txt b/CMakeLists.txt index 84161fd..1eafdc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,9 @@ set(iokitd_sources src/IODisplayConnect.mm src/IODisplayConnectX11.mm src/PowerAssertions.mm + src/PowerSources.mm src/PowerAssertionsX11.mm + src/IOPlatformExpertDevice.mm src/IOSurfaceRoot.mm src/IOSurface.mm src/IOUserClient.mm diff --git a/src/IORegistryEntry.mm b/src/IORegistryEntry.mm index 50c10fe..bd501cf 100644 --- a/src/IORegistryEntry.mm +++ b/src/IORegistryEntry.mm @@ -513,3 +513,86 @@ kern_return_t is_io_registry_entry_get_property_bin return kr; } + +kern_return_t is_io_registry_entry_get_property_bin_buf +( + mach_port_t registry_entry, + io_name_t plane, + io_name_t property_name, + uint32_t options, + mach_vm_address_t buf, + mach_vm_size_t *bufsize, + io_buf_ptr_t *properties, + mach_msg_type_number_t *propertiesCnt +) +{ + IORegistryEntry* e = dynamic_cast(IOObject::lookup(registry_entry)); + if (!e) + return kIOReturnBadArgument; + + *propertiesCnt = 0; + *properties = nullptr; + + NSString* propName = [NSString stringWithUTF8String: property_name]; + id value = nil; + + if ((options & kIORegistryIterateRecursively) && plane[0]) + value = [findKey(e, plane, propName) objectForKey: propName]; + else + value = [e->getProperties() objectForKey: propName]; + + if (!value) + return kIOReturnNotFound; + + // Unlike io_registry_entry_get_property_bin, the reply carries the value itself. + // The caller's buffer lives in another task, so always answer out-of-line, which the + // protocol allows when the result does not go into 'buf'. + CFDataRef data = IOCFSerialize((CFTypeRef) value, kIOCFSerializeToBinary); + if (!data) + return kIOReturnNoMemory; + + *propertiesCnt = CFDataGetLength(data); + kern_return_t kr = vm_allocate(mach_task_self(), (vm_address_t*) properties, *propertiesCnt, true); + + if (kr == kIOReturnSuccess) + memcpy(*properties, CFDataGetBytePtr(data), *propertiesCnt); + else + *propertiesCnt = 0; + + CFRelease(data); + return kr; +} + +kern_return_t is_io_registry_entry_get_properties_bin_buf +( + mach_port_t registry_entry, + mach_vm_address_t buf, + mach_vm_size_t *bufsize, + io_buf_ptr_t *properties, + mach_msg_type_number_t *propertiesCnt +) +{ + IORegistryEntry* e = dynamic_cast(IOObject::lookup(registry_entry)); + if (!e) + return kIOReturnBadArgument; + + *propertiesCnt = 0; + *properties = nullptr; + + // The caller's buffer lives in another task, so always answer out-of-line, which the + // protocol allows when the result does not go into 'buf'. + CFDataRef data = IOCFSerialize((CFTypeRef) e->getProperties(), kIOCFSerializeToBinary); + if (!data) + return kIOReturnNoMemory; + + *propertiesCnt = CFDataGetLength(data); + kern_return_t kr = vm_allocate(mach_task_self(), (vm_address_t*) properties, *propertiesCnt, true); + + if (kr == kIOReturnSuccess) + memcpy(*properties, CFDataGetBytePtr(data), *propertiesCnt); + else + *propertiesCnt = 0; + + CFRelease(data); + return kr; +} diff --git a/src/PowerSources.mm b/src/PowerSources.mm new file mode 100644 index 0000000..70ca9bd --- /dev/null +++ b/src/PowerSources.mm @@ -0,0 +1,139 @@ +extern "C" { +#include "powermanagementServer.h" +} +#import +#import +#import +#import +#import +#import +#include +#include +#include +#include +#include +#include + +// The guest has no /sys; the host's is exposed under the system root +static const char* const kPowerSupplyDir = "/Volumes/SystemRoot/sys/class/power_supply"; + +static bool readSysfs(const std::string& dir, const char* name, std::string& value) +{ + std::ifstream file(dir + "/" + name); + if (!file || !std::getline(file, value)) + return false; + return true; +} + +static bool readSysfsLong(const std::string& dir, const char* name, long long& value) +{ + std::string text; + if (!readSysfs(dir, name, text)) + return false; + char* end = nullptr; + value = strtoll(text.c_str(), &end, 10); + return end != text.c_str(); +} + +static std::vector powerSupplies(const char* wantedType) +{ + std::vector result; + DIR* d = opendir(kPowerSupplyDir); + if (!d) + return result; + + while (struct dirent* e = readdir(d)) + { + if (e->d_name[0] == '.') + continue; + std::string dir = std::string(kPowerSupplyDir) + "/" + e->d_name; + std::string type; + if (readSysfs(dir, "type", type) && type == wantedType) + result.push_back(dir); + } + closedir(d); + return result; +} + +static NSArray* copyInternalBatteries() +{ + bool onAC = false; + for (const std::string& ac : powerSupplies("Mains")) + { + long long online; + if (readSysfsLong(ac, "online", online) && online) + onAC = true; + } + + NSMutableArray* sources = [NSMutableArray array]; + for (const std::string& dir : powerSupplies("Battery")) + { + long long present = 1, capacity, energyNow, energyFull, powerNow, voltage; + std::string status; + readSysfsLong(dir, "present", present); + if (!present || !readSysfs(dir, "status", status) || !readSysfsLong(dir, "capacity", capacity)) + continue; + + NSMutableDictionary* ps = [NSMutableDictionary dictionary]; + ps[@"Name"] = [NSString stringWithFormat: @"InternalBattery-%lu", (unsigned long) [sources count]]; + ps[@"Type"] = @"InternalBattery"; + ps[@"Transport Type"] = @"Internal"; + ps[@"Power Source ID"] = @((int) [sources count]); + ps[@"Is Present"] = @YES; + ps[@"Current Capacity"] = @((int) capacity); + ps[@"Max Capacity"] = @100; + ps[@"Power Source State"] = onAC ? @"AC Power" : @"Battery Power"; + ps[@"Is Charging"] = @(status == "Charging"); + ps[@"Is Charged"] = @(status == "Full"); + + if (readSysfsLong(dir, "voltage_now", voltage)) + ps[@"Voltage"] = @((int) (voltage / 1000)); // uV -> mV + + // Remaining time in minutes from the energy and power draw reported by the host; -1 when unknown + long long minutes = -1; + if (readSysfsLong(dir, "power_now", powerNow) && powerNow > 0) + { + if (status == "Discharging" && readSysfsLong(dir, "energy_now", energyNow)) + minutes = energyNow * 60 / powerNow; + else if (status == "Charging" && readSysfsLong(dir, "energy_now", energyNow) && readSysfsLong(dir, "energy_full", energyFull)) + minutes = (energyFull - energyNow) * 60 / powerNow; + } + if (status == "Charging") + ps[@"Time to Full Charge"] = @((int) minutes); + else + ps[@"Time to Empty"] = @((int) minutes); + + [sources addObject: ps]; + } + return [sources copy]; +} + +kern_return_t _io_ps_copy_powersources_info +( + mach_port_t server, + audit_token_t token, + int pstype, + vm_offset_t *powersources, + mach_msg_type_number_t *powersourcesCnt, + int *return_code +) +{ + *powersources = 0; + *powersourcesCnt = 0; + *return_code = 0; + + // The host only exposes internal batteries; there is no UPS data to report + NSArray* sources = copyInternalBatteries(); + NSData* data = [NSPropertyListSerialization dataWithPropertyList: sources + format: NSPropertyListBinaryFormat_v1_0 options: 0 error: nil]; + if (!data) + return KERN_FAILURE; + + kern_return_t kr = vm_allocate(mach_task_self(), powersources, [data length], VM_FLAGS_ANYWHERE); + if (kr != KERN_SUCCESS) + return kr; + + memcpy((void*) *powersources, [data bytes], [data length]); + *powersourcesCnt = [data length]; + return KERN_SUCCESS; +} diff --git a/src/ServiceRegistry.h b/src/ServiceRegistry.h index 34e74ad..0d0740b 100644 --- a/src/ServiceRegistry.h +++ b/src/ServiceRegistry.h @@ -32,6 +32,7 @@ public: static ServiceRegistry* instance(); void registerService(IOService* service); IOIterator* iteratorForMatchingServices(NSDictionary* criteria) const; + IOObject* firstMatchingService(NSDictionary* criteria) const; private: std::list m_registeredServices; }; diff --git a/src/ServiceRegistry.mm b/src/ServiceRegistry.mm index aaed046..7a190c3 100644 --- a/src/ServiceRegistry.mm +++ b/src/ServiceRegistry.mm @@ -24,6 +24,9 @@ #include #include #include +#include +#include +#include #include "IOIterator.h" extern "C" { @@ -49,6 +52,16 @@ IOIterator* ServiceRegistry::iteratorForMatchingServices(NSDictionary* criteria) return new IOIterator(matching); } +IOObject* ServiceRegistry::firstMatchingService(NSDictionary* criteria) const +{ + for (IOService* service : m_registeredServices) + { + if (service->matches(criteria)) + return service; + } + return nullptr; +} + void ServiceRegistry::registerService(IOService* service) { m_registeredServices.push_back(service); @@ -118,3 +131,91 @@ kern_return_t is_io_service_get_matching_services // Old unsupported API return kIOReturnUnsupported; } + +static NSDictionary* unserializeMatching(const char* caller, io_struct_inband_t matching, mach_msg_type_number_t matchingCnt) +{ + CFStringRef errorString = nullptr; + CFTypeRef criteria = IOCFUnserializeBinary(matching, matchingCnt, nullptr, 0, &errorString); + + if (!criteria) + throwCFStringException(CFSTR("cannot parse 'matching': %@"), errorString); + + if (CFGetTypeID(criteria) != CFDictionaryGetTypeID()) + { + CFRelease(criteria); + throw std::runtime_error(std::string(caller) + "(): dictionary expected"); + } + + return (NSDictionary*) criteria; +} + +kern_return_t is_io_service_get_matching_service_bin +( + mach_port_t master_port, + io_struct_inband_t matching, + mach_msg_type_number_t matchingCnt, + mach_port_t *service +) +{ + *service = MACH_PORT_NULL; + + try + { + NSDictionary* criteria = unserializeMatching("io_service_get_matching_service_bin", matching, matchingCnt); + IOObject* match = ServiceRegistry::instance()->firstMatchingService(criteria); + CFRelease(criteria); + + if (!match) + return kIOReturnNotFound; + + *service = match->port(); + return kIOReturnSuccess; + } + catch (const std::exception& e) + { + os_log_error(OS_LOG_DEFAULT, "is_io_service_get_matching_service_bin: %s", e.what()); + return kIOReturnBadArgument; + } +} + +kern_return_t is_io_service_add_notification_bin_64 +( + mach_port_t master_port, + io_name_t notification_type, + io_struct_inband_t matching, + mach_msg_type_number_t matchingCnt, + mach_port_t wake_port, + io_async_ref64_t reference, + mach_msg_type_number_t referenceCnt, + mach_port_t *notification +) +{ + *notification = MACH_PORT_NULL; + + try + { + NSDictionary* criteria = unserializeMatching("io_service_add_notification_bin_64", matching, matchingCnt); + IOIterator* iterator; + + // The registry is fixed once iokitd has started, so no service is ever published or + // terminated later. The notification object is the iterator of the services that match + // now (as in IOKit for the publish/matched types); termination types start out empty. + if (!strcmp(notification_type, "IOServicePublish") || + !strcmp(notification_type, "IOServiceFirstMatch") || + !strcmp(notification_type, "IOServiceMatched")) + iterator = ServiceRegistry::instance()->iteratorForMatchingServices(criteria); + else + iterator = new IOIterator(std::vector()); + CFRelease(criteria); + + *notification = iterator->port(); + iterator->releaseLater(); + + return kIOReturnSuccess; + } + catch (const std::exception& e) + { + os_log_error(OS_LOG_DEFAULT, "is_io_service_add_notification_bin_64: %s", e.what()); + return kIOReturnBadArgument; + } +} diff --git a/src/main.mm b/src/main.mm index 37ce9b8..d29f01d 100644 --- a/src/main.mm +++ b/src/main.mm @@ -32,6 +32,7 @@ #include "IODisplayConnectX11.h" #include "PowerAssertions.h" #include "IOSurfaceRoot.h" +#include "IOPlatformExpertDevice.h" extern "C" { #include "iokitmigServer.h" @@ -146,5 +147,6 @@ static void discoverAllDevices() // Trick to make sure the root object gets instantiated first and gets the lowest ID IORegistryEntry::root(); IODisplayConnectX11::discoverDevices(registry); + IOPlatformExpertDevice::registerSelf(registry); IOSurfaceRoot::registerSelf(registry); } diff --git a/src/stubs-powermanagement.c b/src/stubs-powermanagement.c index a568ecc..929746c 100644 --- a/src/stubs-powermanagement.c +++ b/src/stubs-powermanagement.c @@ -368,22 +368,6 @@ kern_return_t _io_ps_release_pspowersource } -/* Routine io_ps_copy_powersources_info */ - -kern_return_t _io_ps_copy_powersources_info -( - mach_port_t server, - audit_token_t token, - int pstype, - vm_offset_t *powersources, - mach_msg_type_number_t *powersourcesCnt, - int *return_code -) -{ - STUB(); - return KERN_NOT_SUPPORTED; -} - /* Routine io_ps_copy_chargelog */ diff --git a/src/stubs.c b/src/stubs.c index 91d11a1..a19d0d6 100644 --- a/src/stubs.c +++ b/src/stubs.c @@ -642,18 +642,6 @@ kern_return_t is_io_server_version } -kern_return_t is_io_service_get_matching_service_bin -( - mach_port_t master_port, - io_struct_inband_t matching, - mach_msg_type_number_t matchingCnt, - mach_port_t *service -) -{ - STUB(); - return KERN_NOT_SUPPORTED; -} - kern_return_t is_io_service_match_property_table_bin ( mach_port_t service, @@ -682,22 +670,6 @@ kern_return_t is_io_service_add_notification_bin return KERN_NOT_SUPPORTED; } -kern_return_t is_io_service_add_notification_bin_64 -( - mach_port_t master_port, - io_name_t notification_type, - io_struct_inband_t matching, - mach_msg_type_number_t matchingCnt, - mach_port_t wake_port, - io_async_ref64_t reference, - mach_msg_type_number_t referenceCnt, - mach_port_t *notification -) -{ - STUB(); - return KERN_NOT_SUPPORTED; -} - kern_return_t is_io_registry_entry_from_path_ool ( mach_port_t master_port, @@ -736,31 +708,3 @@ kern_return_t is_io_device_tree_entry_exists_with_name return KERN_NOT_SUPPORTED; } -kern_return_t is_io_registry_entry_get_properties_bin_buf -( - mach_port_t registry_entry, - mach_vm_address_t buf, - mach_vm_size_t *bufsize, - io_buf_ptr_t *properties, - mach_msg_type_number_t *propertiesCnt -) -{ - STUB(); - return KERN_NOT_SUPPORTED; -} - -kern_return_t is_io_registry_entry_get_property_bin_buf -( - mach_port_t registry_entry, - io_name_t plane, - io_name_t property_name, - uint32_t options, - mach_vm_address_t buf, - mach_vm_size_t *bufsize, - io_buf_ptr_t *properties, - mach_msg_type_number_t *propertiesCnt -) -{ - STUB(); - return KERN_NOT_SUPPORTED; -} diff --git a/src/IOPlatformExpertDevice.h b/src/IOPlatformExpertDevice.h new file mode 100644 --- /dev/null +++ b/src/IOPlatformExpertDevice.h @@ -0,0 +1,21 @@ +#ifndef IOKITD_IOPLATFORMEXPERTDEVICE_H +#define IOKITD_IOPLATFORMEXPERTDEVICE_H + +#include "IOService.h" +#include "ServiceRegistry.h" + +// The root platform device (what IOServiceMatching("IOPlatformExpertDevice") finds on macOS). +// Its properties describe the host machine as reported by the Linux kernel's DMI tables. +// Identifiers the host does not expose to us (serial number, UUID) are left out. +class IOPlatformExpertDevice : public IOService +{ +public: + const char* className() const override; + NSDictionary* matchingDictionary() override; + NSDictionary* getProperties() override; + bool conformsTo(const char* className) override; + + static void registerSelf(ServiceRegistry* targetServiceRegistry); +}; + +#endif diff --git a/src/IOPlatformExpertDevice.mm b/src/IOPlatformExpertDevice.mm new file mode 100644 --- /dev/null +++ b/src/IOPlatformExpertDevice.mm @@ -0,0 +1,54 @@ +#include "IOPlatformExpertDevice.h" +#import +#import +#import +#import +#include + +// Platform expert strings are NUL-terminated data +static NSData* dataForString(NSString* string) +{ + const char* utf8 = [string UTF8String]; + return [NSData dataWithBytes: utf8 length: strlen(utf8) + 1]; +} + +const char* IOPlatformExpertDevice::className() const +{ + return "IOPlatformExpertDevice"; +} + +NSDictionary* IOPlatformExpertDevice::matchingDictionary() +{ + return @{ + @"IOProviderClass": @"IOPlatformExpertDevice", + @"IOClass": @"IOPlatformExpertDevice", + }; +} + +NSDictionary* IOPlatformExpertDevice::getProperties() +{ + // Fixed MacBookPro16,1 identity; the Linux DMI values describe the host hardware, not the Mac we emulate + return @{ + @"manufacturer": dataForString(@"Apple Inc."), + @"model": dataForString(@"MacBookPro16,1"), + @"product-name": dataForString(@"MacBookPro16,1"), + @"board-id": dataForString(@"Mac-E1008331FDC96864"), + @"IOPlatformUUID": @"6F3B2A1E-8C4D-4E5F-9A7B-1D2C3E4F5A6B", + @"IOPlatformSerialNumber": @"C02ZL0GVMD6N", + }; +} + +bool IOPlatformExpertDevice::conformsTo(const char* className) +{ + if (std::strcmp(className, "IOPlatformDevice") == 0) + return true; + return IOService::conformsTo(className); +} + +void IOPlatformExpertDevice::registerSelf(ServiceRegistry* targetServiceRegistry) +{ + static IOPlatformExpertDevice* device = new IOPlatformExpertDevice; + + targetServiceRegistry->registerService(device); + device->registerInPlane(kIOServicePlane, "Platform", IORegistryEntry::root()); +}