Home | History | Annotate | Download | only in config
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "gpu/config/gpu_info_collector.h"
      6 
      7 #include <X11/Xlib.h>
      8 #include <vector>
      9 
     10 #include "base/command_line.h"
     11 #include "base/debug/trace_event.h"
     12 #include "base/file_util.h"
     13 #include "base/logging.h"
     14 #include "base/memory/scoped_ptr.h"
     15 #include "base/message_loop/message_loop.h"
     16 #include "base/strings/string_piece.h"
     17 #include "base/strings/string_split.h"
     18 #include "base/strings/string_tokenizer.h"
     19 #include "base/strings/string_util.h"
     20 #include "library_loaders/libpci.h"
     21 #include "third_party/libXNVCtrl/NVCtrl.h"
     22 #include "third_party/libXNVCtrl/NVCtrlLib.h"
     23 #include "ui/gl/gl_bindings.h"
     24 #include "ui/gl/gl_context.h"
     25 #include "ui/gl/gl_implementation.h"
     26 #include "ui/gl/gl_surface.h"
     27 #include "ui/gl/gl_switches.h"
     28 
     29 namespace gpu {
     30 
     31 namespace {
     32 
     33 // This checks if a system supports PCI bus.
     34 // We check the existence of /sys/bus/pci or /sys/bug/pci_express.
     35 bool IsPciSupported() {
     36   const base::FilePath pci_path("/sys/bus/pci/");
     37   const base::FilePath pcie_path("/sys/bus/pci_express/");
     38   return (base::PathExists(pci_path) ||
     39           base::PathExists(pcie_path));
     40 }
     41 
     42 // Scan /etc/ati/amdpcsdb.default for "ReleaseVersion".
     43 // Return empty string on failing.
     44 std::string CollectDriverVersionATI() {
     45   const base::FilePath::CharType kATIFileName[] =
     46       FILE_PATH_LITERAL("/etc/ati/amdpcsdb.default");
     47   base::FilePath ati_file_path(kATIFileName);
     48   if (!base::PathExists(ati_file_path))
     49     return std::string();
     50   std::string contents;
     51   if (!base::ReadFileToString(ati_file_path, &contents))
     52     return std::string();
     53   base::StringTokenizer t(contents, "\r\n");
     54   while (t.GetNext()) {
     55     std::string line = t.token();
     56     if (StartsWithASCII(line, "ReleaseVersion=", true)) {
     57       size_t begin = line.find_first_of("0123456789");
     58       if (begin != std::string::npos) {
     59         size_t end = line.find_first_not_of("0123456789.", begin);
     60         if (end == std::string::npos)
     61           return line.substr(begin);
     62         else
     63           return line.substr(begin, end - begin);
     64       }
     65     }
     66   }
     67   return std::string();
     68 }
     69 
     70 // Use NVCtrl extention to query NV driver version.
     71 // Return empty string on failing.
     72 std::string CollectDriverVersionNVidia() {
     73   Display* display = base::MessagePumpForUI::GetDefaultXDisplay();
     74   if (!display) {
     75     LOG(ERROR) << "XOpenDisplay failed.";
     76     return std::string();
     77   }
     78   int event_base = 0, error_base = 0;
     79   if (!XNVCTRLQueryExtension(display, &event_base, &error_base)) {
     80     VLOG(1) << "NVCtrl extension does not exist.";
     81     return std::string();
     82   }
     83   int screen_count = ScreenCount(display);
     84   for (int screen = 0; screen < screen_count; ++screen) {
     85     char* buffer = NULL;
     86     if (XNVCTRLIsNvScreen(display, screen) &&
     87         XNVCTRLQueryStringAttribute(display, screen, 0,
     88                                     NV_CTRL_STRING_NVIDIA_DRIVER_VERSION,
     89                                     &buffer)) {
     90       std::string driver_version(buffer);
     91       XFree(buffer);
     92       return driver_version;
     93     }
     94   }
     95   return std::string();
     96 }
     97 
     98 const uint32 kVendorIDIntel = 0x8086;
     99 const uint32 kVendorIDNVidia = 0x10de;
    100 const uint32 kVendorIDAMD = 0x1002;
    101 
    102 bool CollectPCIVideoCardInfo(GPUInfo* gpu_info) {
    103   DCHECK(gpu_info);
    104 
    105   if (IsPciSupported() == false) {
    106     VLOG(1) << "PCI bus scanning is not supported";
    107     return false;
    108   }
    109 
    110   // TODO(zmo): be more flexible about library name.
    111   LibPciLoader libpci_loader;
    112   if (!libpci_loader.Load("libpci.so.3") &&
    113       !libpci_loader.Load("libpci.so")) {
    114     VLOG(1) << "Failed to locate libpci";
    115     return false;
    116   }
    117 
    118   pci_access* access = (libpci_loader.pci_alloc)();
    119   DCHECK(access != NULL);
    120   (libpci_loader.pci_init)(access);
    121   (libpci_loader.pci_scan_bus)(access);
    122   bool primary_gpu_identified = false;
    123   for (pci_dev* device = access->devices;
    124        device != NULL; device = device->next) {
    125     // Fill the IDs and class fields.
    126     (libpci_loader.pci_fill_info)(device, 33);
    127     // TODO(zmo): there might be other classes that qualify as display devices.
    128     if (device->device_class != 0x0300)  // Device class is DISPLAY_VGA.
    129       continue;
    130 
    131     GPUInfo::GPUDevice gpu;
    132     gpu.vendor_id = device->vendor_id;
    133     gpu.device_id = device->device_id;
    134 
    135     if (!primary_gpu_identified) {
    136       primary_gpu_identified = true;
    137       gpu_info->gpu = gpu;
    138     } else {
    139       // TODO(zmo): if there are multiple GPUs, we assume the non Intel
    140       // one is primary. Revisit this logic because we actually don't know
    141       // which GPU we are using at this point.
    142       if (gpu_info->gpu.vendor_id == kVendorIDIntel &&
    143           gpu.vendor_id != kVendorIDIntel) {
    144         gpu_info->secondary_gpus.push_back(gpu_info->gpu);
    145         gpu_info->gpu = gpu;
    146       } else {
    147         gpu_info->secondary_gpus.push_back(gpu);
    148       }
    149     }
    150   }
    151 
    152   // Detect Optimus or AMD Switchable GPU.
    153   if (gpu_info->secondary_gpus.size() == 1 &&
    154       gpu_info->secondary_gpus[0].vendor_id == kVendorIDIntel) {
    155     if (gpu_info->gpu.vendor_id == kVendorIDNVidia)
    156       gpu_info->optimus = true;
    157     if (gpu_info->gpu.vendor_id == kVendorIDAMD)
    158       gpu_info->amd_switchable = true;
    159   }
    160 
    161   (libpci_loader.pci_cleanup)(access);
    162   return (primary_gpu_identified);
    163 }
    164 
    165 // Find the first GPU in |secondary_gpus| list that matches |active_vendor|
    166 // and switch it to primary gpu.
    167 // Return false if we cannot find a match.
    168 bool FindAndSetActiveGPU(GPUInfo* gpu_info, uint32 active_vendor) {
    169   DCHECK(gpu_info);
    170   GPUInfo::GPUDevice* device = NULL;
    171   for (size_t ii = 0; ii < gpu_info->secondary_gpus.size(); ++ii) {
    172     if (gpu_info->secondary_gpus[ii].vendor_id == active_vendor) {
    173       device = &(gpu_info->secondary_gpus[ii]);
    174       break;
    175     }
    176   }
    177   if (device == NULL)
    178     return false;
    179   GPUInfo::GPUDevice temp = gpu_info->gpu;
    180   gpu_info->gpu = *device;
    181   *device = temp;
    182   return true;
    183 }
    184 
    185 }  // namespace anonymous
    186 
    187 bool CollectContextGraphicsInfo(GPUInfo* gpu_info) {
    188   DCHECK(gpu_info);
    189 
    190   TRACE_EVENT0("gpu", "gpu_info_collector::CollectGraphicsInfo");
    191 
    192   if (CommandLine::ForCurrentProcess()->HasSwitch(
    193           switches::kGpuNoContextLost)) {
    194     gpu_info->can_lose_context = false;
    195   } else {
    196 #if defined(OS_CHROMEOS)
    197     gpu_info->can_lose_context = false;
    198 #else
    199     // TODO(zmo): need to consider the case where we are running on top
    200     // of desktop GL and GL_ARB_robustness extension is available.
    201     gpu_info->can_lose_context =
    202         (gfx::GetGLImplementation() == gfx::kGLImplementationEGLGLES2);
    203 #endif
    204   }
    205 
    206   gpu_info->finalized = true;
    207   bool rt = CollectGraphicsInfoGL(gpu_info);
    208 
    209   return rt;
    210 }
    211 
    212 GpuIDResult CollectGpuID(uint32* vendor_id, uint32* device_id) {
    213   DCHECK(vendor_id && device_id);
    214   *vendor_id = 0;
    215   *device_id = 0;
    216 
    217   GPUInfo gpu_info;
    218   if (CollectPCIVideoCardInfo(&gpu_info)) {
    219     *vendor_id = gpu_info.gpu.vendor_id;
    220     *device_id = gpu_info.gpu.device_id;
    221     return kGpuIDSuccess;
    222   }
    223   return kGpuIDFailure;
    224 }
    225 
    226 bool CollectBasicGraphicsInfo(GPUInfo* gpu_info) {
    227   DCHECK(gpu_info);
    228 
    229   bool rt = CollectPCIVideoCardInfo(gpu_info);
    230 
    231   std::string driver_version;
    232   switch (gpu_info->gpu.vendor_id) {
    233     case kVendorIDAMD:
    234       driver_version = CollectDriverVersionATI();
    235       if (!driver_version.empty()) {
    236         gpu_info->driver_vendor = "ATI / AMD";
    237         gpu_info->driver_version = driver_version;
    238       }
    239       break;
    240     case kVendorIDNVidia:
    241       driver_version = CollectDriverVersionNVidia();
    242       if (!driver_version.empty()) {
    243         gpu_info->driver_vendor = "NVIDIA";
    244         gpu_info->driver_version = driver_version;
    245       }
    246       break;
    247     case kVendorIDIntel:
    248       // In dual-GPU cases, sometimes PCI scan only gives us the
    249       // integrated GPU (i.e., the Intel one).
    250       driver_version = CollectDriverVersionNVidia();
    251       if (!driver_version.empty()) {
    252         gpu_info->driver_vendor = "NVIDIA";
    253         gpu_info->driver_version = driver_version;
    254         // Machines with more than two GPUs are not handled.
    255         if (gpu_info->secondary_gpus.size() <= 1)
    256           gpu_info->optimus = true;
    257       }
    258       break;
    259   }
    260 
    261   return rt;
    262 }
    263 
    264 bool CollectDriverInfoGL(GPUInfo* gpu_info) {
    265   DCHECK(gpu_info);
    266 
    267   std::string gl_version_string = gpu_info->gl_version_string;
    268   if (StartsWithASCII(gl_version_string, "OpenGL ES", true))
    269     gl_version_string = gl_version_string.substr(10);
    270   std::vector<std::string> pieces;
    271   base::SplitStringAlongWhitespace(gl_version_string, &pieces);
    272   // In linux, the gl version string might be in the format of
    273   //   GLVersion DriverVendor DriverVersion
    274   if (pieces.size() < 3)
    275     return false;
    276 
    277   std::string driver_version = pieces[2];
    278   size_t pos = driver_version.find_first_not_of("0123456789.");
    279   if (pos == 0)
    280     return false;
    281   if (pos != std::string::npos)
    282     driver_version = driver_version.substr(0, pos);
    283 
    284   gpu_info->driver_vendor = pieces[1];
    285   gpu_info->driver_version = driver_version;
    286   return true;
    287 }
    288 
    289 void MergeGPUInfo(GPUInfo* basic_gpu_info,
    290                   const GPUInfo& context_gpu_info) {
    291   MergeGPUInfoGL(basic_gpu_info, context_gpu_info);
    292 }
    293 
    294 bool DetermineActiveGPU(GPUInfo* gpu_info) {
    295   DCHECK(gpu_info);
    296   if (gpu_info->secondary_gpus.size() == 0)
    297     return true;
    298   if (gpu_info->gl_vendor.empty())
    299     return false;
    300   uint32 active_vendor = 0;
    301   // For now we only handle Intel/NVIDIA/AMD.
    302   if (gpu_info->gl_vendor.find("Intel") != std::string::npos) {
    303     if (gpu_info->gpu.vendor_id == kVendorIDIntel)
    304       return true;
    305     active_vendor = kVendorIDIntel;
    306   }
    307   if (gpu_info->gl_vendor.find("NVIDIA") != std::string::npos) {
    308     if (gpu_info->gpu.vendor_id == kVendorIDNVidia)
    309       return true;
    310     active_vendor = kVendorIDNVidia;
    311   }
    312   if (gpu_info->gl_vendor.find("ATI") != std::string::npos ||
    313       gpu_info->gl_vendor.find("AMD") != std::string::npos) {
    314     if (gpu_info->gpu.vendor_id == kVendorIDAMD)
    315       return true;
    316     active_vendor = kVendorIDAMD;
    317   }
    318   if (active_vendor == 0)
    319     return false;
    320   return FindAndSetActiveGPU(gpu_info, active_vendor);
    321 }
    322 
    323 }  // namespace gpu
    324