| /************************************************************************** |
| * |
| * Copyright 2015, 2018 Collabora |
| * All Rights Reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sub license, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial portions |
| * of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| **************************************************************************/ |
| |
| #ifdef HAVE_LIBDRM |
| #include <xf86drm.h> |
| #endif |
| #include "util/macros.h" |
| |
| #include "eglcurrent.h" |
| #include "egldevice.h" |
| #include "egllog.h" |
| #include "eglglobals.h" |
| #include "egltypedefs.h" |
| |
| |
| struct _egl_device { |
| _EGLDevice *Next; |
| |
| const char *extensions; |
| |
| EGLBoolean MESA_device_software; |
| EGLBoolean EXT_device_drm; |
| |
| #ifdef HAVE_LIBDRM |
| drmDevicePtr device; |
| #endif |
| }; |
| |
| void |
| _eglFiniDevice(void) |
| { |
| _EGLDevice *dev_list, *dev; |
| |
| /* atexit function is called with global mutex locked */ |
| |
| dev_list = _eglGlobal.DeviceList; |
| |
| /* The first device is static allocated SW device */ |
| assert(dev_list); |
| assert(_eglDeviceSupports(dev_list, _EGL_DEVICE_SOFTWARE)); |
| dev_list = dev_list->Next; |
| |
| while (dev_list) { |
| /* pop list head */ |
| dev = dev_list; |
| dev_list = dev_list->Next; |
| |
| #ifdef HAVE_LIBDRM |
| assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM)); |
| drmFreeDevice(&dev->device); |
| #endif |
| free(dev); |
| } |
| |
| _eglGlobal.DeviceList = NULL; |
| } |
| |
| EGLBoolean |
| _eglCheckDeviceHandle(EGLDeviceEXT device) |
| { |
| _EGLDevice *cur; |
| |
| mtx_lock(_eglGlobal.Mutex); |
| cur = _eglGlobal.DeviceList; |
| while (cur) { |
| if (cur == (_EGLDevice *) device) |
| break; |
| cur = cur->Next; |
| } |
| mtx_unlock(_eglGlobal.Mutex); |
| return (cur != NULL); |
| } |
| |
| _EGLDevice _eglSoftwareDevice = { |
| .extensions = "EGL_MESA_device_software", |
| .MESA_device_software = EGL_TRUE, |
| }; |
| |
| #ifdef HAVE_LIBDRM |
| /* |
| * Negative value on error, zero if newly added, one if already in list. |
| */ |
| static int |
| _eglAddDRMDevice(drmDevicePtr device, _EGLDevice **out_dev) |
| { |
| _EGLDevice *dev; |
| |
| if ((device->available_nodes & (1 << DRM_NODE_PRIMARY | |
| 1 << DRM_NODE_RENDER)) == 0) |
| return -1; |
| |
| dev = _eglGlobal.DeviceList; |
| |
| /* The first device is always software */ |
| assert(dev); |
| assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE)); |
| |
| while (dev->Next) { |
| dev = dev->Next; |
| |
| assert(_eglDeviceSupports(dev, _EGL_DEVICE_DRM)); |
| if (drmDevicesEqual(device, dev->device) != 0) { |
| if (out_dev) |
| *out_dev = dev; |
| return 1; |
| } |
| } |
| |
| dev->Next = calloc(1, sizeof(_EGLDevice)); |
| if (!dev->Next) { |
| if (out_dev) |
| *out_dev = NULL; |
| return -1; |
| } |
| |
| dev = dev->Next; |
| dev->extensions = "EGL_EXT_device_drm"; |
| dev->EXT_device_drm = EGL_TRUE; |
| dev->device = device; |
| |
| if (out_dev) |
| *out_dev = dev; |
| |
| return 0; |
| } |
| #endif |
| |
| /* Adds a device in DeviceList, if needed for the given fd. |
| * |
| * If a software device, the fd is ignored. |
| */ |
| _EGLDevice * |
| _eglAddDevice(int fd, bool software) |
| { |
| _EGLDevice *dev; |
| |
| mtx_lock(_eglGlobal.Mutex); |
| dev = _eglGlobal.DeviceList; |
| |
| /* The first device is always software */ |
| assert(dev); |
| assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE)); |
| if (software) |
| goto out; |
| |
| #ifdef HAVE_LIBDRM |
| drmDevicePtr device; |
| |
| if (drmGetDevice2(fd, 0, &device) != 0) { |
| dev = NULL; |
| goto out; |
| } |
| |
| /* Device is not added - error or already present */ |
| if (_eglAddDRMDevice(device, &dev) != 0) |
| drmFreeDevice(&device); |
| #else |
| _eglLog(_EGL_FATAL, "Driver bug: Built without libdrm, yet looking for HW device"); |
| dev = NULL; |
| #endif |
| |
| out: |
| mtx_unlock(_eglGlobal.Mutex); |
| return dev; |
| } |
| |
| EGLBoolean |
| _eglDeviceSupports(_EGLDevice *dev, _EGLDeviceExtension ext) |
| { |
| switch (ext) { |
| case _EGL_DEVICE_SOFTWARE: |
| return dev->MESA_device_software; |
| case _EGL_DEVICE_DRM: |
| return dev->EXT_device_drm; |
| default: |
| assert(0); |
| return EGL_FALSE; |
| }; |
| } |
| |
| /* Ideally we'll have an extension which passes the render node, |
| * instead of the card one + magic. |
| * |
| * Then we can move this in _eglQueryDeviceStringEXT below. Until then |
| * keep it separate. |
| */ |
| const char * |
| _eglGetDRMDeviceRenderNode(_EGLDevice *dev) |
| { |
| #ifdef HAVE_LIBDRM |
| return dev->device->nodes[DRM_NODE_RENDER]; |
| #else |
| return NULL; |
| #endif |
| } |
| |
| EGLBoolean |
| _eglQueryDeviceAttribEXT(_EGLDevice *dev, EGLint attribute, |
| EGLAttrib *value) |
| { |
| switch (attribute) { |
| default: |
| _eglError(EGL_BAD_ATTRIBUTE, "eglQueryDeviceStringEXT"); |
| return EGL_FALSE; |
| } |
| } |
| |
| const char * |
| _eglQueryDeviceStringEXT(_EGLDevice *dev, EGLint name) |
| { |
| switch (name) { |
| case EGL_EXTENSIONS: |
| return dev->extensions; |
| #ifdef HAVE_LIBDRM |
| case EGL_DRM_DEVICE_FILE_EXT: |
| if (_eglDeviceSupports(dev, _EGL_DEVICE_DRM)) |
| return dev->device->nodes[DRM_NODE_PRIMARY]; |
| /* fall through */ |
| #endif |
| default: |
| _eglError(EGL_BAD_PARAMETER, "eglQueryDeviceStringEXT"); |
| return NULL; |
| }; |
| } |
| |
| /* Do a fresh lookup for devices. |
| * |
| * Walks through the DeviceList, discarding no longer available ones |
| * and adding new ones as applicable. |
| * |
| * Must be called with the global lock held. |
| */ |
| static int |
| _eglRefreshDeviceList(void) |
| { |
| MAYBE_UNUSED _EGLDevice *dev; |
| int count = 0; |
| |
| dev = _eglGlobal.DeviceList; |
| |
| /* The first device is always software */ |
| assert(dev); |
| assert(_eglDeviceSupports(dev, _EGL_DEVICE_SOFTWARE)); |
| count++; |
| |
| #ifdef HAVE_LIBDRM |
| drmDevicePtr devices[64]; |
| int num_devs, ret; |
| |
| num_devs = drmGetDevices2(0, devices, ARRAY_SIZE(devices)); |
| for (int i = 0; i < num_devs; i++) { |
| ret = _eglAddDRMDevice(devices[i], NULL); |
| |
| /* Device is not added - error or already present */ |
| if (ret != 0) |
| drmFreeDevice(&devices[i]); |
| |
| if (ret >= 0) |
| count++; |
| } |
| #endif |
| |
| return count; |
| } |
| |
| EGLBoolean |
| _eglQueryDevicesEXT(EGLint max_devices, |
| _EGLDevice **devices, |
| EGLint *num_devices) |
| { |
| _EGLDevice *dev, *devs; |
| int i = 0, num_devs; |
| |
| if ((devices && max_devices <= 0) || !num_devices) |
| return _eglError(EGL_BAD_PARAMETER, "eglQueryDevicesEXT"); |
| |
| mtx_lock(_eglGlobal.Mutex); |
| |
| num_devs = _eglRefreshDeviceList(); |
| devs = _eglGlobal.DeviceList; |
| |
| /* bail early if we only care about the count */ |
| if (!devices) { |
| *num_devices = num_devs; |
| goto out; |
| } |
| |
| /* Push the first device (the software one) to the end of the list. |
| * Sending it to the user only if they've requested the full list. |
| * |
| * By default, the user is likely to pick the first device so having the |
| * software (aka least performant) one is not a good idea. |
| */ |
| *num_devices = MIN2(num_devs, max_devices); |
| |
| for (i = 0, dev = devs->Next; dev && i < max_devices; i++) { |
| devices[i] = dev; |
| dev = dev->Next; |
| } |
| |
| /* User requested the full device list, add the sofware device. */ |
| if (max_devices >= num_devs) { |
| assert(_eglDeviceSupports(devs, _EGL_DEVICE_SOFTWARE)); |
| devices[num_devs - 1] = devs; |
| } |
| |
| out: |
| mtx_unlock(_eglGlobal.Mutex); |
| |
| return EGL_TRUE; |
| } |