• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2018 Broadcom
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 /**
25  * @file
26  *
27  * Implements wrappers of libc functions to fake having a DRM device that
28  * isn't actually present in the kernel.
29  */
30 
31 /* Prevent glibc from defining open64 when we want to alias it. */
32 #undef _FILE_OFFSET_BITS
33 #define _LARGEFILE64_SOURCE
34 
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/ioctl.h>
41 #include <sys/mman.h>
42 #include <sys/stat.h>
43 #include <sys/sysmacros.h>
44 #include <stdarg.h>
45 #include <fcntl.h>
46 #include <dlfcn.h>
47 #include <dirent.h>
48 #include <c11/threads.h>
49 #include <drm-uapi/drm.h>
50 
51 #include "util/set.h"
52 #include "util/u_debug.h"
53 #include "drm_shim.h"
54 
55 #define REAL_FUNCTION_POINTER(x) typeof(x) *real_##x
56 
57 static mtx_t shim_lock = _MTX_INITIALIZER_NP;
58 struct set *opendir_set;
59 bool drm_shim_debug;
60 
61 /* If /dev/dri doesn't exist, we'll need an arbitrary pointer that wouldn't be
62  * returned by any other opendir() call so we can return just our fake node.
63  */
64 DIR *fake_dev_dri = (void *)&opendir_set;
65 
66 /* XXX: implement REAL_FUNCTION_POINTER(close); */
67 REAL_FUNCTION_POINTER(closedir);
68 REAL_FUNCTION_POINTER(dup);
69 REAL_FUNCTION_POINTER(fcntl);
70 REAL_FUNCTION_POINTER(fopen);
71 REAL_FUNCTION_POINTER(ioctl);
72 REAL_FUNCTION_POINTER(mmap);
73 REAL_FUNCTION_POINTER(open);
74 REAL_FUNCTION_POINTER(opendir);
75 REAL_FUNCTION_POINTER(readdir);
76 REAL_FUNCTION_POINTER(readdir64);
77 REAL_FUNCTION_POINTER(readlink);
78 REAL_FUNCTION_POINTER(realpath);
79 REAL_FUNCTION_POINTER(__xstat);
80 REAL_FUNCTION_POINTER(__xstat64);
81 REAL_FUNCTION_POINTER(__fxstat);
82 REAL_FUNCTION_POINTER(__fxstat64);
83 
84 /* Full path of /dev/dri/renderD* */
85 static char *render_node_path;
86 /* renderD* */
87 static char *render_node_dirent_name;
88 /* /sys/dev/char/major:minor/device */
89 static char *device_path;
90 /* /sys/dev/char/major:minor/device/subsystem */
91 static char *subsystem_path;
92 int render_node_minor = -1;
93 
94 struct file_override {
95    const char *path;
96    char *contents;
97 };
98 static struct file_override file_overrides[10];
99 static int file_overrides_count;
100 extern bool drm_shim_driver_prefers_first_render_node;
101 
102 #define nfasprintf(...)                         \
103    {                                            \
104       UNUSED int __ret = asprintf(__VA_ARGS__); \
105       assert(__ret >= 0);                       \
106    }
107 #define nfvasprintf(...)                         \
108    {                                             \
109       UNUSED int __ret = vasprintf(__VA_ARGS__); \
110       assert(__ret >= 0);                        \
111    }
112 
113 /* Pick the minor and filename for our shimmed render node.  This can be
114  * either a new one that didn't exist on the system, or if the driver wants,
115  * it can replace the first render node.
116  */
117 static void
get_dri_render_node_minor(void)118 get_dri_render_node_minor(void)
119 {
120    for (int i = 0; i < 10; i++) {
121       UNUSED int minor = 128 + i;
122       nfasprintf(&render_node_dirent_name, "renderD%d", minor);
123       nfasprintf(&render_node_path, "/dev/dri/%s",
124                  render_node_dirent_name);
125       struct stat st;
126       if (drm_shim_driver_prefers_first_render_node ||
127           stat(render_node_path, &st) == -1) {
128 
129          render_node_minor = minor;
130          return;
131       }
132    }
133 
134    fprintf(stderr, "Couldn't find a spare render node slot\n");
135 }
136 
get_function_pointer(const char * name)137 static void *get_function_pointer(const char *name)
138 {
139    void *func = dlsym(RTLD_NEXT, name);
140    if (!func) {
141       fprintf(stderr, "Failed to resolve %s\n", name);
142       abort();
143    }
144    return func;
145 }
146 
147 #define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x)
148 
149 void
drm_shim_override_file(const char * contents,const char * path_format,...)150 drm_shim_override_file(const char *contents, const char *path_format, ...)
151 {
152    assert(file_overrides_count < ARRAY_SIZE(file_overrides));
153 
154    char *path;
155    va_list ap;
156    va_start(ap, path_format);
157    nfvasprintf(&path, path_format, ap);
158    va_end(ap);
159 
160    struct file_override *override = &file_overrides[file_overrides_count++];
161    override->path = path;
162    override->contents = strdup(contents);
163 }
164 
165 static void
destroy_shim(void)166 destroy_shim(void)
167 {
168    _mesa_set_destroy(opendir_set, NULL);
169    free(render_node_path);
170    free(render_node_dirent_name);
171    free(subsystem_path);
172 }
173 
174 /* Initialization, which will be called from the first general library call
175  * that might need to be wrapped with the shim.
176  */
177 static void
init_shim(void)178 init_shim(void)
179 {
180    static bool inited = false;
181    drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false);
182 
183    /* We can't lock this, because we recurse during initialization. */
184    if (inited)
185       return;
186 
187    /* This comes first (and we're locked), to make sure we don't recurse
188     * during initialization.
189     */
190    inited = true;
191 
192    opendir_set = _mesa_set_create(NULL,
193                                   _mesa_hash_string,
194                                   _mesa_key_string_equal);
195 
196    GET_FUNCTION_POINTER(closedir);
197    GET_FUNCTION_POINTER(dup);
198    GET_FUNCTION_POINTER(fcntl);
199    GET_FUNCTION_POINTER(fopen);
200    GET_FUNCTION_POINTER(ioctl);
201    GET_FUNCTION_POINTER(mmap);
202    GET_FUNCTION_POINTER(open);
203    GET_FUNCTION_POINTER(opendir);
204    GET_FUNCTION_POINTER(readdir);
205    GET_FUNCTION_POINTER(readdir64);
206    GET_FUNCTION_POINTER(readlink);
207    GET_FUNCTION_POINTER(realpath);
208    GET_FUNCTION_POINTER(__xstat);
209    GET_FUNCTION_POINTER(__xstat64);
210    GET_FUNCTION_POINTER(__fxstat);
211    GET_FUNCTION_POINTER(__fxstat64);
212 
213    get_dri_render_node_minor();
214 
215    if (drm_shim_debug) {
216       fprintf(stderr, "Initializing DRM shim on %s\n",
217               render_node_path);
218    }
219 
220    nfasprintf(&device_path,
221               "/sys/dev/char/%d:%d/device",
222               DRM_MAJOR, render_node_minor);
223 
224    nfasprintf(&subsystem_path,
225               "/sys/dev/char/%d:%d/device/subsystem",
226               DRM_MAJOR, render_node_minor);
227 
228    drm_shim_device_init();
229 
230    atexit(destroy_shim);
231 }
232 
233 /* Override libdrm's reading of various sysfs files for device enumeration. */
fopen(const char * path,const char * mode)234 PUBLIC FILE *fopen(const char *path, const char *mode)
235 {
236    init_shim();
237 
238    for (int i = 0; i < file_overrides_count; i++) {
239       if (strcmp(file_overrides[i].path, path) == 0) {
240          int fds[2];
241          pipe(fds);
242          write(fds[1], file_overrides[i].contents,
243                strlen(file_overrides[i].contents));
244          close(fds[1]);
245          return fdopen(fds[0], "r");
246       }
247    }
248 
249    return real_fopen(path, mode);
250 }
251 PUBLIC FILE *fopen64(const char *path, const char *mode)
252    __attribute__((alias("fopen")));
253 
254 /* Intercepts open(render_node_path) to redirect it to the simulator. */
open(const char * path,int flags,...)255 PUBLIC int open(const char *path, int flags, ...)
256 {
257    init_shim();
258 
259    va_list ap;
260    va_start(ap, flags);
261    mode_t mode = va_arg(ap, mode_t);
262    va_end(ap);
263 
264    if (strcmp(path, render_node_path) != 0)
265       return real_open(path, flags, mode);
266 
267    int fd = real_open("/dev/null", O_RDWR, 0);
268 
269    drm_shim_fd_register(fd, NULL);
270 
271    return fd;
272 }
273 PUBLIC int open64(const char*, int, ...) __attribute__((alias("open")));
274 
275 /* Fakes stat to return character device stuff for our fake render node. */
__xstat(int ver,const char * path,struct stat * st)276 PUBLIC int __xstat(int ver, const char *path, struct stat *st)
277 {
278    init_shim();
279 
280    /* Note: call real stat if we're in the process of probing for a free
281     * render node!
282     */
283    if (render_node_minor == -1)
284       return real___xstat(ver, path, st);
285 
286    /* Fool libdrm's probe of whether the /sys dir for this char dev is
287     * there.
288     */
289    char *sys_dev_drm_dir;
290    nfasprintf(&sys_dev_drm_dir,
291               "/sys/dev/char/%d:%d/device/drm",
292               DRM_MAJOR, render_node_minor);
293    if (strcmp(path, sys_dev_drm_dir) == 0) {
294       free(sys_dev_drm_dir);
295       return 0;
296    }
297    free(sys_dev_drm_dir);
298 
299    if (strcmp(path, render_node_path) != 0)
300       return real___xstat(ver, path, st);
301 
302    memset(st, 0, sizeof(*st));
303    st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
304    st->st_mode = S_IFCHR;
305 
306    return 0;
307 }
308 
309 /* Fakes stat to return character device stuff for our fake render node. */
__xstat64(int ver,const char * path,struct stat64 * st)310 PUBLIC int __xstat64(int ver, const char *path, struct stat64 *st)
311 {
312    init_shim();
313 
314    /* Note: call real stat if we're in the process of probing for a free
315     * render node!
316     */
317    if (render_node_minor == -1)
318       return real___xstat64(ver, path, st);
319 
320    /* Fool libdrm's probe of whether the /sys dir for this char dev is
321     * there.
322     */
323    char *sys_dev_drm_dir;
324    nfasprintf(&sys_dev_drm_dir,
325               "/sys/dev/char/%d:%d/device/drm",
326               DRM_MAJOR, render_node_minor);
327    if (strcmp(path, sys_dev_drm_dir) == 0) {
328       free(sys_dev_drm_dir);
329       return 0;
330    }
331    free(sys_dev_drm_dir);
332 
333    if (strcmp(path, render_node_path) != 0)
334       return real___xstat64(ver, path, st);
335 
336    memset(st, 0, sizeof(*st));
337    st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
338    st->st_mode = S_IFCHR;
339 
340    return 0;
341 }
342 
343 /* Fakes fstat to return character device stuff for our fake render node. */
__fxstat(int ver,int fd,struct stat * st)344 PUBLIC int __fxstat(int ver, int fd, struct stat *st)
345 {
346    init_shim();
347 
348    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
349 
350    if (!shim_fd)
351       return real___fxstat(ver, fd, st);
352 
353    memset(st, 0, sizeof(*st));
354    st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
355    st->st_mode = S_IFCHR;
356 
357    return 0;
358 }
359 
__fxstat64(int ver,int fd,struct stat64 * st)360 PUBLIC int __fxstat64(int ver, int fd, struct stat64 *st)
361 {
362    init_shim();
363 
364    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
365 
366    if (!shim_fd)
367       return real___fxstat64(ver, fd, st);
368 
369    memset(st, 0, sizeof(*st));
370    st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
371    st->st_mode = S_IFCHR;
372 
373    return 0;
374 }
375 
376 /* Tracks if the opendir was on /dev/dri. */
377 PUBLIC DIR *
opendir(const char * name)378 opendir(const char *name)
379 {
380    init_shim();
381 
382    DIR *dir = real_opendir(name);
383    if (strcmp(name, "/dev/dri") == 0) {
384       if (!dir) {
385          /* If /dev/dri didn't exist, we still want to be able to return our
386           * fake /dev/dri/render* even though we probably can't
387           * mkdir("/dev/dri").  Return a fake DIR pointer for that.
388           */
389          dir = fake_dev_dri;
390       }
391 
392       mtx_lock(&shim_lock);
393       _mesa_set_add(opendir_set, dir);
394       mtx_unlock(&shim_lock);
395    }
396 
397    return dir;
398 }
399 
400 /* If we've reached the end of the real directory list and we're
401  * looking at /dev/dri, add our render node to the list.
402  */
403 PUBLIC struct dirent *
readdir(DIR * dir)404 readdir(DIR *dir)
405 {
406    init_shim();
407 
408    struct dirent *ent = NULL;
409 
410    if (dir != fake_dev_dri)
411       ent = real_readdir(dir);
412    static struct dirent render_node_dirent = { 0 };
413 
414    if (!ent) {
415       mtx_lock(&shim_lock);
416       if (_mesa_set_search(opendir_set, dir)) {
417          strcpy(render_node_dirent.d_name,
418                 render_node_dirent_name);
419          ent = &render_node_dirent;
420          _mesa_set_remove_key(opendir_set, dir);
421       }
422       mtx_unlock(&shim_lock);
423    }
424 
425    return ent;
426 }
427 
428 /* If we've reached the end of the real directory list and we're
429  * looking at /dev/dri, add our render node to the list.
430  */
431 PUBLIC struct dirent64 *
readdir64(DIR * dir)432 readdir64(DIR *dir)
433 {
434    init_shim();
435 
436    struct dirent64 *ent = NULL;
437    if (dir != fake_dev_dri)
438       ent = real_readdir64(dir);
439    static struct dirent64 render_node_dirent = { 0 };
440 
441    if (!ent) {
442       mtx_lock(&shim_lock);
443       if (_mesa_set_search(opendir_set, dir)) {
444          strcpy(render_node_dirent.d_name,
445                 render_node_dirent_name);
446          ent = &render_node_dirent;
447          _mesa_set_remove_key(opendir_set, dir);
448       }
449       mtx_unlock(&shim_lock);
450    }
451 
452    return ent;
453 }
454 
455 /* Cleans up tracking of opendir("/dev/dri") */
456 PUBLIC int
closedir(DIR * dir)457 closedir(DIR *dir)
458 {
459    init_shim();
460 
461    mtx_lock(&shim_lock);
462    _mesa_set_remove_key(opendir_set, dir);
463    mtx_unlock(&shim_lock);
464 
465    if (dir != fake_dev_dri)
466       return real_closedir(dir);
467    else
468       return 0;
469 }
470 
471 /* Handles libdrm's readlink to figure out what kind of device we have. */
472 PUBLIC ssize_t
readlink(const char * path,char * buf,size_t size)473 readlink(const char *path, char *buf, size_t size)
474 {
475    init_shim();
476 
477    if (strcmp(path, subsystem_path) != 0)
478       return real_readlink(path, buf, size);
479 
480    static const struct {
481       const char *name;
482       int bus_type;
483    } bus_types[] = {
484       { "/pci", DRM_BUS_PCI },
485       { "/usb", DRM_BUS_USB },
486       { "/platform", DRM_BUS_PLATFORM },
487       { "/spi", DRM_BUS_PLATFORM },
488       { "/host1x", DRM_BUS_HOST1X },
489    };
490 
491    for (uint32_t i = 0; i < ARRAY_SIZE(bus_types); i++) {
492       if (bus_types[i].bus_type != shim_device.bus_type)
493          continue;
494 
495       strncpy(buf, bus_types[i].name, size);
496       buf[size - 1] = 0;
497       break;
498    }
499 
500    return strlen(buf) + 1;
501 }
502 
503 /* Handles libdrm's realpath to figure out what kind of device we have. */
504 PUBLIC char *
realpath(const char * path,char * resolved_path)505 realpath(const char *path, char *resolved_path)
506 {
507    init_shim();
508 
509    if (strcmp(path, device_path) != 0)
510       return real_realpath(path, resolved_path);
511 
512    strcpy(resolved_path, path);
513 
514    return resolved_path;
515 }
516 
517 /* Main entrypoint to DRM drivers: the ioctl syscall.  We send all ioctls on
518  * our DRM fd to drm_shim_ioctl().
519  */
520 PUBLIC int
ioctl(int fd,unsigned long request,...)521 ioctl(int fd, unsigned long request, ...)
522 {
523    init_shim();
524 
525    va_list ap;
526    va_start(ap, request);
527    void *arg = va_arg(ap, void *);
528    va_end(ap);
529 
530    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
531    if (!shim_fd)
532       return real_ioctl(fd, request, arg);
533 
534    return drm_shim_ioctl(fd, request, arg);
535 }
536 
537 /* Gallium uses this to dup the incoming fd on gbm screen creation */
538 PUBLIC int
fcntl(int fd,int cmd,...)539 fcntl(int fd, int cmd, ...)
540 {
541    init_shim();
542 
543    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
544 
545    va_list ap;
546    va_start(ap, cmd);
547    void *arg = va_arg(ap, void *);
548    va_end(ap);
549 
550    int ret = real_fcntl(fd, cmd, arg);
551 
552    if (shim_fd && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC))
553       drm_shim_fd_register(ret, shim_fd);
554 
555    return ret;
556 }
557 PUBLIC int fcntl64(int, int, ...)
558    __attribute__((alias("fcntl")));
559 
560 /* I wrote this when trying to fix gallium screen creation, leaving it around
561  * since it's probably good to have.
562  */
563 PUBLIC int
dup(int fd)564 dup(int fd)
565 {
566    init_shim();
567 
568    int ret = real_dup(fd);
569 
570    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
571    if (shim_fd && ret >= 0)
572       drm_shim_fd_register(ret, shim_fd);
573 
574    return ret;
575 }
576 
577 PUBLIC void *
mmap(void * addr,size_t length,int prot,int flags,int fd,off_t offset)578 mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
579 {
580    init_shim();
581 
582    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
583    if (shim_fd)
584       return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset);
585 
586    return real_mmap(addr, length, prot, flags, fd, offset);
587 }
588 PUBLIC void *mmap64(void*, size_t, int, int, int, off_t)
589    __attribute__((alias("mmap")));
590