Atoll/DynamicIsland/utils/AppleHardwareInfo.swift
2026-01-29 12:26:04 +05:30

173 lines
6.3 KiB
Swift

/*
* Atoll (DynamicIsland)
* Copyright (C) 2024-2026 Atoll Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Foundation
import IOKit
import Darwin
enum ApplePlatform: String {
case intel
case m1
case m1Pro
case m1Max
case m1Ultra
case m2
case m2Pro
case m2Max
case m2Ultra
case m3
case m3Pro
case m3Max
case m3Ultra
case m4
case m4Pro
case m4Max
case m4Ultra
}
struct CPUClusterFrequencies {
let eCoreFrequencies: [Int32]
let pCoreFrequencies: [Int32]
}
struct CPUClusterCounts {
let eCores: Int
let pCores: Int
}
final class AppleHardwareInfo {
static let shared = AppleHardwareInfo()
let cpuName: String?
let platform: ApplePlatform?
let logicalCoreCount: Int
let clusterCounts: CPUClusterCounts
let clusterFrequencies: CPUClusterFrequencies
private init() {
let name = AppleHardwareInfo.fetchCPUName()
cpuName = name
platform = AppleHardwareInfo.detectPlatform(from: name)
logicalCoreCount = AppleHardwareInfo.fetchLogicalCoreCount()
clusterCounts = AppleHardwareInfo.fetchClusterCounts()
clusterFrequencies = AppleHardwareInfo.fetchClusterFrequencies(cpuName: name ?? "")
}
private static func fetchCPUName() -> String? {
var size = 0
sysctlbyname("machdep.cpu.brand_string", nil, &size, nil, 0)
guard size > 0 else { return nil }
var buffer = [CChar](repeating: 0, count: size)
let result = sysctlbyname("machdep.cpu.brand_string", &buffer, &size, nil, 0)
guard result == 0 else { return nil }
return String(cString: buffer)
}
private static func fetchLogicalCoreCount() -> Int {
var cores: Int32 = 0
var size = MemoryLayout<Int32>.size
let result = sysctlbyname("hw.logicalcpu", &cores, &size, nil, 0)
return result == 0 ? Int(cores) : ProcessInfo.processInfo.processorCount
}
private static func detectPlatform(from cpuName: String?) -> ApplePlatform? {
guard let name = cpuName?.lowercased() else { return nil }
if name.contains("intel") { return .intel }
if name.contains("m1") {
if name.contains("ultra") { return .m1Ultra }
if name.contains("max") { return .m1Max }
if name.contains("pro") { return .m1Pro }
return .m1
}
if name.contains("m2") {
if name.contains("ultra") { return .m2Ultra }
if name.contains("max") { return .m2Max }
if name.contains("pro") { return .m2Pro }
return .m2
}
if name.contains("m3") {
if name.contains("ultra") { return .m3Ultra }
if name.contains("max") { return .m3Max }
if name.contains("pro") { return .m3Pro }
return .m3
}
if name.contains("m4") {
if name.contains("ultra") { return .m4Ultra }
if name.contains("max") { return .m4Max }
if name.contains("pro") { return .m4Pro }
return .m4
}
return nil
}
private static func fetchClusterCounts() -> CPUClusterCounts {
var iterator = io_iterator_t()
guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMPE"), &iterator) == KERN_SUCCESS else {
return CPUClusterCounts(eCores: 0, pCores: 0)
}
defer { IOObjectRelease(iterator) }
var eCoreCount: Int32 = 0
var pCoreCount: Int32 = 0
while case let service = IOIteratorNext(iterator), service != 0 {
defer { IOObjectRelease(service) }
var children = io_iterator_t()
guard IORegistryEntryGetChildIterator(service, kIOServicePlane, &children) == KERN_SUCCESS else { continue }
defer { IOObjectRelease(children) }
while case let child = IOIteratorNext(children), child != 0 {
defer { IOObjectRelease(child) }
guard let name = di_getIOName(child), name == "cpus", let props = di_getIOProperties(child) else { continue }
if let data = props.object(forKey: "e-core-count") as? Data {
eCoreCount = data.withUnsafeBytes { $0.load(as: Int32.self) }
}
if let data = props.object(forKey: "p-core-count") as? Data {
pCoreCount = data.withUnsafeBytes { $0.load(as: Int32.self) }
}
}
}
return CPUClusterCounts(eCores: Int(eCoreCount), pCores: Int(pCoreCount))
}
private static func fetchClusterFrequencies(cpuName: String) -> CPUClusterFrequencies {
var iterator = io_iterator_t()
guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMIODevice"), &iterator) == KERN_SUCCESS else {
return CPUClusterFrequencies(eCoreFrequencies: [], pCoreFrequencies: [])
}
defer { IOObjectRelease(iterator) }
let lowerName = cpuName.lowercased()
let isM4 = lowerName.contains("m4")
var eFreq: [Int32] = []
var pFreq: [Int32] = []
while case let service = IOIteratorNext(iterator), service != 0 {
defer { IOObjectRelease(service) }
guard let name = di_getIOName(service), name == "pmgr", let props = di_getIOProperties(service) else { continue }
if let data = props["voltage-states1-sram"] as? Data {
eFreq = di_convertCFDataToFrequencies(data, isM4: isM4)
}
if let data = props["voltage-states5-sram"] as? Data {
pFreq = di_convertCFDataToFrequencies(data, isM4: isM4)
}
}
return CPUClusterFrequencies(eCoreFrequencies: eFreq, pCoreFrequencies: pFreq)
}
}