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