• 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 /** @file
25  *
26  * Implements core GEM support (particularly ioctls) underneath the libc ioctl
27  * wrappers, and calls into the driver-specific code as necessary.
28  */
29 
30 #include <c11/threads.h>
31 #include <errno.h>
32 #include <linux/memfd.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/ioctl.h>
38 #include <sys/mman.h>
39 #include <unistd.h>
40 #include "drm-uapi/drm.h"
41 #include "drm_shim.h"
42 #include "util/hash_table.h"
43 #include "util/u_atomic.h"
44 
45 #define SHIM_MEM_SIZE (4ull * 1024 * 1024 * 1024)
46 
47 #ifndef HAVE_MEMFD_CREATE
48 #include <sys/syscall.h>
49 
50 static inline int
memfd_create(const char * name,unsigned int flags)51 memfd_create(const char *name, unsigned int flags)
52 {
53    return syscall(SYS_memfd_create, name, flags);
54 }
55 #endif
56 
57 /* Global state for the shim shared between libc, core, and driver. */
58 struct shim_device shim_device;
59 
60 long shim_page_size;
61 
62 static uint32_t
uint_key_hash(const void * key)63 uint_key_hash(const void *key)
64 {
65    return (uintptr_t)key;
66 }
67 
68 static bool
uint_key_compare(const void * a,const void * b)69 uint_key_compare(const void *a, const void *b)
70 {
71    return a == b;
72 }
73 
74 /**
75  * Called when the first libc shim is called, to initialize GEM simulation
76  * state (other than the shims themselves).
77  */
78 void
drm_shim_device_init(void)79 drm_shim_device_init(void)
80 {
81    shim_device.fd_map = _mesa_hash_table_create(NULL,
82                                                 uint_key_hash,
83                                                 uint_key_compare);
84 
85    mtx_init(&shim_device.mem_lock, mtx_plain);
86 
87    shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC);
88    assert(shim_device.mem_fd != -1);
89 
90    ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE);
91    assert(ret == 0);
92 
93    /* The man page for mmap() says
94     *
95     *    offset must be a multiple of the page size as returned by
96     *    sysconf(_SC_PAGE_SIZE).
97     *
98     * Depending on the configuration of the kernel, this may not be 4096. Get
99     * this page size once and use it as the page size throughout, ensuring that
100     * are offsets are page-size aligned as required. Otherwise, mmap will fail
101     * with EINVAL.
102     */
103 
104    shim_page_size = sysconf(_SC_PAGE_SIZE);
105 
106    util_vma_heap_init(&shim_device.mem_heap, shim_page_size,
107                       SHIM_MEM_SIZE - shim_page_size);
108 
109    drm_shim_driver_init();
110 }
111 
112 static struct shim_fd *
drm_shim_file_create(int fd)113 drm_shim_file_create(int fd)
114 {
115    struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd));
116 
117    shim_fd->fd = fd;
118    mtx_init(&shim_fd->handle_lock, mtx_plain);
119    shim_fd->handles = _mesa_hash_table_create(NULL,
120                                               uint_key_hash,
121                                               uint_key_compare);
122 
123    return shim_fd;
124 }
125 
126 /**
127  * Called when the libc shims have interposed an open or dup of our simulated
128  * DRM device.
129  */
drm_shim_fd_register(int fd,struct shim_fd * shim_fd)130 void drm_shim_fd_register(int fd, struct shim_fd *shim_fd)
131 {
132    if (!shim_fd)
133       shim_fd = drm_shim_file_create(fd);
134 
135    _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd);
136 }
137 
138 struct shim_fd *
drm_shim_fd_lookup(int fd)139 drm_shim_fd_lookup(int fd)
140 {
141    if (fd == -1)
142       return NULL;
143 
144    struct hash_entry *entry =
145       _mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
146 
147    if (!entry)
148       return NULL;
149    return entry->data;
150 }
151 
152 /* ioctl used by drmGetVersion() */
153 static int
drm_shim_ioctl_version(int fd,unsigned long request,void * arg)154 drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
155 {
156    struct drm_version *args = arg;
157    const char *date = "20190320";
158    const char *desc = "shim";
159 
160    args->version_major = shim_device.version_major;
161    args->version_minor = shim_device.version_minor;
162    args->version_patchlevel = shim_device.version_patchlevel;
163 
164    if (args->name)
165       strncpy(args->name, shim_device.driver_name, args->name_len);
166    if (args->date)
167       strncpy(args->date, date, args->date_len);
168    if (args->desc)
169       strncpy(args->desc, desc, args->desc_len);
170    args->name_len = strlen(shim_device.driver_name);
171    args->date_len = strlen(date);
172    args->desc_len = strlen(desc);
173 
174    return 0;
175 }
176 
177 static int
drm_shim_ioctl_get_cap(int fd,unsigned long request,void * arg)178 drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
179 {
180    struct drm_get_cap *gc = arg;
181 
182    switch (gc->capability) {
183    case DRM_CAP_PRIME:
184    case DRM_CAP_SYNCOBJ:
185    case DRM_CAP_SYNCOBJ_TIMELINE:
186       gc->value = 1;
187       return 0;
188 
189    default:
190       fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
191               (int)gc->capability);
192       return -1;
193    }
194 }
195 
196 static int
drm_shim_ioctl_gem_close(int fd,unsigned long request,void * arg)197 drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
198 {
199    struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
200    struct drm_gem_close *c = arg;
201 
202    if (!c->handle)
203       return 0;
204 
205    mtx_lock(&shim_fd->handle_lock);
206    struct hash_entry *entry =
207       _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
208    if (!entry) {
209       mtx_unlock(&shim_fd->handle_lock);
210       return -EINVAL;
211    }
212 
213    struct shim_bo *bo = entry->data;
214    _mesa_hash_table_remove(shim_fd->handles, entry);
215    drm_shim_bo_put(bo);
216    mtx_unlock(&shim_fd->handle_lock);
217    return 0;
218 }
219 
220 static int
drm_shim_ioctl_syncobj_create(int fd,unsigned long request,void * arg)221 drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg)
222 {
223    struct drm_syncobj_create *create = arg;
224 
225    create->handle = 1; /* 0 is invalid */
226 
227    return 0;
228 }
229 
230 static int
drm_shim_ioctl_stub(int fd,unsigned long request,void * arg)231 drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
232 {
233    return 0;
234 }
235 
236 ioctl_fn_t core_ioctls[] = {
237    [_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
238    [_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
239    [_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
240    [_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create,
241    [_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
242    [_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
243    [_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
244    [_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub,
245 };
246 
247 /**
248  * Implements the GEM core ioctls, and calls into driver-specific ioctls.
249  */
250 int
drm_shim_ioctl(int fd,unsigned long request,void * arg)251 drm_shim_ioctl(int fd, unsigned long request, void *arg)
252 {
253    ASSERTED int type = _IOC_TYPE(request);
254    int nr = _IOC_NR(request);
255 
256    assert(type == DRM_IOCTL_BASE);
257 
258    if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
259       int driver_nr = nr - DRM_COMMAND_BASE;
260 
261       if (driver_nr < shim_device.driver_ioctl_count &&
262           shim_device.driver_ioctls[driver_nr]) {
263          return shim_device.driver_ioctls[driver_nr](fd, request, arg);
264       }
265    } else {
266       if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
267          return core_ioctls[nr](fd, request, arg);
268       }
269    }
270 
271    if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
272       fprintf(stderr,
273               "DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
274               nr - DRM_COMMAND_BASE, request);
275    } else {
276       fprintf(stderr,
277               "DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
278               nr, request);
279    }
280 
281    return -EINVAL;
282 }
283 
284 void
drm_shim_bo_init(struct shim_bo * bo,size_t size)285 drm_shim_bo_init(struct shim_bo *bo, size_t size)
286 {
287 
288    mtx_lock(&shim_device.mem_lock);
289    bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size);
290    mtx_unlock(&shim_device.mem_lock);
291    assert(bo->mem_addr);
292 
293    bo->size = size;
294 }
295 
296 struct shim_bo *
drm_shim_bo_lookup(struct shim_fd * shim_fd,int handle)297 drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
298 {
299    if (!handle)
300       return NULL;
301 
302    mtx_lock(&shim_fd->handle_lock);
303    struct hash_entry *entry =
304       _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
305    struct shim_bo *bo = entry ? entry->data : NULL;
306    mtx_unlock(&shim_fd->handle_lock);
307 
308    if (bo)
309       p_atomic_inc(&bo->refcount);
310 
311    return bo;
312 }
313 
314 void
drm_shim_bo_get(struct shim_bo * bo)315 drm_shim_bo_get(struct shim_bo *bo)
316 {
317    p_atomic_inc(&bo->refcount);
318 }
319 
320 void
drm_shim_bo_put(struct shim_bo * bo)321 drm_shim_bo_put(struct shim_bo *bo)
322 {
323    if (p_atomic_dec_return(&bo->refcount) == 0)
324       return;
325 
326    if (shim_device.driver_bo_free)
327       shim_device.driver_bo_free(bo);
328 
329    mtx_lock(&shim_device.mem_lock);
330    util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size);
331    mtx_unlock(&shim_device.mem_lock);
332    free(bo);
333 }
334 
335 int
drm_shim_bo_get_handle(struct shim_fd * shim_fd,struct shim_bo * bo)336 drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
337 {
338    /* We should probably have some real datastructure for finding the free
339     * number.
340     */
341    mtx_lock(&shim_fd->handle_lock);
342    for (int new_handle = 1; ; new_handle++) {
343       void *key = (void *)(uintptr_t)new_handle;
344       if (!_mesa_hash_table_search(shim_fd->handles, key)) {
345          drm_shim_bo_get(bo);
346          _mesa_hash_table_insert(shim_fd->handles, key, bo);
347          mtx_unlock(&shim_fd->handle_lock);
348          return new_handle;
349       }
350    }
351    mtx_unlock(&shim_fd->handle_lock);
352 
353    return 0;
354 }
355 
356 /* Creates an mmap offset for the BO in the DRM fd.
357  *
358  * XXX: We should be maintaining a u_mm allocator where the mmap offsets
359  * allocate the size of the BO and it can be used to look the BO back up.
360  * Instead, we just stuff the shim's pointer as the return value, and treat
361  * the incoming mmap offset on the DRM fd as a BO pointer.  This doesn't work
362  * if someone tries to map a subset of the BO, but it's enough to get V3D
363  * working for now.
364  */
365 uint64_t
drm_shim_bo_get_mmap_offset(struct shim_fd * shim_fd,struct shim_bo * bo)366 drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
367 {
368    return (uintptr_t)bo;
369 }
370 
371 /* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
372  * fd.
373  */
374 void *
drm_shim_mmap(struct shim_fd * shim_fd,size_t length,int prot,int flags,int fd,off_t offset)375 drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
376               int fd, off_t offset)
377 {
378    struct shim_bo *bo = (void *)(uintptr_t)offset;
379 
380    /* The offset we pass to mmap must be aligned to the page size */
381    assert((bo->mem_addr & (shim_page_size - 1)) == 0);
382 
383    return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr);
384 }
385