1/* 2 * Copyright (c) 2022 The Khronos Group Inc. 3 * Copyright (c) 2022 Valve Corporation 4 * Copyright (c) 2022 LunarG, Inc. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * 18 * Author: Jon Ashburn <jon@lunarg.com> 19 * Author: Courtney Goeltzenleuchter <courtney@LunarG.com> 20 * Author: Mark Young <marky@lunarg.com> 21 * Author: Lenny Komow <lenny@lunarg.com> 22 * Author: Charles Giessen <charles@lunarg.com> 23 */ 24 25#include "unknown_function_handling.h" 26 27#include "allocation.h" 28#include "log.h" 29 30// Forward declarations 31void *loader_get_dev_ext_trampoline(uint32_t index); 32void *loader_get_phys_dev_ext_tramp(uint32_t index); 33void *loader_get_phys_dev_ext_termin(uint32_t index); 34 35// Device function handling 36 37// Initialize device_ext dispatch table entry as follows: 38// If dev == NULL find all logical devices created within this instance and 39// init the entry (given by idx) in the ext dispatch table. 40// If dev != NULL only initialize the entry in the given dev's dispatch table. 41// The initialization value is gotten by calling down the device chain with 42// GDPA. 43// If GDPA returns NULL then don't initialize the dispatch table entry. 44void loader_init_dispatch_dev_ext_entry(struct loader_instance *inst, struct loader_device *dev, uint32_t idx, const char *funcName) 45 46{ 47 void *gdpa_value; 48 if (dev != NULL) { 49 gdpa_value = dev->loader_dispatch.core_dispatch.GetDeviceProcAddr(dev->chain_device, funcName); 50 if (gdpa_value != NULL) dev->loader_dispatch.ext_dispatch[idx] = (PFN_vkDevExt)gdpa_value; 51 } else { 52 for (struct loader_icd_term *icd_term = inst->icd_terms; icd_term != NULL; icd_term = icd_term->next) { 53 struct loader_device *ldev = icd_term->logical_device_list; 54 while (ldev) { 55 gdpa_value = ldev->loader_dispatch.core_dispatch.GetDeviceProcAddr(ldev->chain_device, funcName); 56 if (gdpa_value != NULL) ldev->loader_dispatch.ext_dispatch[idx] = (PFN_vkDevExt)gdpa_value; 57 ldev = ldev->next; 58 } 59 } 60 } 61} 62 63// Find all dev extension in the function names array and initialize the dispatch table 64// for dev for each of those extension entrypoints found in function names array. 65void loader_init_dispatch_dev_ext(struct loader_instance *inst, struct loader_device *dev) { 66 for (uint32_t i = 0; i < MAX_NUM_UNKNOWN_EXTS; i++) { 67 if (inst->dev_ext_disp_functions[i] != NULL) 68 loader_init_dispatch_dev_ext_entry(inst, dev, i, inst->dev_ext_disp_functions[i]); 69 } 70} 71 72bool loader_check_icds_for_dev_ext_address(struct loader_instance *inst, const char *funcName) { 73 struct loader_icd_term *icd_term; 74 icd_term = inst->icd_terms; 75 while (NULL != icd_term) { 76 if (icd_term->scanned_icd->GetInstanceProcAddr(icd_term->instance, funcName)) 77 // this icd supports funcName 78 return true; 79 icd_term = icd_term->next; 80 } 81 82 return false; 83} 84 85// Look in the layers list of device extensions, which contain names of entry points. If funcName is present, return true 86// If not, call down the first layer's vkGetInstanceProcAddr to determine if any layers support the function 87bool loader_check_layer_list_for_dev_ext_address(struct loader_instance *inst, const char *funcName) { 88 // Iterate over the layers. 89 for (uint32_t layer = 0; layer < inst->expanded_activated_layer_list.count; ++layer) { 90 // Iterate over the extensions. 91 const struct loader_device_extension_list *const extensions = 92 &(inst->expanded_activated_layer_list.list[layer]->device_extension_list); 93 for (uint32_t extension = 0; extension < extensions->count; ++extension) { 94 // Iterate over the entry points. 95 const struct loader_dev_ext_props *const property = &(extensions->list[extension]); 96 for (uint32_t entry = 0; entry < property->entrypoints.count; ++entry) { 97 if (strcmp(property->entrypoints.list[entry], funcName) == 0) { 98 return true; 99 } 100 } 101 } 102 } 103 // If the function pointer doesn't appear in the layer manifest for intercepted device functions, look down the 104 // vkGetInstanceProcAddr chain 105 if (inst->expanded_activated_layer_list.count > 0) { 106 const struct loader_layer_functions *const functions = &(inst->expanded_activated_layer_list.list[0]->functions); 107 if (NULL != functions->get_instance_proc_addr) { 108 return NULL != functions->get_instance_proc_addr((VkInstance)inst->instance, funcName); 109 } 110 } 111 112 return false; 113} 114 115void loader_free_dev_ext_table(struct loader_instance *inst) { 116 for (uint32_t i = 0; i < inst->dev_ext_disp_function_count; i++) { 117 loader_instance_heap_free(inst, inst->dev_ext_disp_functions[i]); 118 } 119 memset(inst->dev_ext_disp_functions, 0, sizeof(inst->dev_ext_disp_functions)); 120} 121 122/* 123 * This function returns generic trampoline code address for unknown entry points. 124 * Presumably, these unknown entry points (as given by funcName) are device extension 125 * entrypoints. 126 * A function name array is used to keep a list of unknown entry points and their 127 * mapping to the device extension dispatch table. 128 * \returns 129 * For a given entry point string (funcName), if an existing mapping is found the 130 * trampoline address for that mapping is returned. 131 * Otherwise, this unknown entry point has not been seen yet. 132 * Next check if an ICD supports it, and if is_tramp is true, check if any layer 133 * supports it by calling down the chain. 134 * If so then a new entry in the function name array is added and that trampoline 135 * address for the new entry is returned. 136 * NULL is returned if the function name array is full or if no discovered layer or 137 * ICD returns a non-NULL GetProcAddr for it. 138 */ 139void *loader_dev_ext_gpa_impl(struct loader_instance *inst, const char *funcName, bool is_tramp) { 140 // Linearly look through already added functions to make sure we haven't seen it before 141 // if we have, return the function at the index found 142 for (uint32_t i = 0; i < inst->dev_ext_disp_function_count; i++) { 143 if (inst->dev_ext_disp_functions[i] && !strcmp(inst->dev_ext_disp_functions[i], funcName)) 144 return loader_get_dev_ext_trampoline(i); 145 } 146 147 // Check if funcName is supported in either ICDs or a layer library 148 if (!loader_check_icds_for_dev_ext_address(inst, funcName)) { 149 if (!is_tramp || !loader_check_layer_list_for_dev_ext_address(inst, funcName)) { 150 // if support found in layers continue on 151 return NULL; 152 } 153 } 154 if (inst->dev_ext_disp_function_count >= MAX_NUM_UNKNOWN_EXTS) { 155 loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_dev_ext_gpa: Exhausted the unknown device function array!"); 156 return NULL; 157 } 158 159 // add found function to dev_ext_disp_functions; 160 size_t funcName_len = strlen(funcName) + 1; 161 inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count] = 162 (char *)loader_instance_heap_alloc(inst, funcName_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); 163 if (NULL == inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count]) { 164 // failed to allocate memory, return NULL 165 return NULL; 166 } 167 loader_strncpy(inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count], funcName_len, funcName, funcName_len); 168 // init any dev dispatch table entries as needed 169 loader_init_dispatch_dev_ext_entry(inst, NULL, inst->dev_ext_disp_function_count, funcName); 170 void *out_function = loader_get_dev_ext_trampoline(inst->dev_ext_disp_function_count); 171 inst->dev_ext_disp_function_count++; 172 return out_function; 173} 174 175void *loader_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) { 176 return loader_dev_ext_gpa_impl(inst, funcName, true); 177} 178 179void *loader_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) { 180 return loader_dev_ext_gpa_impl(inst, funcName, false); 181} 182 183// Physical Device function handling 184 185bool loader_check_icds_for_phys_dev_ext_address(struct loader_instance *inst, const char *funcName) { 186 struct loader_icd_term *icd_term; 187 icd_term = inst->icd_terms; 188 while (NULL != icd_term) { 189 if (icd_term->scanned_icd->interface_version >= MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION && 190 icd_term->scanned_icd->GetPhysicalDeviceProcAddr && 191 icd_term->scanned_icd->GetPhysicalDeviceProcAddr(icd_term->instance, funcName)) 192 // this icd supports funcName 193 return true; 194 icd_term = icd_term->next; 195 } 196 197 return false; 198} 199 200bool loader_check_layer_list_for_phys_dev_ext_address(struct loader_instance *inst, const char *funcName) { 201 for (uint32_t layer = 0; layer < inst->expanded_activated_layer_list.count; layer++) { 202 struct loader_layer_properties *layer_prop_list = inst->expanded_activated_layer_list.list[layer]; 203 // Find the first layer in the call chain which supports vk_layerGetPhysicalDeviceProcAddr 204 // and call that, returning whether it found a valid pointer for this function name. 205 // We return if the topmost layer supports GPDPA since the layer should call down the chain for us. 206 if (layer_prop_list->interface_version > 1) { 207 const struct loader_layer_functions *const functions = &(layer_prop_list->functions); 208 if (NULL != functions->get_physical_device_proc_addr) { 209 return NULL != functions->get_physical_device_proc_addr((VkInstance)inst->instance, funcName); 210 } 211 } 212 } 213 return false; 214} 215 216void loader_free_phys_dev_ext_table(struct loader_instance *inst) { 217 for (uint32_t i = 0; i < MAX_NUM_UNKNOWN_EXTS; i++) { 218 loader_instance_heap_free(inst, inst->phys_dev_ext_disp_functions[i]); 219 } 220 memset(inst->phys_dev_ext_disp_functions, 0, sizeof(inst->phys_dev_ext_disp_functions)); 221} 222 223// This function returns a generic trampoline or terminator function 224// address for any unknown physical device extension commands. An array 225// is used to keep a list of unknown entry points and their 226// mapping to the physical device extension dispatch table (struct 227// loader_phys_dev_ext_dispatch_table). 228// For a given entry point string (funcName), if an existing mapping is 229// found, then the address for that mapping is returned. The is_tramp 230// parameter is used to decide whether to return a trampoline or terminator 231// If it has not been seen before check if a layer or and ICD supports it. 232// If so then a new entry in the function name array is added. 233// Null is returned if discovered layer or ICD returns a non-NULL GetProcAddr for it 234// or if the function name table is full. 235void *loader_phys_dev_ext_gpa_impl(struct loader_instance *inst, const char *funcName, bool is_tramp) { 236 assert(NULL != inst); 237 238 // We should always check to see if any ICD supports it. 239 if (!loader_check_icds_for_phys_dev_ext_address(inst, funcName)) { 240 // If we're not checking layers, or we are and it's not in a layer, just 241 // return 242 if (!is_tramp || !loader_check_layer_list_for_phys_dev_ext_address(inst, funcName)) { 243 return NULL; 244 } 245 } 246 247 bool has_found = false; 248 uint32_t new_function_index = 0; 249 // Linearly look through already added functions to make sure we haven't seen it before 250 // if we have, return the function at the index found 251 for (uint32_t i = 0; i < inst->phys_dev_ext_disp_function_count; i++) { 252 if (inst->phys_dev_ext_disp_functions[i] && !strcmp(inst->phys_dev_ext_disp_functions[i], funcName)) { 253 has_found = true; 254 new_function_index = i; 255 break; 256 } 257 } 258 259 // A never before seen function name, store it in the array 260 if (!has_found) { 261 if (inst->phys_dev_ext_disp_function_count >= MAX_NUM_UNKNOWN_EXTS) { 262 loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, 263 "loader_dev_ext_gpa: Exhausted the unknown physical device function array!"); 264 return NULL; 265 } 266 267 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, 268 "loader_phys_dev_ext_gpa: Adding unknown physical function %s to internal store at index %u", funcName, 269 inst->phys_dev_ext_disp_function_count); 270 271 // add found function to phys_dev_ext_disp_functions; 272 size_t funcName_len = strlen(funcName) + 1; 273 inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count] = 274 (char *)loader_instance_heap_alloc(inst, funcName_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); 275 if (NULL == inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count]) { 276 // failed to allocate memory, return NULL 277 return NULL; 278 } 279 loader_strncpy(inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count], funcName_len, funcName, 280 funcName_len); 281 282 new_function_index = inst->phys_dev_ext_disp_function_count; 283 // increment the count so that the subsequent logic includes the newly added entry point when searching for functions 284 inst->phys_dev_ext_disp_function_count++; 285 } 286 287 // Setup the ICD function pointers 288 struct loader_icd_term *icd_term = inst->icd_terms; 289 while (NULL != icd_term) { 290 if (MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION <= icd_term->scanned_icd->interface_version && 291 NULL != icd_term->scanned_icd->GetPhysicalDeviceProcAddr) { 292 icd_term->phys_dev_ext[new_function_index] = 293 (PFN_PhysDevExt)icd_term->scanned_icd->GetPhysicalDeviceProcAddr(icd_term->instance, funcName); 294 if (NULL != icd_term->phys_dev_ext[new_function_index]) { 295 // Make sure we set the instance dispatch to point to the loader's terminator now since we can at least handle 296 // it in one ICD. 297 inst->disp->phys_dev_ext[new_function_index] = loader_get_phys_dev_ext_termin(new_function_index); 298 299 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "loader_phys_dev_ext_gpa: Driver %s returned ptr %p for %s", 300 icd_term->scanned_icd->lib_name, inst->disp->phys_dev_ext[new_function_index], funcName); 301 } 302 } else { 303 icd_term->phys_dev_ext[new_function_index] = NULL; 304 } 305 306 icd_term = icd_term->next; 307 } 308 309 // Now if this is being run in the trampoline, search for the first layer attached and query using it to get the first entry 310 // point. Only set the instance dispatch table to it if it isn't NULL. 311 if (is_tramp) { 312 for (uint32_t i = 0; i < inst->expanded_activated_layer_list.count; i++) { 313 struct loader_layer_properties *layer_prop = inst->expanded_activated_layer_list.list[i]; 314 if (layer_prop->interface_version > 1 && NULL != layer_prop->functions.get_physical_device_proc_addr) { 315 void *layer_ret_function = 316 (PFN_PhysDevExt)layer_prop->functions.get_physical_device_proc_addr(inst->instance, funcName); 317 if (NULL != layer_ret_function) { 318 inst->disp->phys_dev_ext[new_function_index] = layer_ret_function; 319 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "loader_phys_dev_ext_gpa: Layer %s returned ptr %p for %s", 320 layer_prop->info.layerName, inst->disp->phys_dev_ext[new_function_index], funcName); 321 break; 322 } 323 } 324 } 325 } 326 327 if (is_tramp) { 328 return loader_get_phys_dev_ext_tramp(new_function_index); 329 } else { 330 return loader_get_phys_dev_ext_termin(new_function_index); 331 } 332} 333// Main interface functions, makes it clear whether it is getting a terminator or trampoline 334void *loader_phys_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) { 335 return loader_phys_dev_ext_gpa_impl(inst, funcName, true); 336} 337void *loader_phys_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) { 338 return loader_phys_dev_ext_gpa_impl(inst, funcName, false); 339} 340