backend-drm: import GBM bo to scanout device if necessary

If the GBM bo was allocated on a different device than the device that is used
for the fb, we have to import the fd first and update the handle.

Use drmPrimeFDToHandle directly instead of using a gbm device for the scanout
device, since a gbm device would require a gbm implementation, which is often
not available for devices that only support scanout.

Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
This commit is contained in:
Michael Tretter 2021-11-10 16:20:44 +01:00 committed by Daniel Stone
parent 575804c7e1
commit 7887d3fb48
4 changed files with 157 additions and 0 deletions

View file

@ -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;

View file

@ -51,6 +51,7 @@
#include <libweston/backend-drm.h>
#include <libweston/weston-log.h>
#include "drm-internal.h"
#include "shared/hash.h"
#include "shared/helpers.h"
#include "shared/timespec-util.h"
#include "shared/string-helpers.h"

View file

@ -38,6 +38,7 @@
#include <libweston/backend-drm.h>
#include <libweston/pixel-formats.h>
#include <libweston/linux-dmabuf.h>
#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);
}

View file

@ -36,6 +36,7 @@ deps_drm = [
dep_egl, # optional
dep_libm,
dep_libdl,
dep_libshared,
dep_libweston_private,
dep_session_helper,
dep_libdrm,