[email protected] | 623c0bd | 2011-03-12 01:00:41 | [diff] [blame] | 1 | // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 623c0bd | 2011-03-12 01:00:41 | [diff] [blame] | 5 | #include "content/gpu/gpu_info_collector.h" |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 6 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 7 | #include <dlfcn.h> |
| 8 | #include <vector> |
| 9 | |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 10 | #include "base/command_line.h" |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 11 | #include "base/file_util.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 12 | #include "base/logging.h" |
[email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 13 | #include "base/memory/scoped_ptr.h" |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 14 | #include "base/string_piece.h" |
| 15 | #include "base/string_split.h" |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 16 | #include "base/string_tokenizer.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 17 | #include "base/string_util.h" |
[email protected] | 5ae0b28 | 2011-03-28 19:24:49 | [diff] [blame] | 18 | #include "ui/gfx/gl/gl_bindings.h" |
| 19 | #include "ui/gfx/gl/gl_context.h" |
| 20 | #include "ui/gfx/gl/gl_implementation.h" |
[email protected] | baa90006 | 2011-08-09 20:53:40 | [diff] [blame] | 21 | #include "ui/gfx/gl/gl_surface.h" |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 22 | #include "ui/gfx/gl/gl_switches.h" |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 23 | |
| 24 | namespace { |
| 25 | |
| 26 | // PciDevice and PciAccess are defined to access libpci functions. Their |
| 27 | // members match the corresponding structures defined by libpci in size up to |
| 28 | // fields we may access. For those members we don't use, their names are |
| 29 | // defined as "fieldX", etc., or, left out if they are declared after the |
| 30 | // members we care about in libpci. |
| 31 | |
| 32 | struct PciDevice { |
| 33 | PciDevice* next; |
| 34 | |
| 35 | uint16 field0; |
| 36 | uint8 field1; |
| 37 | uint8 field2; |
| 38 | uint8 field3; |
| 39 | int field4; |
| 40 | |
| 41 | uint16 vendor_id; |
| 42 | uint16 device_id; |
| 43 | uint16 device_class; |
| 44 | }; |
| 45 | |
| 46 | struct PciAccess { |
| 47 | unsigned int field0; |
| 48 | int field1; |
| 49 | int field2; |
| 50 | char* field3; |
| 51 | int field4; |
| 52 | int field5; |
| 53 | unsigned int field6; |
| 54 | int field7; |
| 55 | |
| 56 | void (*function0)(); |
| 57 | void (*function1)(); |
| 58 | void (*function2)(); |
| 59 | |
| 60 | PciDevice* device_list; |
| 61 | }; |
| 62 | |
| 63 | // Define function types. |
| 64 | typedef PciAccess* (*FT_pci_alloc)(); |
| 65 | typedef void (*FT_pci_init)(PciAccess*); |
| 66 | typedef void (*FT_pci_cleanup)(PciAccess*); |
| 67 | typedef void (*FT_pci_scan_bus)(PciAccess*); |
| 68 | typedef void (*FT_pci_scan_bus)(PciAccess*); |
| 69 | typedef int (*FT_pci_fill_info)(PciDevice*, int); |
| 70 | typedef char* (*FT_pci_lookup_name)(PciAccess*, char*, int, int, ...); |
| 71 | |
| 72 | // This includes dynamically linked library handle and functions pointers from |
| 73 | // libpci. |
| 74 | struct PciInterface { |
| 75 | void* lib_handle; |
| 76 | |
| 77 | FT_pci_alloc pci_alloc; |
| 78 | FT_pci_init pci_init; |
| 79 | FT_pci_cleanup pci_cleanup; |
| 80 | FT_pci_scan_bus pci_scan_bus; |
| 81 | FT_pci_fill_info pci_fill_info; |
| 82 | FT_pci_lookup_name pci_lookup_name; |
| 83 | }; |
| 84 | |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 85 | // This checks if a system supports PCI bus. |
| 86 | // We check the existence of /sys/bus/pci or /sys/bug/pci_express. |
| 87 | bool IsPciSupported() { |
| 88 | const FilePath pci_path("/sys/bus/pci/"); |
| 89 | const FilePath pcie_path("/sys/bus/pci_express/"); |
| 90 | return (file_util::PathExists(pci_path) || |
| 91 | file_util::PathExists(pcie_path)); |
| 92 | } |
| 93 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 94 | // This dynamically opens libpci and get function pointers we need. Return |
| 95 | // NULL if library fails to open or any functions can not be located. |
| 96 | // Returned interface (if not NULL) should be deleted in FinalizeLibPci. |
| 97 | PciInterface* InitializeLibPci(const char* lib_name) { |
| 98 | void* handle = dlopen(lib_name, RTLD_LAZY); |
| 99 | if (handle == NULL) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 100 | VLOG(1) << "Failed to dlopen " << lib_name; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 101 | return NULL; |
| 102 | } |
| 103 | PciInterface* interface = new struct PciInterface; |
| 104 | interface->lib_handle = handle; |
| 105 | interface->pci_alloc = reinterpret_cast<FT_pci_alloc>( |
| 106 | dlsym(handle, "pci_alloc")); |
| 107 | interface->pci_init = reinterpret_cast<FT_pci_init>( |
| 108 | dlsym(handle, "pci_init")); |
| 109 | interface->pci_cleanup = reinterpret_cast<FT_pci_cleanup>( |
| 110 | dlsym(handle, "pci_cleanup")); |
| 111 | interface->pci_scan_bus = reinterpret_cast<FT_pci_scan_bus>( |
| 112 | dlsym(handle, "pci_scan_bus")); |
| 113 | interface->pci_fill_info = reinterpret_cast<FT_pci_fill_info>( |
| 114 | dlsym(handle, "pci_fill_info")); |
| 115 | interface->pci_lookup_name = reinterpret_cast<FT_pci_lookup_name>( |
| 116 | dlsym(handle, "pci_lookup_name")); |
| 117 | if (interface->pci_alloc == NULL || |
| 118 | interface->pci_init == NULL || |
| 119 | interface->pci_cleanup == NULL || |
| 120 | interface->pci_scan_bus == NULL || |
| 121 | interface->pci_fill_info == NULL || |
| 122 | interface->pci_lookup_name == NULL) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 123 | VLOG(1) << "Missing required function(s) from " << lib_name; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 124 | dlclose(handle); |
| 125 | delete interface; |
| 126 | return NULL; |
| 127 | } |
| 128 | return interface; |
| 129 | } |
| 130 | |
| 131 | // This close the dynamically opened libpci and delete the interface. |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 132 | void FinalizeLibPci(PciInterface** interface) { |
| 133 | DCHECK(interface && *interface && (*interface)->lib_handle); |
| 134 | dlclose((*interface)->lib_handle); |
| 135 | delete (*interface); |
| 136 | *interface = NULL; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 137 | } |
| 138 | |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 139 | // Scan /etc/ati/amdpcsdb.default for "ReleaseVersion". |
| 140 | // Return "" on failing. |
| 141 | std::string CollectDriverVersionATI() { |
| 142 | const FilePath::CharType kATIFileName[] = |
| 143 | FILE_PATH_LITERAL("/etc/ati/amdpcsdb.default"); |
| 144 | FilePath ati_file_path(kATIFileName); |
| 145 | if (!file_util::PathExists(ati_file_path)) |
| 146 | return ""; |
| 147 | std::string contents; |
| 148 | if (!file_util::ReadFileToString(ati_file_path, &contents)) |
| 149 | return ""; |
| 150 | StringTokenizer t(contents, "\r\n"); |
| 151 | while (t.GetNext()) { |
| 152 | std::string line = t.token(); |
| 153 | if (StartsWithASCII(line, "ReleaseVersion=", true)) { |
| 154 | size_t begin = line.find_first_of("0123456789"); |
| 155 | if (begin != std::string::npos) { |
| 156 | size_t end = line.find_first_not_of("0123456789.", begin); |
| 157 | if (end == std::string::npos) |
| 158 | return line.substr(begin); |
| 159 | else |
| 160 | return line.substr(begin, end - begin); |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | return ""; |
| 165 | } |
| 166 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 167 | } // namespace anonymous |
| 168 | |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 169 | namespace gpu_info_collector { |
| 170 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 171 | bool CollectGraphicsInfo(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 172 | DCHECK(gpu_info); |
[email protected] | 41579ae | 2010-11-15 22:31:26 | [diff] [blame] | 173 | |
[email protected] | a605729 | 2011-07-28 21:26:56 | [diff] [blame] | 174 | if (CommandLine::ForCurrentProcess()->HasSwitch( |
| 175 | switches::kGpuNoContextLost)) { |
| 176 | gpu_info->can_lose_context = false; |
| 177 | } else { |
| 178 | // TODO(zmo): need to consider the case where we are running on top |
| 179 | // of desktop GL and GL_ARB_robustness extension is available. |
| 180 | gpu_info->can_lose_context = |
| 181 | (gfx::GetGLImplementation() == gfx::kGLImplementationEGLGLES2); |
| 182 | } |
| 183 | |
[email protected] | 6cc8ece6 | 2011-03-14 20:05:47 | [diff] [blame] | 184 | gpu_info->finalized = true; |
[email protected] | 29ad10c | 2011-11-23 19:48:06 | [diff] [blame^] | 185 | bool rt = CollectGraphicsInfoGL(gpu_info); |
[email protected] | 079c4ad | 2011-02-19 00:05:57 | [diff] [blame] | 186 | |
[email protected] | 3225aeb | 2011-03-22 21:34:59 | [diff] [blame] | 187 | if (gpu_info->vendor_id == 0x1002) { // ATI |
| 188 | std::string ati_driver_version = CollectDriverVersionATI(); |
| 189 | if (ati_driver_version != "") { |
| 190 | gpu_info->driver_vendor = "ATI / AMD"; |
| 191 | gpu_info->driver_version = ati_driver_version; |
| 192 | } |
| 193 | } |
[email protected] | 079c4ad | 2011-02-19 00:05:57 | [diff] [blame] | 194 | |
| 195 | return rt; |
| 196 | } |
| 197 | |
[email protected] | 29ad10c | 2011-11-23 19:48:06 | [diff] [blame^] | 198 | bool CollectPreliminaryGraphicsInfo(content::GPUInfo* gpu_info) { |
| 199 | DCHECK(gpu_info); |
| 200 | |
| 201 | return CollectVideoCardInfo(gpu_info); |
| 202 | } |
| 203 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 204 | bool CollectVideoCardInfo(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 205 | DCHECK(gpu_info); |
[email protected] | 41579ae | 2010-11-15 22:31:26 | [diff] [blame] | 206 | |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 207 | if (IsPciSupported() == false) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 208 | VLOG(1) << "PCI bus scanning is not supported"; |
[email protected] | 67f8857 | 2011-03-01 21:29:27 | [diff] [blame] | 209 | return false; |
| 210 | } |
| 211 | |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 212 | // TODO(zmo): be more flexible about library name. |
| 213 | PciInterface* interface = InitializeLibPci("libpci.so.3"); |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 214 | if (interface == NULL) |
[email protected] | c5790ae | 2011-03-01 16:51:49 | [diff] [blame] | 215 | interface = InitializeLibPci("libpci.so"); |
| 216 | if (interface == NULL) { |
[email protected] | 7ace0ef4e | 2011-03-17 21:23:05 | [diff] [blame] | 217 | VLOG(1) << "Failed to locate libpci"; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 218 | return false; |
[email protected] | c5790ae | 2011-03-01 16:51:49 | [diff] [blame] | 219 | } |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 220 | |
| 221 | PciAccess* access = (interface->pci_alloc)(); |
| 222 | DCHECK(access != NULL); |
| 223 | (interface->pci_init)(access); |
| 224 | (interface->pci_scan_bus)(access); |
| 225 | std::vector<PciDevice*> gpu_list; |
| 226 | PciDevice* gpu_active = NULL; |
| 227 | for (PciDevice* device = access->device_list; |
| 228 | device != NULL; device = device->next) { |
| 229 | (interface->pci_fill_info)(device, 33); // Fill the IDs and class fields. |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 230 | // TODO(zmo): there might be other classes that qualify as display devices. |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 231 | if (device->device_class == 0x0300) { // Device class is DISPLAY_VGA. |
[email protected] | c1e7064 | 2011-04-08 00:47:22 | [diff] [blame] | 232 | if (gpu_info->vendor_id == 0 || gpu_info->vendor_id == device->vendor_id) |
| 233 | gpu_list.push_back(device); |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 234 | } |
| 235 | } |
| 236 | if (gpu_list.size() == 1) { |
| 237 | gpu_active = gpu_list[0]; |
| 238 | } else { |
| 239 | // If more than one graphics card are identified, find the one that matches |
| 240 | // gl VENDOR and RENDERER info. |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 241 | std::string gl_vendor_string = gpu_info->gl_vendor; |
| 242 | std::string gl_renderer_string = gpu_info->gl_renderer; |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 243 | const int buffer_size = 255; |
| 244 | scoped_array<char> buffer(new char[buffer_size]); |
| 245 | std::vector<PciDevice*> candidates; |
| 246 | for (size_t i = 0; i < gpu_list.size(); ++i) { |
| 247 | PciDevice* gpu = gpu_list[i]; |
| 248 | // The current implementation of pci_lookup_name returns the same pointer |
| 249 | // as the passed in upon success, and a different one (NULL or a pointer |
| 250 | // to an error message) upon failure. |
| 251 | if ((interface->pci_lookup_name)(access, |
| 252 | buffer.get(), |
| 253 | buffer_size, |
| 254 | 1, |
| 255 | gpu->vendor_id) != buffer.get()) |
| 256 | continue; |
| 257 | std::string vendor_string = buffer.get(); |
| 258 | const bool kCaseSensitive = false; |
| 259 | if (!StartsWithASCII(gl_vendor_string, vendor_string, kCaseSensitive)) |
| 260 | continue; |
| 261 | if ((interface->pci_lookup_name)(access, |
| 262 | buffer.get(), |
| 263 | buffer_size, |
| 264 | 2, |
| 265 | gpu->vendor_id, |
| 266 | gpu->device_id) != buffer.get()) |
| 267 | continue; |
| 268 | std::string device_string = buffer.get(); |
| 269 | size_t begin = device_string.find_first_of('['); |
| 270 | size_t end = device_string.find_last_of(']'); |
| 271 | if (begin != std::string::npos && end != std::string::npos && |
| 272 | begin < end) { |
| 273 | device_string = device_string.substr(begin + 1, end - begin - 1); |
| 274 | } |
| 275 | if (StartsWithASCII(gl_renderer_string, device_string, kCaseSensitive)) { |
| 276 | gpu_active = gpu; |
| 277 | break; |
| 278 | } |
| 279 | // If a device's vendor matches gl VENDOR string, we want to consider the |
| 280 | // possibility that libpci may not return the exact same name as gl |
| 281 | // RENDERER string. |
| 282 | candidates.push_back(gpu); |
| 283 | } |
| 284 | if (gpu_active == NULL && candidates.size() == 1) |
| 285 | gpu_active = candidates[0]; |
| 286 | } |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 287 | if (gpu_active != NULL) { |
| 288 | gpu_info->vendor_id = gpu_active->vendor_id; |
| 289 | gpu_info->device_id = gpu_active->device_id; |
| 290 | } |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 291 | (interface->pci_cleanup)(access); |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 292 | FinalizeLibPci(&interface); |
[email protected] | 48fe9f3 | 2010-12-21 16:54:10 | [diff] [blame] | 293 | return (gpu_active != NULL); |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 294 | } |
| 295 | |
[email protected] | a80f5ece | 2011-10-20 23:56:55 | [diff] [blame] | 296 | bool CollectDriverInfoGL(content::GPUInfo* gpu_info) { |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 297 | DCHECK(gpu_info); |
| 298 | |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 299 | std::string gl_version_string = gpu_info->gl_version_string; |
[email protected] | aa2284d | 2011-08-04 18:36:03 | [diff] [blame] | 300 | if (StartsWithASCII(gl_version_string, "OpenGL ES", true)) |
| 301 | gl_version_string = gl_version_string.substr(10); |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 302 | std::vector<std::string> pieces; |
| 303 | base::SplitStringAlongWhitespace(gl_version_string, &pieces); |
| 304 | // In linux, the gl version string might be in the format of |
| 305 | // GLVersion DriverVendor DriverVersion |
| 306 | if (pieces.size() < 3) |
| 307 | return false; |
| 308 | |
| 309 | std::string driver_version = pieces[2]; |
| 310 | size_t pos = driver_version.find_first_not_of("0123456789."); |
| 311 | if (pos == 0) |
| 312 | return false; |
| 313 | if (pos != std::string::npos) |
| 314 | driver_version = driver_version.substr(0, pos); |
| 315 | |
[email protected] | a61508e5 | 2011-03-08 17:59:42 | [diff] [blame] | 316 | gpu_info->driver_vendor = pieces[1]; |
| 317 | gpu_info->driver_version = driver_version; |
[email protected] | 7004d7eb | 2011-01-21 00:27:53 | [diff] [blame] | 318 | return true; |
| 319 | } |
| 320 | |
[email protected] | 1bf14bc | 2010-07-09 16:10:43 | [diff] [blame] | 321 | } // namespace gpu_info_collector |