1//
2// Copyright (c) 2017 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7// SystemInfo_mac.cpp: implementation of the Mac-specific parts of SystemInfo.h
8
9#include "gpu_info_util/SystemInfo_internal.h"
10
11#import <Cocoa/Cocoa.h>
12#import <IOKit/IOKitLib.h>
13
14namespace angle
15{
16
17namespace
18{
19
20std::string GetMachineModel()
21{
22    io_service_t platformExpert = IOServiceGetMatchingService(
23        kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
24
25    if (platformExpert == IO_OBJECT_NULL)
26    {
27        return "";
28    }
29
30    CFDataRef modelData = static_cast<CFDataRef>(
31        IORegistryEntryCreateCFProperty(platformExpert, CFSTR("model"), kCFAllocatorDefault, 0));
32    if (modelData == nullptr)
33    {
34        IOObjectRelease(platformExpert);
35        return "";
36    }
37
38    std::string result = reinterpret_cast<const char *>(CFDataGetBytePtr(modelData));
39
40    IOObjectRelease(platformExpert);
41    CFRelease(modelData);
42
43    return result;
44}
45
46// Extracts one integer property from a registry entry.
47bool GetEntryProperty(io_registry_entry_t entry, CFStringRef name, uint32_t *value)
48{
49    *value = 0;
50
51    CFDataRef data = static_cast<CFDataRef>(
52        IORegistryEntrySearchCFProperty(entry, kIOServicePlane, name, kCFAllocatorDefault,
53                                        kIORegistryIterateRecursively | kIORegistryIterateParents));
54
55    if (data == nullptr)
56    {
57        return false;
58    }
59
60    const uint32_t *valuePtr = reinterpret_cast<const uint32_t *>(CFDataGetBytePtr(data));
61
62    if (valuePtr == nullptr)
63    {
64        CFRelease(data);
65        return false;
66    }
67
68    *value = *valuePtr;
69    CFRelease(data);
70    return true;
71}
72
73// CGDisplayIOServicePort is deprecated as of macOS 10.9, but has no replacement, see
74// https://crbug.com/650837
75#pragma clang diagnostic push
76#pragma clang diagnostic ignored "-Wdeprecated-declarations"
77
78// Find the info of the current GPU.
79bool GetActiveGPU(VendorID *vendorId, DeviceID *deviceId)
80{
81    io_registry_entry_t port = CGDisplayIOServicePort(kCGDirectMainDisplay);
82
83    return GetEntryProperty(port, CFSTR("vendor-id"), vendorId) &&
84           GetEntryProperty(port, CFSTR("device-id"), deviceId);
85}
86
87#pragma clang diagnostic pop
88
89// Gathers the vendor and device IDs for the PCI GPUs
90bool GetPCIDevices(std::vector<GPUDeviceInfo> *devices)
91{
92    // matchDictionary will be consumed by IOServiceGetMatchingServices, no need to release it.
93    CFMutableDictionaryRef matchDictionary = IOServiceMatching("IOPCIDevice");
94
95    io_iterator_t entryIterator;
96    if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &entryIterator) !=
97        kIOReturnSuccess)
98    {
99        return false;
100    }
101
102    io_registry_entry_t entry = IO_OBJECT_NULL;
103
104    while ((entry = IOIteratorNext(entryIterator)) != IO_OBJECT_NULL)
105    {
106        constexpr uint32_t kClassCodeDisplayVGA = 0x30000;
107        uint32_t classCode;
108        GPUDeviceInfo info;
109
110        if (GetEntryProperty(entry, CFSTR("class-code"), &classCode) &&
111            classCode == kClassCodeDisplayVGA &&
112            GetEntryProperty(entry, CFSTR("vendor-id"), &info.vendorId) &&
113            GetEntryProperty(entry, CFSTR("device-id"), &info.deviceId))
114        {
115            devices->push_back(info);
116        }
117
118        IOObjectRelease(entry);
119    }
120    IOObjectRelease(entryIterator);
121
122    return true;
123}
124
125}  // anonymous namespace
126
127bool GetSystemInfo(SystemInfo *info)
128{
129    {
130        int32_t major = 0;
131        int32_t minor = 0;
132        ParseMacMachineModel(GetMachineModel(), &info->machineModelName, &major, &minor);
133        info->machineModelVersion = std::to_string(major) + "." + std::to_string(minor);
134    }
135
136    if (!GetPCIDevices(&(info->gpus)))
137    {
138        return false;
139    }
140
141    if (info->gpus.empty())
142    {
143        return false;
144    }
145
146    // Find the active GPU
147    {
148        VendorID activeVendor;
149        DeviceID activeDevice;
150        if (!GetActiveGPU(&activeVendor, &activeDevice))
151        {
152            return false;
153        }
154
155        for (size_t i = 0; i < info->gpus.size(); ++i)
156        {
157            if (info->gpus[i].vendorId == activeVendor && info->gpus[i].deviceId == activeDevice)
158            {
159                info->activeGPUIndex = i;
160                break;
161            }
162        }
163    }
164
165    FindPrimaryGPU(info);
166
167    return true;
168}
169
170}  // namespace angle
171