/************************************************************************** * * 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 #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; const int wanted_nodes = 1 << DRM_NODE_RENDER | 1 << DRM_NODE_PRIMARY; if ((device->available_nodes & wanted_nodes) != wanted_nodes) 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) { ASSERTED _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; }