1 /**************************************************************************
2  *
3  * Copyright 2015, 2018 Collabora
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sub license, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice (including the
15  * next paragraph) shall be included in all copies or substantial portions
16  * of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
21  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24  * DEALINGS IN THE SOFTWARE.
25  *
26  **************************************************************************/
27 
28 #ifdef HAVE_LIBDRM
29 #include <xf86drm.h>
30 #endif
31 #include "util/compiler.h"
32 #include "util/macros.h"
33 
34 #include "eglcurrent.h"
35 #include "egldevice.h"
36 #include "egllog.h"
37 #include "eglglobals.h"
38 #include "egltypedefs.h"
39 
40 
41 struct _egl_device {
42    _EGLDevice *Next;
43 
44    const char *extensions;
45 
46    EGLBoolean MESA_device_software;
47    EGLBoolean EXT_device_drm;
48    EGLBoolean EXT_device_drm_render_node;
49 
50 #ifdef HAVE_LIBDRM
51    drmDevicePtr device;
52 #endif
53 };
54 
55 void
_eglFiniDevice(void)56 _eglFiniDevice(void)
57 {
58    _EGLDevice *dev_list, *dev;
59 
60    /* atexit function is called with global mutex locked */
61 
62    dev_list = _eglGlobal.DeviceList;
63 
64    /* The first device is static allocated SW device */
65    assert(dev_list);
66    assert(_eglDeviceSupports(dev_list, _EGL_DEVICE_SOFTWARE));
67    dev_list = dev_list->Next;
68 
69    while (dev_list) {
70       /* pop list head */
71       dev = dev_list;
72       dev_list = dev_list->Next;
73 
74 #ifdef HAVE_LIBDRM
75       assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM));
76       drmFreeDevice(&dev->device);
77 #endif
78       free(dev);
79    }
80 
81    _eglGlobal.DeviceList = NULL;
82 }
83 
84 EGLBoolean
_eglCheckDeviceHandle(EGLDeviceEXT device)85 _eglCheckDeviceHandle(EGLDeviceEXT device)
86 {
87    _EGLDevice *cur;
88 
89    mtx_lock(_eglGlobal.Mutex);
90    cur = _eglGlobal.DeviceList;
91    while (cur) {
92       if (cur == (_EGLDevice *) device)
93          break;
94       cur = cur->Next;
95    }
96    mtx_unlock(_eglGlobal.Mutex);
97    return (cur != NULL);
98 }
99 
100 _EGLDevice _eglSoftwareDevice = {
101    /* TODO: EGL_EXT_device_drm support for KMS + llvmpipe */
102    .extensions = "EGL_MESA_device_software EGL_EXT_device_drm_render_node",
103    .MESA_device_software = EGL_TRUE,
104    .EXT_device_drm_render_node = EGL_TRUE,
105 };
106 
107 #ifdef HAVE_LIBDRM
108 /*
109  * Negative value on error, zero if newly added, one if already in list.
110  */
111 static int
_eglAddDRMDevice(drmDevicePtr device,_EGLDevice ** out_dev)112 _eglAddDRMDevice(drmDevicePtr device, _EGLDevice **out_dev)
113 {
114    _EGLDevice *dev;
115 
116    if ((device->available_nodes & (1 << DRM_NODE_PRIMARY |
117                                    1 << DRM_NODE_RENDER)) == 0)
118       return -1;
119 
120    dev = _eglGlobal.DeviceList;
121 
122    /* The first device is always software */
123    assert(dev);
124    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
125 
126    while (dev->Next) {
127       dev = dev->Next;
128 
129       assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM));
130       if (drmDevicesEqual(device, dev->device) != 0) {
131          if (out_dev)
132             *out_dev = dev;
133          return 1;
134       }
135    }
136 
137    dev->Next = calloc(1, sizeof(_EGLDevice));
138    if (!dev->Next) {
139       if (out_dev)
140          *out_dev = NULL;
141       return -1;
142    }
143 
144    dev = dev->Next;
145    dev->extensions = "EGL_EXT_device_drm";
146    dev->EXT_device_drm = EGL_TRUE;
147    dev->device = device;
148 
149    /* TODO: EGL_EXT_device_drm_render_node support for kmsro + renderonly */
150    if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
151       dev->extensions = "EGL_EXT_device_drm EGL_EXT_device_drm_render_node";
152       dev->EXT_device_drm_render_node = EGL_TRUE;
153    }
154 
155    if (out_dev)
156       *out_dev = dev;
157 
158    return 0;
159 }
160 #endif
161 
162 /* Adds a device in DeviceList, if needed for the given fd.
163  *
164  * If a software device, the fd is ignored.
165  */
166 _EGLDevice *
_eglAddDevice(int fd,bool software)167 _eglAddDevice(int fd, bool software)
168 {
169    _EGLDevice *dev;
170 
171    mtx_lock(_eglGlobal.Mutex);
172    dev = _eglGlobal.DeviceList;
173 
174    /* The first device is always software */
175    assert(dev);
176    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
177    if (software)
178       goto out;
179 
180 #ifdef HAVE_LIBDRM
181    drmDevicePtr device;
182 
183    if (drmGetDevice2(fd, 0, &device) != 0) {
184       dev = NULL;
185       goto out;
186    }
187 
188    /* Device is not added - error or already present */
189    if (_eglAddDRMDevice(device, &dev) != 0)
190       drmFreeDevice(&device);
191 #else
192    _eglLog(_EGL_FATAL, "Driver bug: Built without libdrm, yet looking for HW device");
193    dev = NULL;
194 #endif
195 
196 out:
197    mtx_unlock(_eglGlobal.Mutex);
198    return dev;
199 }
200 
201 EGLBoolean
_eglDeviceSupports(_EGLDevice * dev,_EGLDeviceExtension ext)202 _eglDeviceSupports(_EGLDevice *dev, _EGLDeviceExtension ext)
203 {
204    switch (ext) {
205    case _EGL_DEVICE_SOFTWARE:
206       return dev->MESA_device_software;
207    case _EGL_DEVICE_DRM:
208       return dev->EXT_device_drm;
209    case _EGL_DEVICE_DRM_RENDER_NODE:
210       return dev->EXT_device_drm_render_node;
211    default:
212       assert(0);
213       return EGL_FALSE;
214    };
215 }
216 
217 /* Ideally we'll have an extension which passes the render node,
218  * instead of the card one + magic.
219  *
220  * Then we can move this in _eglQueryDeviceStringEXT below. Until then
221  * keep it separate.
222  */
223 const char *
_eglGetDRMDeviceRenderNode(_EGLDevice * dev)224 _eglGetDRMDeviceRenderNode(_EGLDevice *dev)
225 {
226 #ifdef HAVE_LIBDRM
227    return dev->device->nodes[DRM_NODE_RENDER];
228 #else
229    return NULL;
230 #endif
231 }
232 
233 EGLBoolean
_eglQueryDeviceAttribEXT(_EGLDevice * dev,EGLint attribute,EGLAttrib * value)234 _eglQueryDeviceAttribEXT(_EGLDevice *dev, EGLint attribute,
235                          EGLAttrib *value)
236 {
237    switch (attribute) {
238    default:
239       _eglError(EGL_BAD_ATTRIBUTE, "eglQueryDeviceAttribEXT");
240       return EGL_FALSE;
241    }
242 }
243 
244 const char *
_eglQueryDeviceStringEXT(_EGLDevice * dev,EGLint name)245 _eglQueryDeviceStringEXT(_EGLDevice *dev, EGLint name)
246 {
247    switch (name) {
248    case EGL_EXTENSIONS:
249       return dev->extensions;
250    case EGL_DRM_DEVICE_FILE_EXT:
251       if (!_eglDeviceSupports(dev, _EGL_DEVICE_DRM))
252          break;
253 #ifdef HAVE_LIBDRM
254       return dev->device->nodes[DRM_NODE_PRIMARY];
255 #else
256       /* This should never happen: we don't yet support EGL_DEVICE_DRM for the
257        * software device, and physical devices are only exposed when libdrm is
258        * available. */
259       assert(0);
260       break;
261 #endif
262    case EGL_DRM_RENDER_NODE_FILE_EXT:
263       if (!_eglDeviceSupports(dev, _EGL_DEVICE_DRM_RENDER_NODE))
264          break;
265 #ifdef HAVE_LIBDRM
266       return dev->device ? dev->device->nodes[DRM_NODE_RENDER] : NULL;
267 #else
268       /* Physical devices are only exposed when libdrm is available. */
269       assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
270       return NULL;
271 #endif
272    }
273    _eglError(EGL_BAD_PARAMETER, "eglQueryDeviceStringEXT");
274    return NULL;
275 }
276 
277 /* Do a fresh lookup for devices.
278  *
279  * Walks through the DeviceList, discarding no longer available ones
280  * and adding new ones as applicable.
281  *
282  * Must be called with the global lock held.
283  */
284 static int
_eglRefreshDeviceList(void)285 _eglRefreshDeviceList(void)
286 {
287    ASSERTED _EGLDevice *dev;
288    int count = 0;
289 
290    dev = _eglGlobal.DeviceList;
291 
292    /* The first device is always software */
293    assert(dev);
294    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
295    count++;
296 
297 #ifdef HAVE_LIBDRM
298    drmDevicePtr devices[64];
299    int num_devs, ret;
300 
301    num_devs = drmGetDevices2(0, devices, ARRAY_SIZE(devices));
302    for (int i = 0; i < num_devs; i++) {
303       if (!(devices[i]->available_nodes & (1 << DRM_NODE_RENDER)))
304          continue;
305 
306       ret = _eglAddDRMDevice(devices[i], NULL);
307 
308       /* Device is not added - error or already present */
309       if (ret != 0)
310          drmFreeDevice(&devices[i]);
311 
312       if (ret >= 0)
313          count++;
314    }
315 #endif
316 
317    return count;
318 }
319 
320 EGLBoolean
_eglQueryDevicesEXT(EGLint max_devices,_EGLDevice ** devices,EGLint * num_devices)321 _eglQueryDevicesEXT(EGLint max_devices,
322                     _EGLDevice **devices,
323                     EGLint *num_devices)
324 {
325    _EGLDevice *dev, *devs;
326    int i = 0, num_devs;
327 
328    if ((devices && max_devices <= 0) || !num_devices)
329       return _eglError(EGL_BAD_PARAMETER, "eglQueryDevicesEXT");
330 
331    mtx_lock(_eglGlobal.Mutex);
332 
333    num_devs = _eglRefreshDeviceList();
334    devs = _eglGlobal.DeviceList;
335 
336    /* bail early if we only care about the count */
337    if (!devices) {
338       *num_devices = num_devs;
339       goto out;
340    }
341 
342    /* Push the first device (the software one) to the end of the list.
343     * Sending it to the user only if they've requested the full list.
344     *
345     * By default, the user is likely to pick the first device so having the
346     * software (aka least performant) one is not a good idea.
347     */
348    *num_devices = MIN2(num_devs, max_devices);
349 
350    for (i = 0, dev = devs->Next; dev && i < max_devices; i++) {
351       devices[i] = dev;
352       dev = dev->Next;
353    }
354 
355    /* User requested the full device list, add the sofware device. */
356    if (max_devices >= num_devs) {
357       assert(_eglDeviceSupports(devs, _EGL_DEVICE_SOFTWARE));
358       devices[num_devs - 1] = devs;
359    }
360 
361 out:
362    mtx_unlock(_eglGlobal.Mutex);
363 
364    return EGL_TRUE;
365 }
366