1/* 2 * 3 * Copyright (c) 2021-2022 The Khronos Group Inc. 4 * Copyright (c) 2021-2022 Valve Corporation 5 * Copyright (c) 2021-2022 LunarG, Inc. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 * Author: Mark Young <marky@lunarg.com> 20 * 21 */ 22 23// Non-windows and non-apple only header file, guard it so that accidental 24// inclusion doesn't cause unknown header include errors 25#if defined(LOADER_ENABLE_LINUX_SORT) 26 27#include <stdio.h> 28#include <stdlib.h> 29 30#include "loader_linux.h" 31 32#include "allocation.h" 33#include "loader_environment.h" 34#include "loader.h" 35#include "log.h" 36 37// Determine a priority based on device type with the higher value being higher priority. 38uint32_t determine_priority_type_value(VkPhysicalDeviceType type) { 39 switch (type) { 40 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: 41 return 10; 42 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: 43 return 5; 44 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: 45 return 3; 46 case VK_PHYSICAL_DEVICE_TYPE_OTHER: 47 return 2; 48 case VK_PHYSICAL_DEVICE_TYPE_CPU: 49 return 1; 50 case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM: // Not really an enum, but throws warning if it's not here 51 break; 52 } 53 return 0; 54} 55 56// Compare the two device types. 57// This behaves similar to a qsort compare. 58int32_t device_type_compare(VkPhysicalDeviceType a, VkPhysicalDeviceType b) { 59 uint32_t a_value = determine_priority_type_value(a); 60 uint32_t b_value = determine_priority_type_value(b); 61 if (a_value > b_value) { 62 return -1; 63 } else if (b_value > a_value) { 64 return 1; 65 } 66 return 0; 67} 68 69// Used to compare two devices and determine which one should have priority. The criteria is 70// simple: 71// 1) Default device ALWAYS wins 72// 2) Sort by type 73// 3) Sort by PCI bus ID 74// 4) Ties broken by device_ID XOR vendor_ID comparison 75int32_t compare_devices(const void *a, const void *b) { 76 struct LinuxSortedDeviceInfo *left = (struct LinuxSortedDeviceInfo *)a; 77 struct LinuxSortedDeviceInfo *right = (struct LinuxSortedDeviceInfo *)b; 78 79 // Default device always gets priority 80 if (left->default_device) { 81 return -1; 82 } else if (right->default_device) { 83 return 1; 84 } 85 86 // Order by device type next 87 int32_t dev_type_comp = device_type_compare(left->device_type, right->device_type); 88 if (0 != dev_type_comp) { 89 return dev_type_comp; 90 } 91 92 // Sort by PCI info (prioritize devices that have info over those that don't) 93 if (left->has_pci_bus_info && !right->has_pci_bus_info) { 94 return -1; 95 } else if (!left->has_pci_bus_info && right->has_pci_bus_info) { 96 return 1; 97 } else if (left->has_pci_bus_info && right->has_pci_bus_info) { 98 // Sort low to high PCI domain 99 if (left->pci_domain < right->pci_domain) { 100 return -1; 101 } else if (left->pci_domain > right->pci_domain) { 102 return 1; 103 } 104 // Sort low to high PCI bus 105 if (left->pci_bus < right->pci_bus) { 106 return -1; 107 } else if (left->pci_bus > right->pci_bus) { 108 return 1; 109 } 110 // Sort low to high PCI device 111 if (left->pci_device < right->pci_device) { 112 return -1; 113 } else if (left->pci_device > right->pci_device) { 114 return 1; 115 } 116 // Sort low to high PCI function 117 if (left->pci_function < right->pci_function) { 118 return -1; 119 } else if (left->pci_function > right->pci_function) { 120 return 1; 121 } 122 } 123 124 // Somehow we have a tie above, so XOR vendorID and deviceID and compare 125 uint32_t left_xord_dev_vend = left->device_id ^ left->vendor_id; 126 uint32_t right_xord_dev_vend = right->device_id ^ right->vendor_id; 127 if (left_xord_dev_vend < right_xord_dev_vend) { 128 return -1; 129 } else if (right_xord_dev_vend < left_xord_dev_vend) { 130 return 1; 131 } 132 return 0; 133} 134 135// Used to compare two device groups and determine which one should have priority. 136// NOTE: This assumes that devices in each group have already been sorted. 137// The group sort criteria is simple: 138// 1) Group with the default device ALWAYS wins 139// 2) Group with the best device type for device 0 wins 140// 3) Group with best PCI bus ID for device 0 wins 141// 4) Ties broken by group device 0 device_ID XOR vendor_ID comparison 142int32_t compare_device_groups(const void *a, const void *b) { 143 struct loader_physical_device_group_term *grp_a = (struct loader_physical_device_group_term *)a; 144 struct loader_physical_device_group_term *grp_b = (struct loader_physical_device_group_term *)b; 145 146 // Use the first GPU's info from each group to sort the groups by 147 struct LinuxSortedDeviceInfo *left = &grp_a->internal_device_info[0]; 148 struct LinuxSortedDeviceInfo *right = &grp_b->internal_device_info[0]; 149 150 // Default device always gets priority 151 if (left->default_device) { 152 return -1; 153 } else if (right->default_device) { 154 return 1; 155 } 156 157 // Order by device type next 158 int32_t dev_type_comp = device_type_compare(left->device_type, right->device_type); 159 if (0 != dev_type_comp) { 160 return dev_type_comp; 161 } 162 163 // Sort by PCI info (prioritize devices that have info over those that don't) 164 if (left->has_pci_bus_info && !right->has_pci_bus_info) { 165 return -1; 166 } else if (!left->has_pci_bus_info && right->has_pci_bus_info) { 167 return 1; 168 } else if (left->has_pci_bus_info && right->has_pci_bus_info) { 169 // Sort low to high PCI domain 170 if (left->pci_domain < right->pci_domain) { 171 return -1; 172 } else if (left->pci_domain > right->pci_domain) { 173 return 1; 174 } 175 // Sort low to high PCI bus 176 if (left->pci_bus < right->pci_bus) { 177 return -1; 178 } else if (left->pci_bus > right->pci_bus) { 179 return 1; 180 } 181 // Sort low to high PCI device 182 if (left->pci_device < right->pci_device) { 183 return -1; 184 } else if (left->pci_device > right->pci_device) { 185 return 1; 186 } 187 // Sort low to high PCI function 188 if (left->pci_function < right->pci_function) { 189 return -1; 190 } else if (left->pci_function > right->pci_function) { 191 return 1; 192 } 193 } 194 195 // Somehow we have a tie above, so XOR vendorID and deviceID and compare 196 uint32_t left_xord_dev_vend = left->device_id ^ left->vendor_id; 197 uint32_t right_xord_dev_vend = right->device_id ^ right->vendor_id; 198 if (left_xord_dev_vend < right_xord_dev_vend) { 199 return -1; 200 } else if (right_xord_dev_vend < left_xord_dev_vend) { 201 return 1; 202 } 203 return 0; 204} 205 206// Search for the default device using the loader environment variable. 207void linux_env_var_default_device(struct loader_instance *inst, uint32_t device_count, 208 struct LinuxSortedDeviceInfo *sorted_device_info) { 209 char *selection = loader_getenv("VK_LOADER_DEVICE_SELECT", inst); 210 if (NULL != selection) { 211 loader_log(inst, VULKAN_LOADER_DEBUG_BIT | VULKAN_LOADER_DRIVER_BIT, 0, 212 "linux_env_var_default_device: Found \'VK_LOADER_DEVICE_SELECT\' set to %s", selection); 213 214 // The environment variable exists, so grab the vendor ID and device ID of the 215 // selected default device 216 unsigned vendor_id, device_id; 217 int32_t matched = sscanf(selection, "%x:%x", &vendor_id, &device_id); 218 if (matched == 2) { 219 for (int32_t i = 0; i < (int32_t)device_count; ++i) { 220 if (sorted_device_info[i].vendor_id == vendor_id && sorted_device_info[i].device_id == device_id) { 221 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, 222 "linux_env_var_default_device: Found default at index %u \'%s\'", i, 223 sorted_device_info[i].device_name); 224 sorted_device_info[i].default_device = true; 225 break; 226 } 227 } 228 } 229 230 loader_free_getenv(selection, inst); 231 } 232} 233 234// This function allocates an array in sorted_devices which must be freed by the caller if not null 235VkResult linux_read_sorted_physical_devices(struct loader_instance *inst, uint32_t icd_count, 236 struct loader_icd_physical_devices *icd_devices, uint32_t phys_dev_count, 237 struct loader_physical_device_term **sorted_device_term) { 238 VkResult res = VK_SUCCESS; 239 bool app_is_vulkan_1_1 = loader_check_version_meets_required(LOADER_VERSION_1_1_0, inst->app_api_version); 240 241 struct LinuxSortedDeviceInfo *sorted_device_info = loader_instance_heap_calloc( 242 inst, phys_dev_count * sizeof(struct LinuxSortedDeviceInfo), VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); 243 if (NULL == sorted_device_info) { 244 res = VK_ERROR_OUT_OF_HOST_MEMORY; 245 goto out; 246 } 247 248 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, "linux_read_sorted_physical_devices:"); 249 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " Original order:"); 250 251 // Grab all the necessary info we can about each device 252 uint32_t index = 0; 253 for (uint32_t icd_idx = 0; icd_idx < icd_count; ++icd_idx) { 254 for (uint32_t phys_dev = 0; phys_dev < icd_devices[icd_idx].device_count; ++phys_dev) { 255 struct loader_icd_term *icd_term = icd_devices[icd_idx].icd_term; 256 VkPhysicalDeviceProperties dev_props = {}; 257 258 sorted_device_info[index].physical_device = icd_devices[icd_idx].physical_devices[phys_dev]; 259 sorted_device_info[index].icd_index = icd_idx; 260 sorted_device_info[index].icd_term = icd_term; 261 sorted_device_info[index].has_pci_bus_info = false; 262 263 icd_term->dispatch.GetPhysicalDeviceProperties(sorted_device_info[index].physical_device, &dev_props); 264 sorted_device_info[index].device_type = dev_props.deviceType; 265 strncpy(sorted_device_info[index].device_name, dev_props.deviceName, VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); 266 sorted_device_info[index].vendor_id = dev_props.vendorID; 267 sorted_device_info[index].device_id = dev_props.deviceID; 268 269 bool device_is_1_1_capable = 270 loader_check_version_meets_required(LOADER_VERSION_1_1_0, loader_make_version(dev_props.apiVersion)); 271 if (!sorted_device_info[index].has_pci_bus_info) { 272 uint32_t ext_count = 0; 273 icd_term->dispatch.EnumerateDeviceExtensionProperties(sorted_device_info[index].physical_device, NULL, &ext_count, 274 NULL); 275 if (ext_count > 0) { 276 VkExtensionProperties *ext_props = 277 (VkExtensionProperties *)loader_stack_alloc(sizeof(VkExtensionProperties) * ext_count); 278 if (NULL == ext_props) { 279 res = VK_ERROR_OUT_OF_HOST_MEMORY; 280 goto out; 281 } 282 icd_term->dispatch.EnumerateDeviceExtensionProperties(sorted_device_info[index].physical_device, NULL, 283 &ext_count, ext_props); 284 for (uint32_t ext = 0; ext < ext_count; ++ext) { 285 if (!strcmp(ext_props[ext].extensionName, VK_EXT_PCI_BUS_INFO_EXTENSION_NAME)) { 286 sorted_device_info[index].has_pci_bus_info = true; 287 break; 288 } 289 } 290 } 291 } 292 293 if (sorted_device_info[index].has_pci_bus_info) { 294 VkPhysicalDevicePCIBusInfoPropertiesEXT pci_props = (VkPhysicalDevicePCIBusInfoPropertiesEXT){ 295 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT}; 296 VkPhysicalDeviceProperties2 dev_props2 = (VkPhysicalDeviceProperties2){ 297 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = (VkBaseInStructure *)&pci_props}; 298 299 PFN_vkGetPhysicalDeviceProperties2 GetPhysDevProps2 = NULL; 300 if (app_is_vulkan_1_1 && device_is_1_1_capable) { 301 GetPhysDevProps2 = icd_term->dispatch.GetPhysicalDeviceProperties2; 302 } else { 303 GetPhysDevProps2 = (PFN_vkGetPhysicalDeviceProperties2)icd_term->dispatch.GetPhysicalDeviceProperties2KHR; 304 } 305 if (NULL != GetPhysDevProps2) { 306 GetPhysDevProps2(sorted_device_info[index].physical_device, &dev_props2); 307 sorted_device_info[index].pci_domain = pci_props.pciDomain; 308 sorted_device_info[index].pci_bus = pci_props.pciBus; 309 sorted_device_info[index].pci_device = pci_props.pciDevice; 310 sorted_device_info[index].pci_function = pci_props.pciFunction; 311 } else { 312 sorted_device_info[index].has_pci_bus_info = false; 313 } 314 } 315 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " [%u] %s", index, 316 sorted_device_info[index].device_name); 317 index++; 318 } 319 } 320 321 // Select default device if set in the environment variable 322 linux_env_var_default_device(inst, phys_dev_count, sorted_device_info); 323 324 // Sort devices by PCI info 325 qsort(sorted_device_info, phys_dev_count, sizeof(struct LinuxSortedDeviceInfo), compare_devices); 326 327 // If we have a selected index, add that first. 328 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " Sorted order:"); 329 330 // Add all others after (they've already been sorted) 331 for (uint32_t dev = 0; dev < phys_dev_count; ++dev) { 332 sorted_device_term[dev]->this_icd_term = sorted_device_info[dev].icd_term; 333 sorted_device_term[dev]->icd_index = sorted_device_info[dev].icd_index; 334 sorted_device_term[dev]->phys_dev = sorted_device_info[dev].physical_device; 335 loader_set_dispatch((void *)sorted_device_term[dev], inst->disp); 336 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " [%u] %s %s", dev, 337 sorted_device_info[dev].device_name, (sorted_device_info[dev].default_device ? "[default]" : "")); 338 } 339 340out: 341 loader_instance_heap_free(inst, sorted_device_info); 342 343 return res; 344} 345 346// This function sorts an array of physical device groups 347VkResult linux_sort_physical_device_groups(struct loader_instance *inst, uint32_t group_count, 348 struct loader_physical_device_group_term *sorted_group_term) { 349 VkResult res = VK_SUCCESS; 350 bool app_is_vulkan_1_1 = loader_check_version_meets_required(LOADER_VERSION_1_1_0, inst->app_api_version); 351 352 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, "linux_sort_physical_device_groups: Original order:"); 353 354 for (uint32_t group = 0; group < group_count; ++group) { 355 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " Group %u", group); 356 357 struct loader_icd_term *icd_term = sorted_group_term[group].this_icd_term; 358 for (uint32_t gpu = 0; gpu < sorted_group_term[group].group_props.physicalDeviceCount; ++gpu) { 359 VkPhysicalDeviceProperties dev_props = {}; 360 361 sorted_group_term[group].internal_device_info[gpu].physical_device = 362 sorted_group_term[group].group_props.physicalDevices[gpu]; 363 sorted_group_term[group].internal_device_info[gpu].has_pci_bus_info = false; 364 365 icd_term->dispatch.GetPhysicalDeviceProperties(sorted_group_term[group].internal_device_info[gpu].physical_device, 366 &dev_props); 367 sorted_group_term[group].internal_device_info[gpu].device_type = dev_props.deviceType; 368 strncpy(sorted_group_term[group].internal_device_info[gpu].device_name, dev_props.deviceName, 369 VK_MAX_PHYSICAL_DEVICE_NAME_SIZE); 370 sorted_group_term[group].internal_device_info[gpu].vendor_id = dev_props.vendorID; 371 sorted_group_term[group].internal_device_info[gpu].device_id = dev_props.deviceID; 372 373 bool device_is_1_1_capable = 374 loader_check_version_meets_required(LOADER_VERSION_1_1_0, loader_make_version(dev_props.apiVersion)); 375 if (!sorted_group_term[group].internal_device_info[gpu].has_pci_bus_info) { 376 uint32_t ext_count; 377 icd_term->dispatch.EnumerateDeviceExtensionProperties( 378 sorted_group_term[group].internal_device_info[gpu].physical_device, NULL, &ext_count, NULL); 379 if (ext_count > 0) { 380 VkExtensionProperties *ext_props = 381 (VkExtensionProperties *)loader_stack_alloc(sizeof(VkExtensionProperties) * ext_count); 382 if (NULL == ext_props) { 383 return VK_ERROR_OUT_OF_HOST_MEMORY; 384 } 385 icd_term->dispatch.EnumerateDeviceExtensionProperties( 386 sorted_group_term[group].internal_device_info[gpu].physical_device, NULL, &ext_count, ext_props); 387 for (uint32_t ext = 0; ext < ext_count; ++ext) { 388 if (!strcmp(ext_props[ext].extensionName, VK_EXT_PCI_BUS_INFO_EXTENSION_NAME)) { 389 sorted_group_term[group].internal_device_info[gpu].has_pci_bus_info = true; 390 break; 391 } 392 } 393 } 394 } 395 396 if (sorted_group_term[group].internal_device_info[gpu].has_pci_bus_info) { 397 VkPhysicalDevicePCIBusInfoPropertiesEXT pci_props = (VkPhysicalDevicePCIBusInfoPropertiesEXT){ 398 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT}; 399 VkPhysicalDeviceProperties2 dev_props2 = (VkPhysicalDeviceProperties2){ 400 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = (VkBaseInStructure *)&pci_props}; 401 402 PFN_vkGetPhysicalDeviceProperties2 GetPhysDevProps2 = NULL; 403 if (app_is_vulkan_1_1 && device_is_1_1_capable) { 404 GetPhysDevProps2 = icd_term->dispatch.GetPhysicalDeviceProperties2; 405 } else { 406 GetPhysDevProps2 = (PFN_vkGetPhysicalDeviceProperties2)icd_term->dispatch.GetPhysicalDeviceProperties2KHR; 407 } 408 if (NULL != GetPhysDevProps2) { 409 GetPhysDevProps2(sorted_group_term[group].internal_device_info[gpu].physical_device, &dev_props2); 410 sorted_group_term[group].internal_device_info[gpu].pci_domain = pci_props.pciDomain; 411 sorted_group_term[group].internal_device_info[gpu].pci_bus = pci_props.pciBus; 412 sorted_group_term[group].internal_device_info[gpu].pci_device = pci_props.pciDevice; 413 sorted_group_term[group].internal_device_info[gpu].pci_function = pci_props.pciFunction; 414 } else { 415 sorted_group_term[group].internal_device_info[gpu].has_pci_bus_info = false; 416 } 417 } 418 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " [%u] %s", gpu, 419 sorted_group_term[group].internal_device_info[gpu].device_name); 420 } 421 422 // Select default device if set in the environment variable 423 linux_env_var_default_device(inst, sorted_group_term[group].group_props.physicalDeviceCount, 424 sorted_group_term[group].internal_device_info); 425 426 // Sort GPUs in each group 427 qsort(sorted_group_term[group].internal_device_info, sorted_group_term[group].group_props.physicalDeviceCount, 428 sizeof(struct LinuxSortedDeviceInfo), compare_devices); 429 430 // Match the externally used physical device list with the sorted physical device list for this group. 431 for (uint32_t dev = 0; dev < sorted_group_term[group].group_props.physicalDeviceCount; ++dev) { 432 sorted_group_term[group].group_props.physicalDevices[dev] = 433 sorted_group_term[group].internal_device_info[dev].physical_device; 434 } 435 } 436 437 // Sort device groups by PCI info 438 qsort(sorted_group_term, group_count, sizeof(struct loader_physical_device_group_term), compare_device_groups); 439 440 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, "linux_sort_physical_device_groups: Sorted order:"); 441 for (uint32_t group = 0; group < group_count; ++group) { 442 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " Group %u", group); 443 for (uint32_t gpu = 0; gpu < sorted_group_term[group].group_props.physicalDeviceCount; ++gpu) { 444 loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0, " [%u] %s %p %s", gpu, 445 sorted_group_term[group].internal_device_info[gpu].device_name, 446 sorted_group_term[group].internal_device_info[gpu].physical_device, 447 (sorted_group_term[group].internal_device_info[gpu].default_device ? "[default]" : "")); 448 } 449 } 450 451 return res; 452} 453 454#endif // LOADER_ENABLE_LINUX_SORT 455