}
EXPORT_SYMBOL(drm_plane_init);
+/**
+ * drm_share_plane_init - Initialize a share plane
+ * @dev: DRM device
+ * @plane: plane object to init
+ * @parent: this plane share some resources with parent plane.
+ * @possible_crtcs: bitmask of possible CRTCs
+ * @funcs: callbacks for the new plane
+ * @formats: array of supported formats (%DRM_FORMAT_*)
+ * @format_count: number of elements in @formats
+ * @type: type of plane (overlay, primary, cursor)
+ *
+ * With this API, the plane can share hardware resources with other planes.
+ *
+ * --------------------------------------------------
+ * | scanout |
+ * | ------------------ |
+ * | | parent plane | |
+ * | | active scanout | |
+ * | | | ----------------- |
+ * | ------------------ | share plane 1 | |
+ * | ----------------- |active scanout | |
+ * | | share plane 0 | | | |
+ * | |active scanout | ----------------- |
+ * | | | |
+ * | ----------------- |
+ * --------------------------------------------------
+ *
+ * parent plane
+ * |---share plane 0
+ * |---share plane 1
+ * ...
+ *
+ * The plane hardware is used when the display scanout run into plane active
+ * scanout, that means we can reuse the plane hardware resources on plane
+ * non-active scanout.
+ *
+ * Because resource share, There are some limit on share plane: one group
+ * of share planes need use same zpos, can't not overlap, etc.
+ *
+ * Here assume share plane is a universal plane with some limit flags.
+ * people who use the share plane need know the limit, should call the ioctl
+ * DRM_CLIENT_CAP_SHARE_PLANES, and judge the planes limit before use it.
+ *
+ * Returns:
+ * Zero on success, error code on failure.
+ */
+
+int drm_share_plane_init(struct drm_device *dev, struct drm_plane *plane,
+ struct drm_plane *parent,
+ unsigned long possible_crtcs,
+ const struct drm_plane_funcs *funcs,
+ const uint32_t *formats, unsigned int format_count,
+ enum drm_plane_type type)
+{
+ struct drm_mode_config *config = &dev->mode_config;
+ int ret;
+ int share_id;
+
+ /*
+ * TODO: only verified on ATOMIC drm driver.
+ */
+ if (!drm_core_check_feature(dev, DRIVER_ATOMIC))
+ return -EINVAL;
+
+ ret = drm_universal_plane_init(dev, plane, possible_crtcs, funcs,
+ formats, format_count, type, NULL);
+ if (ret)
+ return ret;
+
+ if (parent) {
+ /*
+ * Can't support more than two level plane share.
+ */
+ WARN_ON(parent->parent);
+ share_id = parent->base.id;
+ plane->parent = parent;
+
+ config->num_share_plane++;
+ if (plane->type == DRM_PLANE_TYPE_OVERLAY)
+ config->num_share_overlay_plane++;
+ } else {
+ share_id = plane->base.id;
+ }
+
+ drm_object_attach_property(&plane->base,
+ config->prop_share_id, share_id);
+ return 0;
+}
+EXPORT_SYMBOL(drm_share_plane_init);
+
/**
* drm_plane_cleanup - Clean up the core plane usage
* @plane: plane to cleanup
dev->mode_config.num_total_plane--;
if (plane->type == DRM_PLANE_TYPE_OVERLAY)
dev->mode_config.num_overlay_plane--;
+ if (plane->parent) {
+ dev->mode_config.num_share_plane--;
+ if (plane->type == DRM_PLANE_TYPE_OVERLAY)
+ dev->mode_config.num_share_overlay_plane--;
+ }
drm_modeset_unlock_all(dev);
WARN_ON(plane->state && !plane->funcs->atomic_destroy_state);
return -ENOMEM;
dev->mode_config.plane_type_property = prop;
+ prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
+ "SHARE_ID", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+
+ dev->mode_config.prop_share_id = prop;
+ prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE,
+ "SHARE_FLAGS", 0, UINT_MAX);
+ if (!prop)
+ return -ENOMEM;
+ dev->mode_config.prop_share_flags = prop;
+
prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
"SRC_X", 0, UINT_MAX);
if (!prop)
num_planes = config->num_total_plane;
else
num_planes = config->num_overlay_plane;
+ if (!file_priv->share_planes) {
+ if (file_priv->universal_planes)
+ num_planes -= config->num_share_plane;
+ else
+ num_planes -= config->num_share_overlay_plane;
+ }
/*
* This ioctl is called twice, once to determine how much space is
if (plane->type != DRM_PLANE_TYPE_OVERLAY &&
!file_priv->universal_planes)
continue;
+ if (plane->parent && !file_priv->share_planes)
+ continue;
if (put_user(plane->base.id, plane_ptr + copied))
return -EFAULT;
return 0;
}
+struct drm_mode_rmfb_work {
+ struct work_struct work;
+ struct list_head fbs;
+};
+
+static void drm_mode_rmfb_work_fn(struct work_struct *w)
+{
+ struct drm_mode_rmfb_work *arg = container_of(w, typeof(*arg), work);
+
+ while (!list_empty(&arg->fbs)) {
+ struct drm_framebuffer *fb =
+ list_first_entry(&arg->fbs, typeof(*fb), filp_head);
+
+ list_del_init(&fb->filp_head);
+ drm_framebuffer_remove(fb);
+ }
+}
+
/**
* drm_mode_rmfb - remove an FB from the configuration
* @dev: drm device for the ioctl
mutex_unlock(&dev->mode_config.fb_lock);
mutex_unlock(&file_priv->fbs_lock);
- drm_framebuffer_unreference(fb);
+ /*
+ * we now own the reference that was stored in the fbs list
+ *
+ * drm_framebuffer_remove may fail with -EINTR on pending signals,
+ * so run this in a separate stack as there's no way to correctly
+ * handle this after the fb is already removed from the lookup table.
+ */
+ if (atomic_read(&fb->refcount.refcount) > 1) {
+ struct drm_mode_rmfb_work arg;
+
+ INIT_WORK_ONSTACK(&arg.work, drm_mode_rmfb_work_fn);
+ INIT_LIST_HEAD(&arg.fbs);
+ list_add_tail(&fb->filp_head, &arg.fbs);
+
+ schedule_work(&arg.work);
+ flush_work(&arg.work);
+ destroy_work_on_stack(&arg.work);
+ } else
+ drm_framebuffer_unreference(fb);
return 0;
return ret;
}
-
/**
* drm_fb_release - remove and free the FBs on this file
* @priv: drm file for the ioctl
void drm_fb_release(struct drm_file *priv)
{
struct drm_framebuffer *fb, *tfb;
+ struct drm_mode_rmfb_work arg;
+
+ INIT_LIST_HEAD(&arg.fbs);
/*
* When the file gets released that means no one else can access the fb
* at it any more.
*/
list_for_each_entry_safe(fb, tfb, &priv->fbs, filp_head) {
- list_del_init(&fb->filp_head);
+ if (atomic_read(&fb->refcount.refcount) > 1) {
+ list_move_tail(&fb->filp_head, &arg.fbs);
+ } else {
+ list_del_init(&fb->filp_head);
- /* This drops the fpriv->fbs reference. */
- drm_framebuffer_unreference(fb);
+ /* This drops the fpriv->fbs reference. */
+ drm_framebuffer_unreference(fb);
+ }
+ }
+
+ if (!list_empty(&arg.fbs)) {
+ INIT_WORK_ONSTACK(&arg.work, drm_mode_rmfb_work_fn);
+
+ schedule_work(&arg.work);
+ flush_work(&arg.work);
+ destroy_work_on_stack(&arg.work);
}
}