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/macros.h"
32 
33 #include "eglcurrent.h"
34 #include "egldevice.h"
35 #include "egllog.h"
36 #include "eglglobals.h"
37 #include "egltypedefs.h"
38 
39 
40 struct _egl_device {
41    _EGLDevice *Next;
42 
43    const char *extensions;
44 
45    EGLBoolean MESA_device_software;
46    EGLBoolean EXT_device_drm;
47 
48 #ifdef HAVE_LIBDRM
49    drmDevicePtr device;
50 #endif
51 };
52 
53 void
_eglFiniDevice(void)54 _eglFiniDevice(void)
55 {
56    _EGLDevice *dev_list, *dev;
57 
58    /* atexit function is called with global mutex locked */
59 
60    dev_list = _eglGlobal.DeviceList;
61 
62    /* The first device is static allocated SW device */
63    assert(dev_list);
64    assert(_eglDeviceSupports(dev_list, _EGL_DEVICE_SOFTWARE));
65    dev_list = dev_list->Next;
66 
67    while (dev_list) {
68       /* pop list head */
69       dev = dev_list;
70       dev_list = dev_list->Next;
71 
72 #ifdef HAVE_LIBDRM
73       assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM));
74       drmFreeDevice(&dev->device);
75 #endif
76       free(dev);
77    }
78 
79    _eglGlobal.DeviceList = NULL;
80 }
81 
82 EGLBoolean
_eglCheckDeviceHandle(EGLDeviceEXT device)83 _eglCheckDeviceHandle(EGLDeviceEXT device)
84 {
85    _EGLDevice *cur;
86 
87    mtx_lock(_eglGlobal.Mutex);
88    cur = _eglGlobal.DeviceList;
89    while (cur) {
90       if (cur == (_EGLDevice *) device)
91          break;
92       cur = cur->Next;
93    }
94    mtx_unlock(_eglGlobal.Mutex);
95    return (cur != NULL);
96 }
97 
98 _EGLDevice _eglSoftwareDevice = {
99    .extensions = "EGL_MESA_device_software",
100    .MESA_device_software = EGL_TRUE,
101 };
102 
103 #ifdef HAVE_LIBDRM
104 /*
105  * Negative value on error, zero if newly added, one if already in list.
106  */
107 static int
_eglAddDRMDevice(drmDevicePtr device,_EGLDevice ** out_dev)108 _eglAddDRMDevice(drmDevicePtr device, _EGLDevice **out_dev)
109 {
110    _EGLDevice *dev;
111    const int wanted_nodes = 1 << DRM_NODE_RENDER | 1 << DRM_NODE_PRIMARY;
112 
113    if ((device->available_nodes & wanted_nodes) != wanted_nodes)
114       return -1;
115 
116    dev = _eglGlobal.DeviceList;
117 
118    /* The first device is always software */
119    assert(dev);
120    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
121 
122    while (dev->Next) {
123       dev = dev->Next;
124 
125       assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM));
126       if (drmDevicesEqual(device, dev->device) != 0) {
127          if (out_dev)
128             *out_dev = dev;
129          return 1;
130       }
131    }
132 
133    dev->Next = calloc(1, sizeof(_EGLDevice));
134    if (!dev->Next) {
135       if (out_dev)
136          *out_dev = NULL;
137       return -1;
138    }
139 
140    dev = dev->Next;
141    dev->extensions = "EGL_EXT_device_drm";
142    dev->EXT_device_drm = EGL_TRUE;
143    dev->device = device;
144 
145    if (out_dev)
146       *out_dev = dev;
147 
148    return 0;
149 }
150 #endif
151 
152 /* Adds a device in DeviceList, if needed for the given fd.
153  *
154  * If a software device, the fd is ignored.
155  */
156 _EGLDevice *
_eglAddDevice(int fd,bool software)157 _eglAddDevice(int fd, bool software)
158 {
159    _EGLDevice *dev;
160 
161    mtx_lock(_eglGlobal.Mutex);
162    dev = _eglGlobal.DeviceList;
163 
164    /* The first device is always software */
165    assert(dev);
166    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
167    if (software)
168       goto out;
169 
170 #ifdef HAVE_LIBDRM
171    drmDevicePtr device;
172 
173    if (drmGetDevice2(fd, 0, &device) != 0) {
174       dev = NULL;
175       goto out;
176    }
177 
178    /* Device is not added - error or already present */
179    if (_eglAddDRMDevice(device, &dev) != 0)
180       drmFreeDevice(&device);
181 #else
182    _eglLog(_EGL_FATAL, "Driver bug: Built without libdrm, yet looking for HW device");
183    dev = NULL;
184 #endif
185 
186 out:
187    mtx_unlock(_eglGlobal.Mutex);
188    return dev;
189 }
190 
191 EGLBoolean
_eglDeviceSupports(_EGLDevice * dev,_EGLDeviceExtension ext)192 _eglDeviceSupports(_EGLDevice *dev, _EGLDeviceExtension ext)
193 {
194    switch (ext) {
195    case _EGL_DEVICE_SOFTWARE:
196       return dev->MESA_device_software;
197    case _EGL_DEVICE_DRM:
198       return dev->EXT_device_drm;
199    default:
200       assert(0);
201       return EGL_FALSE;
202    };
203 }
204 
205 /* Ideally we'll have an extension which passes the render node,
206  * instead of the card one + magic.
207  *
208  * Then we can move this in _eglQueryDeviceStringEXT below. Until then
209  * keep it separate.
210  */
211 const char *
_eglGetDRMDeviceRenderNode(_EGLDevice * dev)212 _eglGetDRMDeviceRenderNode(_EGLDevice *dev)
213 {
214 #ifdef HAVE_LIBDRM
215    return dev->device->nodes[DRM_NODE_RENDER];
216 #else
217    return NULL;
218 #endif
219 }
220 
221 EGLBoolean
_eglQueryDeviceAttribEXT(_EGLDevice * dev,EGLint attribute,EGLAttrib * value)222 _eglQueryDeviceAttribEXT(_EGLDevice *dev, EGLint attribute,
223                          EGLAttrib *value)
224 {
225    switch (attribute) {
226    default:
227       _eglError(EGL_BAD_ATTRIBUTE, "eglQueryDeviceStringEXT");
228       return EGL_FALSE;
229    }
230 }
231 
232 const char *
_eglQueryDeviceStringEXT(_EGLDevice * dev,EGLint name)233 _eglQueryDeviceStringEXT(_EGLDevice *dev, EGLint name)
234 {
235    switch (name) {
236    case EGL_EXTENSIONS:
237       return dev->extensions;
238 #ifdef HAVE_LIBDRM
239    case EGL_DRM_DEVICE_FILE_EXT:
240       if (_eglDeviceSupports(dev, _EGL_DEVICE_DRM))
241          return dev->device->nodes[DRM_NODE_PRIMARY];
242 #endif
243       /* fall through */
244    default:
245       _eglError(EGL_BAD_PARAMETER, "eglQueryDeviceStringEXT");
246       return NULL;
247    };
248 }
249 
250 /* Do a fresh lookup for devices.
251  *
252  * Walks through the DeviceList, discarding no longer available ones
253  * and adding new ones as applicable.
254  *
255  * Must be called with the global lock held.
256  */
257 static int
_eglRefreshDeviceList(void)258 _eglRefreshDeviceList(void)
259 {
260    ASSERTED _EGLDevice *dev;
261    int count = 0;
262 
263    dev = _eglGlobal.DeviceList;
264 
265    /* The first device is always software */
266    assert(dev);
267    assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE));
268    count++;
269 
270 #ifdef HAVE_LIBDRM
271    drmDevicePtr devices[64];
272    int num_devs, ret;
273 
274    num_devs = drmGetDevices2(0, devices, ARRAY_SIZE(devices));
275    for (int i = 0; i < num_devs; i++) {
276       ret = _eglAddDRMDevice(devices[i], NULL);
277 
278       /* Device is not added - error or already present */
279       if (ret != 0)
280          drmFreeDevice(&devices[i]);
281 
282       if (ret >= 0)
283          count++;
284    }
285 #endif
286 
287    return count;
288 }
289 
290 EGLBoolean
_eglQueryDevicesEXT(EGLint max_devices,_EGLDevice ** devices,EGLint * num_devices)291 _eglQueryDevicesEXT(EGLint max_devices,
292                     _EGLDevice **devices,
293                     EGLint *num_devices)
294 {
295    _EGLDevice *dev, *devs;
296    int i = 0, num_devs;
297 
298    if ((devices && max_devices <= 0) || !num_devices)
299       return _eglError(EGL_BAD_PARAMETER, "eglQueryDevicesEXT");
300 
301    mtx_lock(_eglGlobal.Mutex);
302 
303    num_devs = _eglRefreshDeviceList();
304    devs = _eglGlobal.DeviceList;
305 
306    /* bail early if we only care about the count */
307    if (!devices) {
308       *num_devices = num_devs;
309       goto out;
310    }
311 
312    /* Push the first device (the software one) to the end of the list.
313     * Sending it to the user only if they've requested the full list.
314     *
315     * By default, the user is likely to pick the first device so having the
316     * software (aka least performant) one is not a good idea.
317     */
318    *num_devices = MIN2(num_devs, max_devices);
319 
320    for (i = 0, dev = devs->Next; dev && i < max_devices; i++) {
321       devices[i] = dev;
322       dev = dev->Next;
323    }
324 
325    /* User requested the full device list, add the sofware device. */
326    if (max_devices >= num_devs) {
327       assert(_eglDeviceSupports(devs, _EGL_DEVICE_SOFTWARE));
328       devices[num_devs - 1] = devs;
329    }
330 
331 out:
332    mtx_unlock(_eglGlobal.Mutex);
333 
334    return EGL_TRUE;
335 }
336