[RFCv4 10/14] drm: convert crtc to properties/state

Sean Paul seanpaul at chromium.org
Tue Mar 4 14:36:40 PST 2014


On Tue, Mar 4, 2014 at 5:04 PM, Rob Clark <robdclark at gmail.com> wrote:
> On Tue, Mar 4, 2014 at 4:29 PM, Sean Paul <seanpaul at chromium.org> wrote:
>> On Mon, Nov 25, 2013 at 9:47 AM, Rob Clark <robdclark at gmail.com> wrote:
>>> Break the mutable state of a crtc out into a separate structure
>>> and use atomic properties mechanism to set crtc attributes.  This
>>> makes it easier to have some helpers for crtc->set_property()
>>> and for checking for invalid params.  The idea is that individual
>>> drivers can wrap the state struct in their own struct which adds
>>> driver specific parameters, for easy build-up of state across
>>> multiple set_property() calls and for easy atomic commit or roll-
>>> back.
>>> ---
>
> <snip>
>
>>> +static int check_connectors(struct drm_crtc *crtc, void *state, bool fix,
>>> +               uint32_t *connector_ids, uint32_t num_connector_ids)
>>> +{
>>> +       struct drm_mode_config *config = &crtc->dev->mode_config;
>>> +       struct drm_crtc *ocrtc; /* other connector */
>>> +
>>> +       list_for_each_entry(ocrtc, &config->crtc_list, head) {
>>> +               struct drm_crtc_state *ostate; /* other state */
>>> +               unsigned i;
>>> +
>>> +               if (ocrtc == crtc)
>>> +                       continue;
>>> +
>>> +               ostate = drm_atomic_get_crtc_state(crtc, state);
>>
>> Hi Rob,
>> This will populate state's placeholder for ocrtc, which will have the
>> unintended consequence of committing ocrtc's state and thus
>> unreferencing ocrtc's current fb in
>> drm_atomic_helper_commit_crtc_state.
>>
>> Maybe a new transient state bit in drm_crtc_state which avoids the
>> commit_crtc_state call is in order?
>
> probably not a bad idea to avoid unnecessary commit,  but need to
> check if that is just masking a refcnt'ing problem.  Ie. userspace
> *should* be allowed to set properties on the crtc (for example, set
> color correction properties, etc) without causing an unintended
> finalizing of the fb..
>

Right, agreed.

Whenever I do a setcrtc on crtc A, I lose a reference to crtc B's
current fb (and vice versa). If I short-circuit this code, we don't
populate crtc B's cstate in state and the fb is not unreferenced. The
alternative would be to take a reference on crtc B's fb here, but that
will cause problems if crtc B is meant to be involved in the commit.

I pasted what I have locally below, everything seems well-behaved now:

diff --git a/drivers/gpu/drm/drm_atomic_helper.c
b/drivers/gpu/drm/drm_atomic_helper.c
index 2386ab1..9fe1276 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -485,6 +485,7 @@ void drm_atomic_helper_init_crtc_state(struct
drm_crtc *crtc,
        /* this should never happen.. but make sure! */
        WARN_ON(cstate->event);
        cstate->event = NULL;
+       cstate->commit_state = false;
 }
 EXPORT_SYMBOL(drm_atomic_helper_init_crtc_state);

@@ -631,6 +632,9 @@ drm_atomic_helper_commit_crtc_state(struct drm_crtc *crtc,
        struct drm_atomic_helper_state *a = cstate->state;
        int ret = -EINVAL;

+       if (!cstate->commit_state)
+               return 0;
+
        if (cstate->set_config)
                return set_config(crtc, cstate);

diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 4834dd7..49eda31 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -959,6 +959,7 @@ int drm_crtc_set_property(struct drm_crtc *crtc,
        struct drm_device *dev = crtc->dev;
        struct drm_mode_config *config = &dev->mode_config;

+       state->commit_state = true;
        drm_object_property_set_value(&crtc->base,
                        &state->propvals, property, value, blob_data);

diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index b81a64c..043ca1e 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -482,6 +482,7 @@ struct drm_crtc_state {
        bool set_config        : 1;
        bool new_fb            : 1;
        bool connectors_change : 1;
+       bool commit_state      : 1;

        uint8_t num_connector_ids;
        uint32_t *connector_ids;



> <snip>
>
>>> @@ -3946,92 +4163,41 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev,
>>>         if (!crtc)
>>>                 return -ENOENT;
>>>
>>> -       drm_modeset_lock(&crtc->mutex, NULL);
>>> -       if (crtc->fb == NULL) {
>>> -               /* The framebuffer is currently unbound, presumably
>>> -                * due to a hotplug event, that userspace has not
>>> -                * yet discovered.
>>> -                */
>>> -               ret = -EBUSY;
>>> -               goto out;
>>> -       }
>>> -
>>> -       if (crtc->funcs->page_flip == NULL)
>>> -               goto out;
>>> -
>>> -       fb = drm_framebuffer_lookup(dev, page_flip->fb_id);
>>> -       if (!fb) {
>>> -               ret = -ENOENT;
>>> -               goto out;
>>> -       }
>>> -
>>> -       ret = drm_crtc_check_viewport(crtc, crtc->x, crtc->y, &crtc->mode, fb);
>>> -       if (ret)
>>> -               goto out;
>>> -
>>> -       if (crtc->fb->pixel_format != fb->pixel_format) {
>>> -               DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n");
>>> -               ret = -EINVAL;
>>> -               goto out;
>>> -       }
>>> +retry:
>>> +       state = dev->driver->atomic_begin(dev,
>>> +                       page_flip->flags | DRM_MODE_ATOMIC_NONBLOCK);
>>> +       if (IS_ERR(state))
>>> +               return PTR_ERR(state);
>>>
>>>         if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
>>> -               ret = -ENOMEM;
>>> -               spin_lock_irqsave(&dev->event_lock, flags);
>>> -               if (file_priv->event_space < sizeof e->event) {
>>> -                       spin_unlock_irqrestore(&dev->event_lock, flags);
>>> +               e = create_vblank_event(dev, file_priv, page_flip->user_data);
>>> +               if (!e) {
>>> +                       ret = -ENOMEM;
>>>                         goto out;
>>>                 }
>>> -               file_priv->event_space -= sizeof e->event;
>>> -               spin_unlock_irqrestore(&dev->event_lock, flags);
>>> -
>>> -               e = kzalloc(sizeof *e, GFP_KERNEL);
>>> -               if (e == NULL) {
>>> -                       spin_lock_irqsave(&dev->event_lock, flags);
>>> -                       file_priv->event_space += sizeof e->event;
>>> -                       spin_unlock_irqrestore(&dev->event_lock, flags);
>>> +               ret = dev->driver->atomic_set_event(dev, state, &crtc->base, e);
>>> +               if (ret) {
>>>                         goto out;
>>>                 }
>>> -
>>> -               e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
>>> -               e->event.base.length = sizeof e->event;
>>> -               e->event.user_data = page_flip->user_data;
>>> -               e->base.event = &e->event.base;
>>> -               e->base.file_priv = file_priv;
>>> -               e->base.destroy =
>>> -                       (void (*) (struct drm_pending_event *)) kfree;
>>>         }
>>>
>>> -       old_fb = crtc->fb;
>>> -       ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags);
>>> -       if (ret) {
>>> -               if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) {
>>> -                       spin_lock_irqsave(&dev->event_lock, flags);
>>> -                       file_priv->event_space += sizeof e->event;
>>> -                       spin_unlock_irqrestore(&dev->event_lock, flags);
>>> -                       kfree(e);
>>> -               }
>>> -               /* Keep the old fb, don't unref it. */
>>> -               old_fb = NULL;
>>> -       } else {
>>> -               /*
>>> -                * Warn if the driver hasn't properly updated the crtc->fb
>>> -                * field to reflect that the new framebuffer is now used.
>>> -                * Failing to do so will screw with the reference counting
>>> -                * on framebuffers.
>>> -                */
>>> -               WARN_ON(crtc->fb != fb);
>>> -               /* Unref only the old framebuffer. */
>>> -               fb = NULL;
>>> -       }
>>> +       ret = drm_mode_crtc_set_obj_prop(crtc, state,
>>> +                       config->prop_fb_id, page_flip->fb_id, NULL);
>>> +       if (ret)
>>> +               goto out;
>>>
>>> -out:
>>> -       if (fb)
>>> -               drm_framebuffer_unreference(fb);
>>> -       if (old_fb)
>>> -               drm_framebuffer_unreference(old_fb);
>>> -       drm_modeset_unlock(&crtc->mutex);
>>> +       ret = dev->driver->atomic_check(dev, state);
>>> +       if (ret)
>>> +               goto out;
>>
>> If atomic_check fails, I think we need to unreference page_flip->fb_id
>
> I don't have the branch in front of me (although I'm back in kernel
> land now, so once I finish up a few other little things, I should be
> rebasing in next few days).. so my memory could be a bit rusty, but:
>
> I had added cstate->new_fb (set to true whenever we take a ref to the
> fb), which should probably be using that to decide when to unref when
> cleaning up applied or unapplied state.  But I don't see any other
> references to new_fb in this patch, so I guess I either lost or forgot
> something ;-)
>
> BR,
> -R
>
>>> +
>>> +       ret = dev->driver->atomic_commit(dev, state);
>>>
>>> +out:
>>> +       if (ret && e)
>>> +               destroy_vblank_event(dev, file_priv, e);
>>> +       dev->driver->atomic_end(dev, state);
>>> +       if (ret == -EDEADLK)
>>> +               goto retry;
>>>         return ret;
>>>  }
>>>
>>
>> <snip>


More information about the dri-devel mailing list