loader.c revision 6f928160a3b46db44196485cf6acb8bc902f2d08
1/*
2 * XGL
3 *
4 * Copyright (C) 2014 LunarG, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 * Authors:
25 *   Chia-I Wu <olv@lunarg.com>
26 *   Courtney Goeltzenleuchter <courtney@lunarg.com>
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <stdarg.h>
32#include <stdbool.h>
33#include <string.h>
34
35#include <sys/types.h>
36#include <dirent.h>
37#include <unistd.h>
38#include <dlfcn.h>
39#include <pthread.h>
40
41#include "loader.h"
42
43typedef XGL_RESULT (XGLAPI *InitAndEnumerateGpusT)(const XGL_APPLICATION_INFO* pAppInfo, const XGL_ALLOC_CALLBACKS* pAllocCb, XGL_UINT maxGpus, XGL_UINT* pGpuCount, XGL_PHYSICAL_GPU* pGpus);
44typedef XGL_RESULT (XGLAPI *DbgRegisterMsgCallbackT)(XGL_DBG_MSG_CALLBACK_FUNCTION pfnMsgCallback, XGL_VOID* pUserData);
45typedef XGL_RESULT (XGLAPI *DbgUnregisterMsgCallbackT)(XGL_DBG_MSG_CALLBACK_FUNCTION pfnMsgCallback);
46typedef XGL_RESULT (XGLAPI *DbgSetGlobalOptionT)(XGL_INT dbgOption, XGL_SIZE dataSize, const XGL_VOID* pData);
47
48struct loader_icd {
49    void *handle;
50
51    InitAndEnumerateGpusT InitAndEnumerateGpus;
52    DbgRegisterMsgCallbackT DbgRegisterMsgCallback;
53    DbgUnregisterMsgCallbackT DbgUnregisterMsgCallback;
54    DbgSetGlobalOptionT DbgSetGlobalOption;
55
56    struct loader_icd *next;
57};
58
59struct loader_msg_callback {
60    XGL_DBG_MSG_CALLBACK_FUNCTION func;
61    XGL_VOID *data;
62
63    struct loader_msg_callback *next;
64};
65
66static struct {
67    bool scanned;
68    struct loader_icd *icds;
69
70    struct loader_msg_callback *msg_callbacks;
71
72    bool debug_echo_enable;
73    bool break_on_error;
74    bool break_on_warning;
75} loader;
76
77static XGL_RESULT loader_msg_callback_add(XGL_DBG_MSG_CALLBACK_FUNCTION func,
78                                          XGL_VOID *data)
79{
80    struct loader_msg_callback *cb;
81
82    cb = malloc(sizeof(*cb));
83    if (!cb)
84        return XGL_ERROR_OUT_OF_MEMORY;
85
86    cb->func = func;
87    cb->data = data;
88
89    cb->next = loader.msg_callbacks;
90    loader.msg_callbacks = cb;
91
92    return XGL_SUCCESS;
93}
94
95static XGL_RESULT loader_msg_callback_remove(XGL_DBG_MSG_CALLBACK_FUNCTION func)
96{
97    struct loader_msg_callback *cb = loader.msg_callbacks;
98
99    /*
100     * Find the first match (last registered).
101     *
102     * XXX What if the same callback function is registered more than once?
103     */
104    while (cb) {
105        if (cb->func == func) {
106            break;
107        }
108
109        cb = cb->next;
110    }
111
112    if (!cb)
113        return XGL_ERROR_INVALID_POINTER;
114
115    free(cb);
116
117    return XGL_SUCCESS;
118}
119
120static void loader_msg_callback_clear(void)
121{
122    struct loader_msg_callback *cb = loader.msg_callbacks;
123
124    while (cb) {
125        struct loader_msg_callback *next = cb->next;
126        free(cb);
127        cb = next;
128    }
129
130    loader.msg_callbacks = NULL;
131}
132
133static void loader_log(XGL_DBG_MSG_TYPE msg_type, XGL_INT msg_code,
134                       const char *format, ...)
135{
136    const struct loader_msg_callback *cb = loader.msg_callbacks;
137    char msg[256];
138    va_list ap;
139    int ret;
140
141    va_start(ap, format);
142    ret = vsnprintf(msg, sizeof(msg), format, ap);
143    if (ret >= sizeof(msg) || ret < 0) {
144        msg[sizeof(msg) - 1] = '\0';
145    }
146    va_end(ap);
147
148    if (loader.debug_echo_enable || !cb) {
149        fputs(msg, stderr);
150        fputc('\n', stderr);
151    }
152
153    while (cb) {
154        cb->func(msg_type, XGL_VALIDATION_LEVEL_0, XGL_NULL_HANDLE, 0,
155                msg_code, (const XGL_CHAR *) msg, cb->data);
156        cb = cb->next;
157    }
158
159    switch (msg_type) {
160    case XGL_DBG_MSG_ERROR:
161        if (loader.break_on_error) {
162            exit(1);
163        }
164        /* fall through */
165    case XGL_DBG_MSG_WARNING:
166        if (loader.break_on_warning) {
167            exit(1);
168        }
169        break;
170    default:
171        break;
172    }
173}
174
175static void
176loader_icd_destroy(struct loader_icd *icd)
177{
178    dlclose(icd->handle);
179    free(icd);
180}
181
182static struct loader_icd *
183loader_icd_create(const char *filename)
184{
185    struct loader_icd *icd;
186
187    icd = malloc(sizeof(*icd));
188    if (!icd)
189        return NULL;
190
191    memset(icd, 0, sizeof(*icd));
192
193    icd->handle = dlopen(filename, RTLD_LAZY | RTLD_LOCAL);
194    if (!icd->handle) {
195        loader_log(XGL_DBG_MSG_WARNING, 0, dlerror());
196        free(icd);
197        return NULL;
198    }
199
200#define LOOKUP(icd, func) do {                              \
201    icd->func = (func## T) dlsym(icd->handle, "xgl" #func); \
202    if (!icd->func) {                                       \
203        loader_log(XGL_DBG_MSG_WARNING, 0, dlerror());      \
204        loader_icd_destroy(icd);                            \
205        return NULL;                                        \
206    }                                                       \
207} while (0)
208    LOOKUP(icd, InitAndEnumerateGpus);
209    LOOKUP(icd, DbgRegisterMsgCallback);
210    LOOKUP(icd, DbgUnregisterMsgCallback);
211    LOOKUP(icd, DbgSetGlobalOption);
212#undef LOOKUP
213
214    return icd;
215}
216
217static XGL_RESULT loader_icd_register_msg_callbacks(const struct loader_icd *icd)
218{
219    const struct loader_msg_callback *cb = loader.msg_callbacks;
220    XGL_RESULT res;
221
222    while (cb) {
223        res = icd->DbgRegisterMsgCallback(cb->func, cb->data);
224        if (res != XGL_SUCCESS) {
225            break;
226        }
227
228        cb = cb->next;
229    }
230
231    /* roll back on errors */
232    if (cb) {
233        const struct loader_msg_callback *tmp = loader.msg_callbacks;
234
235        while (tmp != cb) {
236            icd->DbgUnregisterMsgCallback(cb->func);
237            tmp = tmp->next;
238        }
239
240        return res;
241    }
242
243    return XGL_SUCCESS;
244}
245
246static XGL_RESULT loader_icd_set_global_options(const struct loader_icd *icd)
247{
248#define SETB(icd, opt, val) do {                                \
249    if (val) {                                                  \
250        const XGL_RESULT res =                                  \
251            icd->DbgSetGlobalOption(opt, sizeof(val), &val);    \
252        if (res != XGL_SUCCESS)                                 \
253            return res;                                         \
254    }                                                           \
255} while (0)
256    SETB(icd, XGL_DBG_OPTION_DEBUG_ECHO_ENABLE, loader.debug_echo_enable);
257    SETB(icd, XGL_DBG_OPTION_BREAK_ON_ERROR, loader.break_on_error);
258    SETB(icd, XGL_DBG_OPTION_BREAK_ON_WARNING, loader.break_on_warning);
259#undef SETB
260
261return XGL_SUCCESS;
262}
263
264static struct loader_icd *loader_icd_add(const char *filename)
265{
266    struct loader_icd *icd;
267
268    icd = loader_icd_create(filename);
269    if (!icd)
270        return NULL;
271
272    if (loader_icd_set_global_options(icd) != XGL_SUCCESS ||
273        loader_icd_register_msg_callbacks(icd) != XGL_SUCCESS) {
274        loader_log(XGL_DBG_MSG_WARNING, 0,
275                "%s ignored: failed to migrate settings", filename);
276        loader_icd_destroy(icd);
277    }
278
279    /* prepend to the list */
280    icd->next = loader.icds;
281    loader.icds = icd;
282
283    return icd;
284}
285
286#ifndef DEFAULT_XGL_DRIVERS_PATH
287// TODO: Is this a good default location?
288// Need to search for both 32bit and 64bit ICDs
289#define DEFAULT_XGL_DRIVERS_PATH "/usr/lib/i386-linux-gnu/xgl:/usr/lib/x86_64-linux-gnu/xgl"
290#endif
291
292/**
293 * Try to \c loader_icd_scan XGL driver(s).
294 *
295 * This function scans the default system path or path
296 * specified by the \c LIBXGL_DRIVERS_PATH environment variable in
297 * order to find loadable XGL ICDs with the name of libXGL_*.
298 *
299 * \returns
300 * void; but side effect is to set loader_icd_scanned to true
301 */
302static void loader_icd_scan(void)
303{
304    const char *libPaths, *p, *next;
305    DIR *sysdir;
306    struct dirent *dent;
307    char icd_library[1024];
308    char path[1024];
309    int len;
310
311    libPaths = NULL;
312    if (geteuid() == getuid()) {
313       /* don't allow setuid apps to use LIBXGL_DRIVERS_PATH */
314       libPaths = getenv("LIBXGL_DRIVERS_PATH");
315    }
316    if (libPaths == NULL)
317       libPaths = DEFAULT_XGL_DRIVERS_PATH;
318
319    for (p = libPaths; *p; p = next) {
320       next = strchr(p, ':');
321       if (next == NULL) {
322          len = strlen(p);
323          next = p + len;
324       }
325       else {
326          len = next - p;
327          sprintf(path, "%.*s", (len > sizeof(path) - 1) ? (int) sizeof(path) - 1 : len, p);
328          p = path;
329          next++;
330       }
331
332       sysdir = opendir(p);
333       if (sysdir) {
334          dent = readdir(sysdir);
335          while (dent) {
336             /* look for ICDs starting with "libXGL_" */
337             if (!strncmp(dent->d_name, "libXGL_", 7)) {
338                snprintf(icd_library, 1024, "%s/%s",p,dent->d_name);
339                loader_icd_add(icd_library);
340             }
341
342             dent = readdir(sysdir);
343          }
344          closedir(sysdir);
345       }
346    }
347
348    /* we have nothing to log anymore */
349    loader_msg_callback_clear();
350
351    loader.scanned = true;
352}
353
354LOADER_EXPORT XGL_RESULT XGLAPI xglInitAndEnumerateGpus(const XGL_APPLICATION_INFO* pAppInfo, const XGL_ALLOC_CALLBACKS* pAllocCb, XGL_UINT maxGpus, XGL_UINT* pGpuCount, XGL_PHYSICAL_GPU* pGpus)
355{
356    static pthread_once_t once = PTHREAD_ONCE_INIT;
357    const struct loader_icd *icd;
358    XGL_UINT count = 0;
359    XGL_RESULT res;
360
361    pthread_once(&once, loader_icd_scan);
362
363    if (!loader.icds)
364        return XGL_ERROR_UNAVAILABLE;
365
366    icd = loader.icds;
367    while (icd) {
368        XGL_PHYSICAL_GPU gpus[XGL_MAX_PHYSICAL_GPUS];
369        XGL_UINT n, max = maxGpus - count;
370
371        if (max > XGL_MAX_PHYSICAL_GPUS) {
372            max = XGL_MAX_PHYSICAL_GPUS;
373        }
374
375        res = icd->InitAndEnumerateGpus(pAppInfo, pAllocCb, max, &n, gpus);
376        if (res == XGL_SUCCESS && n) {
377            memcpy(pGpus + count, gpus, sizeof(*pGpus) * n);
378            count += n;
379
380            if (count >= maxGpus) {
381                break;
382            }
383        }
384
385        icd = icd->next;
386    }
387
388    *pGpuCount = count;
389
390    return (count > 0) ? XGL_SUCCESS : res;
391}
392
393LOADER_EXPORT XGL_RESULT XGLAPI xglDbgRegisterMsgCallback(XGL_DBG_MSG_CALLBACK_FUNCTION pfnMsgCallback, XGL_VOID* pUserData)
394{
395    const struct loader_icd *icd = loader.icds;
396    XGL_RESULT res;
397
398    if (!loader.scanned) {
399        return loader_msg_callback_add(pfnMsgCallback, pUserData);
400    }
401
402    while (icd) {
403        res = icd->DbgRegisterMsgCallback(pfnMsgCallback, pUserData);
404        if (res != XGL_SUCCESS) {
405            break;
406        }
407
408        icd = icd->next;
409    }
410
411    /* roll back on errors */
412    if (icd) {
413        const struct loader_icd *tmp = loader.icds;
414
415        while (tmp != icd) {
416            tmp->DbgUnregisterMsgCallback(pfnMsgCallback);
417            tmp = tmp->next;
418        }
419
420        return res;
421    }
422
423    return XGL_SUCCESS;
424}
425
426LOADER_EXPORT XGL_RESULT XGLAPI xglDbgUnregisterMsgCallback(XGL_DBG_MSG_CALLBACK_FUNCTION pfnMsgCallback)
427{
428    const struct loader_icd *icd = loader.icds;
429    XGL_RESULT res = XGL_SUCCESS;
430
431    if (!loader.scanned) {
432        return loader_msg_callback_remove(pfnMsgCallback);
433    }
434
435    while (icd) {
436        XGL_RESULT r = icd->DbgUnregisterMsgCallback(pfnMsgCallback);
437        if (r != XGL_SUCCESS) {
438            res = r;
439        }
440        icd = icd->next;
441    }
442
443    return res;
444}
445
446LOADER_EXPORT XGL_RESULT XGLAPI xglDbgSetGlobalOption(XGL_DBG_GLOBAL_OPTION dbgOption, XGL_SIZE dataSize, const XGL_VOID* pData)
447{
448    const struct loader_icd *icd = loader.icds;
449    XGL_RESULT res = XGL_SUCCESS;
450
451    if (!loader.scanned) {
452        if (dataSize == 0)
453            return XGL_ERROR_INVALID_VALUE;
454
455        switch (dbgOption) {
456        case XGL_DBG_OPTION_DEBUG_ECHO_ENABLE:
457            loader.debug_echo_enable = *((const bool *) pData);
458            break;
459        case XGL_DBG_OPTION_BREAK_ON_ERROR:
460            loader.break_on_error = *((const bool *) pData);
461            break;
462        case XGL_DBG_OPTION_BREAK_ON_WARNING:
463            loader.break_on_warning = *((const bool *) pData);
464            break;
465        default:
466            res = XGL_ERROR_INVALID_VALUE;
467            break;
468        }
469
470        return res;
471    }
472
473    while (icd) {
474        XGL_RESULT r = icd->DbgSetGlobalOption(dbgOption, dataSize, pData);
475        /* unfortunately we cannot roll back */
476        if (r != XGL_SUCCESS) {
477            res = r;
478        }
479
480        icd = icd->next;
481    }
482
483    return res;
484}
485