Collect device information on Metal

Becuase CGDisplayIOServicePort is deprecated in OSX >= 10.9, we create
an alternative function which manually finding a service port with
matching vendor and product IDs.

BUG=dawn:10
TEST=dawn_end2end_tests

Change-Id: I94ff65911e159c2b7075209d8902c1551560ed47
Reviewed-on: https://dawn-review.googlesource.com/c/2541
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 2408412..1a4922f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -478,6 +478,7 @@
     libs += [
       "Metal.framework",
       "Cocoa.framework",
+      "IOKit.framework",
     ]
     sources += [
       "src/dawn_native/metal/BlendStateMTL.h",
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 4d62e5d..a01ab6a 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -33,10 +33,96 @@
 #include "dawn_native/metal/SwapChainMTL.h"
 #include "dawn_native/metal/TextureMTL.h"
 
+#include <IOKit/graphics/IOGraphicsLib.h>
 #include <unistd.h>
 
 namespace dawn_native { namespace metal {
 
+    namespace {
+        // Since CGDisplayIOServicePort was deprecated in macOS 10.9, we need create
+        // an alternative function for getting I/O service port from current display.
+        io_service_t GetDisplayIOServicePort() {
+            // The matching service port (or 0 if none can be found)
+            io_service_t servicePort = 0;
+
+            // Create matching dictionary for display service
+            CFMutableDictionaryRef matchingDict = IOServiceMatching("IODisplayConnect");
+            if (matchingDict == nullptr) {
+                return 0;
+            }
+
+            io_iterator_t iter;
+            // IOServiceGetMatchingServices look up the default master ports that match a
+            // matching dictionary, and will consume the reference on the matching dictionary,
+            // so we don't need to release the dictionary, but the iterator handle should
+            // be released when its iteration is finished.
+            if (IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter) !=
+                kIOReturnSuccess) {
+                return 0;
+            }
+
+            // Vendor number and product number of current main display
+            const uint32_t displayVendorNumber = CGDisplayVendorNumber(kCGDirectMainDisplay);
+            const uint32_t displayProductNumber = CGDisplayModelNumber(kCGDirectMainDisplay);
+
+            io_service_t serv;
+            while ((serv = IOIteratorNext(iter)) != IO_OBJECT_NULL) {
+                CFDictionaryRef displayInfo =
+                    IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
+
+                CFNumberRef vendorIDRef, productIDRef;
+                Boolean success;
+                // The ownership of CF object follows the 'Get Rule', we don't need to
+                // release these values
+                success = CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplayVendorID),
+                                                        (const void**)&vendorIDRef);
+                success &= CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplayProductID),
+                                                         (const void**)&productIDRef);
+                if (success) {
+                    CFIndex vendorID = 0, productID = 0;
+                    CFNumberGetValue(vendorIDRef, kCFNumberSInt32Type, &vendorID);
+                    CFNumberGetValue(productIDRef, kCFNumberSInt32Type, &productID);
+
+                    if (vendorID == displayVendorNumber && productID == displayProductNumber) {
+                        // Check if vendor id and product id match with current display's
+                        // If it does, we find the desired service port
+                        servicePort = serv;
+                        CFRelease(displayInfo);
+                        break;
+                    }
+                }
+
+                CFRelease(displayInfo);
+                IOObjectRelease(serv);
+            }
+            IOObjectRelease(iter);
+            return servicePort;
+        }
+
+        // Get integer property from registry entry.
+        uint32_t GetEntryProperty(io_registry_entry_t entry, CFStringRef name) {
+            uint32_t value = 0;
+
+            // Recursively search registry entry and its parents for property name
+            // The data should release with CFRelease
+            CFDataRef data = static_cast<CFDataRef>(IORegistryEntrySearchCFProperty(
+                entry, kIOServicePlane, name, kCFAllocatorDefault,
+                kIORegistryIterateRecursively | kIORegistryIterateParents));
+
+            if (data != nullptr) {
+                const uint32_t* valuePtr =
+                    reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data));
+                if (valuePtr) {
+                    value = *valuePtr;
+                }
+
+                CFRelease(data);
+            }
+
+            return value;
+        }
+    }  // anonymous namespace
+
     dawnDevice CreateDevice(id<MTLDevice> metalDevice) {
         return reinterpret_cast<dawnDevice>(new Device(metalDevice));
     }
@@ -200,8 +286,15 @@
         return mResourceUploader.get();
     }
 
-    // TODO(jiawei.shao@intel.com): collect device information on Metal
     void Device::CollectPCIInfo() {
+        io_registry_entry_t entry = GetDisplayIOServicePort();
+        if (entry != IO_OBJECT_NULL) {
+            mPCIInfo.vendorId = GetEntryProperty(entry, CFSTR("vendor-id"));
+            mPCIInfo.deviceId = GetEntryProperty(entry, CFSTR("device-id"));
+            IOObjectRelease(entry);
+        }
+
+        mPCIInfo.name = std::string([mMtlDevice.name UTF8String]);
     }
 
 }}  // namespace dawn_native::metal
diff --git a/src/tests/end2end/TextureViewTests.cpp b/src/tests/end2end/TextureViewTests.cpp
index a0ee5ad..acccc23 100644
--- a/src/tests/end2end/TextureViewTests.cpp
+++ b/src/tests/end2end/TextureViewTests.cpp
@@ -365,7 +365,7 @@
 
 // Test sampling from a 2D array texture view created on a 2D array texture.
 TEST_P(TextureViewTest, Texture2DArrayViewOn2DArrayTexture) {
-    DAWN_SKIP_TEST_IF(IsMetal());
+    DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
     Texture2DArrayViewTest(6, 1, 2, 0);
 }
 
@@ -381,7 +381,7 @@
 
 // Test sampling from a 2D array texture view created on a mipmap level of a 2D array texture.
 TEST_P(TextureViewTest, Texture2DArrayViewOnOneLevelOf2DArrayTexture) {
-    DAWN_SKIP_TEST_IF(IsMetal());
+    DAWN_SKIP_TEST_IF(IsMetal() && IsIntel());
     Texture2DArrayViewTest(6, 6, 2, 4);
 }