diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index a21ad4d6..4711de02 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -300,6 +300,11 @@ struct drm_device { dev_t devnum; } drm; + /* Track the GEM handles if the device does not have a gbm device, which + * tracks the handles for us. + */ + struct hash_table *gem_handle_refcnt; + /* drm_crtc::link */ struct wl_list crtc_list; @@ -378,6 +383,8 @@ enum drm_fb_type { struct drm_fb { enum drm_fb_type type; + struct drm_device *scanout_device; + int refcnt; uint32_t fb_id, size; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 5d34433b..2849b448 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -51,6 +51,7 @@ #include #include #include "drm-internal.h" +#include "shared/hash.h" #include "shared/helpers.h" #include "shared/timespec-util.h" #include "shared/string-helpers.h" diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c index b73153e4..40d4bf0c 100644 --- a/libweston/backend-drm/fb.c +++ b/libweston/backend-drm/fb.c @@ -38,6 +38,7 @@ #include #include #include +#include "shared/hash.h" #include "shared/helpers.h" #include "shared/weston-drm-fourcc.h" #include "drm-internal.h" @@ -68,6 +69,138 @@ drm_fb_destroy_dumb(struct drm_fb *fb) drm_fb_destroy(fb); } +#ifdef BUILD_DRM_GBM +static int gem_handle_get(struct drm_device *device, int handle) +{ + unsigned int *ref_count; + + ref_count = hash_table_lookup(device->gem_handle_refcnt, handle); + if (!ref_count) { + ref_count = zalloc(sizeof(*ref_count)); + hash_table_insert(device->gem_handle_refcnt, handle, ref_count); + } + (*ref_count)++; + + return handle; +} + +static void gem_handle_put(struct drm_device *device, int handle) +{ + unsigned int *ref_count; + + if (handle == 0) + return; + + ref_count = hash_table_lookup(device->gem_handle_refcnt, handle); + if (!ref_count) { + weston_log("failed to find GEM handle %d for device %s\n", + handle, device->drm.filename); + return; + } + (*ref_count)--; + + if (*ref_count == 0) { + hash_table_remove(device->gem_handle_refcnt, handle); + free(ref_count); + drmCloseBufferHandle(device->drm.fd, handle); + } +} + +static int +drm_fb_import_plane(struct drm_device *device, struct drm_fb *fb, int plane) +{ + int bo_fd; + uint32_t handle; + int ret; + + bo_fd = gbm_bo_get_fd_for_plane(fb->bo, plane); + if (bo_fd < 0) + return bo_fd; + + /* + * drmPrimeFDToHandle is dangerous, because the GEM handles are + * not reference counted by the kernel and user space needs a + * single reference counting implementation to avoid double + * closing of GEM handles. + * + * It is not desirable to use a GBM device here, because this + * requires a GBM device implementation, which might not be + * available for simple or custom DRM devices that only support + * scanout and no rendering. + * + * We are only importing the buffers from the render device to + * the scanout device if the devices are distinct, since + * otherwise no import is necessary. Therefore, we are the only + * instance using the handles and we can implement reference + * counting for the handles per device. See gem_handle_get and + * gem_handle_put for the implementation. + */ + ret = drmPrimeFDToHandle(fb->fd, bo_fd, &handle); + if (ret) + goto out; + + fb->handles[plane] = gem_handle_get(device, handle); + +out: + close(bo_fd); + return ret; +} +#endif + +/* + * If the fb is using a GBM surface, there is a possibility that the GBM + * surface has been created on a different device than the device which + * should be used for the fb. We have to import the fd of the GBM bo + * into the scanout device. + */ +static int +drm_fb_maybe_import(struct drm_device *device, struct drm_fb *fb) +{ +#ifndef BUILD_DRM_GBM + /* + * Without GBM support, the fb is always allocated on the scanout device + * and import is never necessary. + */ + return 0; +#else + struct gbm_device *gbm_device; + int ret = 0; + int plane; + + /* No import possible, if there is no gbm bo */ + if (!fb->bo) + return 0; + + /* No import necessary, if the gbm bo and the fb use the same device */ + gbm_device = gbm_bo_get_device(fb->bo); + if (gbm_device_get_fd(gbm_device) == fb->fd) + return 0; + + if (fb->fd != device->drm.fd) { + weston_log("fb was not allocated for scanout device %s\n", + device->drm.filename); + return -1; + } + + for (plane = 0; plane < gbm_bo_get_plane_count(fb->bo); plane++) { + ret = drm_fb_import_plane(device, fb, plane); + if (ret) + goto err; + } + + fb->scanout_device = device; + + return 0; +err: + for (; plane >= 0; plane--) { + gem_handle_put(device, fb->handles[plane]); + fb->handles[plane] = 0; + } + + return ret; +#endif +} + static int drm_fb_addfb(struct drm_device *device, struct drm_fb *fb) { @@ -75,6 +208,10 @@ drm_fb_addfb(struct drm_device *device, struct drm_fb *fb) uint64_t mods[4] = { }; size_t i; + ret = drm_fb_maybe_import(device, fb); + if (ret) + return ret; + /* If we have a modifier set, we must only use the WithModifiers * entrypoint; we cannot import it through legacy ioctls. */ if (device->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { @@ -209,10 +346,21 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) static void drm_fb_destroy_dmabuf(struct drm_fb *fb) { + int i; + /* We deliberately do not close the GEM handles here; GBM manages * their lifetime through the BO. */ if (fb->bo) gbm_bo_destroy(fb->bo); + + /* + * If we imported the dmabuf into a scanout device, we are responsible + * for closing the GEM handle. + */ + for (i = 0; i < 4; i++) + if (fb->scanout_device && fb->handles[i] != 0) + gem_handle_put(fb->scanout_device, fb->handles[i]); + drm_fb_destroy(fb); } diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index 179e4a16..e15be78b 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -36,6 +36,7 @@ deps_drm = [ dep_egl, # optional dep_libm, dep_libdl, + dep_libshared, dep_libweston_private, dep_session_helper, dep_libdrm,