On Tue, 26 Sep 2017 18:15:37 +0100
Daniel Stone <dani...@collabora.com> wrote:

> Change the type of cursor_plane from a weston_plane (base tracking
> structure) to a drm_plane (wrapper containing additional DRM-specific
> details), and make it a dynamically-allocated pointer.
> 
> Using the standard drm_plane allows us to reuse code which already deals
> with drm_planes, e.g. a common cleanup function.
> 
> This patch introduces a 'special plane' helper, creating a drm_plane
> either from a real KMS plane when using universal planes, or a fake plane
> otherwise. Without universal planes, the cursor and primary planes are
> hidden from us; this helper allows us to pretend otherwise.
> 
> Signed-off-by: Daniel Stone <dani...@collabora.com>
> ---
>  libweston/compositor-drm.c | 423 
> ++++++++++++++++++++++++++++++++++-----------
>  1 file changed, 322 insertions(+), 101 deletions(-)

Hi Daniel,

this patch looks mostly good, but there are lifetime issues.


>  static struct weston_plane *
>  drm_output_prepare_cursor_view(struct drm_output_state *output_state,
>                              struct weston_view *ev)
>  {
>       struct drm_output *output = output_state->output;
>       struct drm_backend *b = to_drm_backend(output->base.compositor);
> +     struct drm_plane *plane = output->cursor_plane;
> +     struct drm_plane_state *plane_state;
>       struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
>       struct wl_shm_buffer *shmbuf;
> +     bool needs_update = false;
>       float x, y;
>  
> +     if (!plane)
> +             return NULL;
> +
>       if (b->cursors_are_broken)
>               return NULL;
>  
> -     if (output->cursor_view)
> +     if (!plane->state_cur->complete)
> +             return NULL;
> +
> +     if (plane->state_cur->output && plane->state_cur->output != output)
>               return NULL;

Just a thought: would it make sense to wrap the two above to something
like

bool
drm_plane_is_available(struct drm_plane *plane, struct drm_output *output)
{
        if (!plane->state_cur->complete)
                return false;

        if (plane->state_cur->output && plane->state_cur->output != output)
                return false;

        return drm_plane_crtc_supported(output, plane);
}

That would make a nice place to explain what the conditions mean:

!plane->state_cur->complete
        The plane is needed for something else already programmed and
        not yet hit the screen.

state_cur->output && state_cur->output != output
        Plane is in use through a different CRTC, so cannot take it
        into use here and now, would need a disabled cycle first.
        Also prevents plane stealing that might otherwise happen
        between outputs updating at different times.

drm_plane_crtc_supported()
        The CRTC and the plane can actually work together
        hardware-wise, at all.

Is that correct?

The cursor path doesn't really need the drm_plane_crtc_supported()
check because that is implied by the drm_output::cursor_plane
assignment, but it doesn't hurt. The overlay path needs it.

>  
>       /* Don't import buffers which span multiple outputs. */
> @@ -2215,89 +2277,101 @@ drm_output_prepare_cursor_view(struct 
> drm_output_state *output_state,
>           ev->surface->height > b->cursor_height)
>               return NULL;
>  
> -     output->cursor_view = ev;
> -     weston_view_to_global_float(ev, 0, 0, &x, &y);
> -     output->cursor_plane.x = x;
> -     output->cursor_plane.y = y;
> +     plane_state =
> +             drm_output_state_get_plane(output_state, output->cursor_plane);
>  
> -     return &output->cursor_plane;
> -}
> -
> -/**
> - * Update the image for the current cursor surface
> - *
> - * @param b DRM backend structure
> - * @param bo GBM buffer object to write into
> - * @param ev View to use for cursor image
> - */
> -static void
> -cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo,
> -              struct weston_view *ev)
> -{
> -     struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
> -     uint32_t buf[b->cursor_width * b->cursor_height];
> -     int32_t stride;
> -     uint8_t *s;
> -     int i;
> -
> -     assert(buffer && buffer->shm_buffer);
> -     assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource));
> -     assert(ev->surface->width <= b->cursor_width);
> -     assert(ev->surface->height <= b->cursor_height);
> -
> -     memset(buf, 0, sizeof buf);
> -     stride = wl_shm_buffer_get_stride(buffer->shm_buffer);
> -     s = wl_shm_buffer_get_data(buffer->shm_buffer);
> +     if (plane_state && plane_state->fb)
> +             return NULL;

I think plane_state cannot be NULL, so it should be not checked here.
It's not checked in the below code either.

plane_state->fb check prevents the same cursor plane for being used
multiple times on this repaint cycle, correct?

Checks on plane->state_cur are checking what has been submitted to the
kernel which is a different thing.

>  
> -     wl_shm_buffer_begin_access(buffer->shm_buffer);
> -     for (i = 0; i < ev->surface->height; i++)
> -             memcpy(buf + i * b->cursor_width,
> -                    s + i * stride,
> -                    ev->surface->width * 4);
> -     wl_shm_buffer_end_access(buffer->shm_buffer);
> +     /* Since we're setting plane state up front, we need to work out
> +      * whether or not we need to upload a new cursor. We can't use the
> +      * plane damage, since the planes haven't actually been calculated
> +      * yet: instead try to figure it out directly. KMS cursor planes are
> +      * pretty unique here, in that they lie partway between a Weston plane
> +      * (direct scanout) and a renderer. */
> +     if (ev != output->cursor_view ||
> +         pixman_region32_not_empty(&ev->surface->damage)) {
> +             output->current_cursor++;
> +             output->current_cursor =
> +                     output->current_cursor %
> +                             ARRAY_LENGTH(output->gbm_cursor_fb);
> +             needs_update = true;
> +     }
>  
> -     if (gbm_bo_write(bo, buf, sizeof buf) < 0)
> -             weston_log("failed update cursor: %m\n");
> +     output->cursor_view = ev;
> +     weston_view_to_global_float(ev, 0, 0, &x, &y);
> +     plane->base.x = x;
> +     plane->base.y = y;
> +
> +     plane_state->fb =
> +             drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]);
> +     plane_state->output = output;
> +     plane_state->src_x = 0;
> +     plane_state->src_y = 0;
> +     plane_state->src_w = b->cursor_width << 16;
> +     plane_state->src_h = b->cursor_height << 16;
> +     plane_state->dest_x = (x - output->base.x) * output->base.current_scale;
> +     plane_state->dest_y = (y - output->base.y) * output->base.current_scale;
> +     plane_state->dest_w = b->cursor_width;
> +     plane_state->dest_h = b->cursor_height;
> +
> +     if (needs_update)
> +             cursor_bo_update(b, plane_state->fb->bo, ev);
> +
> +     return &plane->base;
>  }


> @@ -2659,19 +2744,30 @@ init_pixman(struct drm_backend *b)
>   * Creates one drm_plane structure for a hardware plane, and initialises its
>   * properties and formats.
>   *
> + * In the absence of universal plane support, where KMS does not explicitly
> + * expose the primary and cursor planes to userspace, this may also create
> + * an 'internal' plane for internal management.

When doing this, should you not also modify drm_plane_destroy() to do
the right thing for the 'internal' planes?

No need to drm_property_info_free() and should not drmModeSetPlane().

> + *
>   * This function does not add the plane to the list of usable planes in 
> Weston
>   * itself; the caller is responsible for this.
>   *
>   * Call drm_plane_destroy to clean up the plane.
>   *
> + * @sa drm_output_find_special_plane
>   * @param b DRM compositor backend
> - * @param kplane DRM plane to create
> + * @param kplane DRM plane to create, or NULL if creating internal plane
> + * @param output Output to create internal plane for, or NULL
> + * @param type Type to use when creating internal plane, or invalid
> + * @param format Format to use for internal planes, or 0
>   */
>  static struct drm_plane *
> -drm_plane_create(struct drm_backend *b, const drmModePlane *kplane)
> +drm_plane_create(struct drm_backend *b, const drmModePlane *kplane,
> +              struct drm_output *output, enum wdrm_plane_type type,
> +              uint32_t format)
>  {
>       struct drm_plane *plane;
>       drmModeObjectProperties *props;
> +     int num_formats = (kplane) ? kplane->count_formats : 1;
>  
>       static struct drm_property_enum_info plane_type_enums[] = {
>               [WDRM_PLANE_TYPE_PRIMARY] = {
> @@ -2692,36 +2788,61 @@ drm_plane_create(struct drm_backend *b, const 
> drmModePlane *kplane)
>               },
>       };
>  
> -     plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) *
> -                                       kplane->count_formats));
> +     plane = zalloc(sizeof(*plane) +
> +                    (sizeof(uint32_t) * num_formats));
>       if (!plane) {
>               weston_log("%s: out of memory\n", __func__);
>               return NULL;
>       }
>  
>       plane->backend = b;
> -     plane->possible_crtcs = kplane->possible_crtcs;
> -     plane->plane_id = kplane->plane_id;
> -     plane->count_formats = kplane->count_formats;
>       plane->state_cur = drm_plane_state_alloc(NULL, plane);
>       plane->state_cur->complete = true;
> -     memcpy(plane->formats, kplane->formats,
> -            kplane->count_formats * sizeof(kplane->formats[0]));
>  
> -     props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id,
> -                                        DRM_MODE_OBJECT_PLANE);
> -     if (!props) {
> -             weston_log("couldn't get plane properties\n");
> +     if (kplane) {
> +             plane->possible_crtcs = kplane->possible_crtcs;
> +             plane->plane_id = kplane->plane_id;
> +             plane->count_formats = kplane->count_formats;
> +             memcpy(plane->formats, kplane->formats,
> +                    kplane->count_formats * sizeof(kplane->formats[0]));
> +
> +             props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id,
> +                                                DRM_MODE_OBJECT_PLANE);
> +             if (!props) {
> +                     weston_log("couldn't get plane properties\n");
> +                     free(plane);
> +                     return NULL;
> +             }
> +             drm_property_info_populate(b, plane_props, plane->props,
> +                                        WDRM_PLANE__COUNT, props);
> +             plane->type =
> +                     drm_property_get_value(&plane->props[WDRM_PLANE_TYPE],
> +                                            props,
> +                                            WDRM_PLANE_TYPE__COUNT);
> +             drmModeFreeObjectProperties(props);
> +     }
> +     else {
> +             plane->possible_crtcs = (1 << output->pipe);
> +             plane->plane_id = 0;
> +             plane->count_formats = 1;
> +             plane->formats[0] = format;
> +             plane->type = type;
> +     }
> +
> +     if (plane->type == WDRM_PLANE_TYPE__COUNT) {

This seems to leak plane->props and plane->state_cur.


>               free(plane);
>               return NULL;
>       }
> -     drm_property_info_populate(b, plane_props, plane->props,
> -                                WDRM_PLANE__COUNT, props);
> -     plane->type =
> -             drm_property_get_value(&plane->props[WDRM_PLANE_TYPE],
> -                                    props,
> -                                    WDRM_PLANE_TYPE_OVERLAY);
> -     drmModeFreeObjectProperties(props);
> +
> +     /* With universal planes, everything is a DRM plane; without
> +      * universal planes, the only DRM planes are overlay planes. */
> +     if (b->universal_planes) {
> +             assert(kplane);
> +     } else {
> +             assert((kplane && plane->type == WDRM_PLANE_TYPE_OVERLAY) ||
> +                    (!kplane && plane->type != WDRM_PLANE_TYPE_OVERLAY &&
> +                     output));
> +     }
>  
>       weston_plane_init(&plane->base, b->compositor, 0, 0);
>       wl_list_insert(&b->plane_list, &plane->link);
> @@ -2729,6 +2850,88 @@ drm_plane_create(struct drm_backend *b, const 
> drmModePlane *kplane)
>       return plane;
>  }
>  
> +/**
> + * Find, or create, a special-purpose plane
> + *
> + * Primary and cursor planes are a special case, in that before universal
> + * planes, they are driven by non-plane API calls. Without universal plane
> + * support, the only way to configure a primary plane is via drmModeSetCrtc,
> + * and the only way to configure a cursor plane is drmModeSetCursor2.
> + *
> + * Although they may actually be regular planes in the hardware, without
> + * universal plane support, these planes are not actually exposed to
> + * userspace in the regular plane list.
> + *
> + * However, for ease of internal tracking, we want to manage all planes
> + * through the same drm_plane structures. Therefore, when we are running
> + * without universal plane support, we create fake drm_plane structures
> + * to track these planes.
> + *
> + * @param b DRM backend
> + * @param output Output to use for plane
> + * @param type Type of plane
> + */
> +static struct drm_plane *
> +drm_output_find_special_plane(struct drm_backend *b, struct drm_output 
> *output,
> +                           enum wdrm_plane_type type)
> +{
> +     struct drm_plane *plane;
> +
> +     if (!b->universal_planes) {
> +             uint32_t format;
> +
> +             switch (type) {
> +             case WDRM_PLANE_TYPE_CURSOR:
> +                     format = GBM_FORMAT_ARGB8888;
> +                     break;
> +             case WDRM_PLANE_TYPE_PRIMARY:
> +                     format = output->gbm_format;

There were discussions in IRC that output->gbm_format is not set at
this time yet, so we can't use it here.

I would propose to just hardcode the list of formats that
parse_gbm_format() supports. I presume the hardcoding here is because
we don't get it from the kernel, so it's no better or worse than before.

> +                     break;
> +             default:
> +                     assert(!"invalid type in 
> drm_output_find_special_plane");
> +                     break;
> +             }
> +
> +             return drm_plane_create(b, NULL, output, type, format);
> +     }
> +
> +     wl_list_for_each(plane, &b->plane_list, link) {
> +             struct drm_output *tmp;
> +             bool found_elsewhere = false;
> +
> +             if (plane->type != type)
> +                     continue;
> +             if (!drm_plane_crtc_supported(output, plane))
> +                     continue;
> +
> +             /* On some platforms, primary/cursor planes can roam
> +              * between different CRTCs, so make sure we don't claim the
> +              * same plane for two outputs. */
> +             wl_list_for_each(tmp, &b->compositor->pending_output_list,
> +                              base.link) {
> +                     if (tmp->cursor_plane == plane) {
> +                             found_elsewhere = true;
> +                             break;
> +                     }
> +             }
> +             wl_list_for_each(tmp, &b->compositor->output_list,
> +                              base.link) {
> +                     if (tmp->cursor_plane == plane) {
> +                             found_elsewhere = true;
> +                             break;
> +                     }
> +             }
> +
> +             if (found_elsewhere)
> +                     continue;
> +
> +             plane->possible_crtcs = (1 << output->pipe);
> +             return plane;
> +     }
> +
> +     return NULL;
> +}
> +
>  /**
>   * Destroy one DRM plane
>   *
> @@ -2778,7 +2981,8 @@ create_sprites(struct drm_backend *b)
>               if (!kplane)
>                       continue;
>  
> -             drm_plane = drm_plane_create(b, kplane);
> +             drm_plane = drm_plane_create(b, kplane, NULL,
> +                                          WDRM_PLANE_TYPE__COUNT, 0);
>               drmModeFreePlane(kplane);
>               if (!drm_plane)
>                       continue;
> @@ -3044,6 +3248,10 @@ drm_output_init_cursor_egl(struct drm_output *output, 
> struct drm_backend *b)
>  {
>       unsigned int i;
>  
> +     /* No point creating cursors if we don't have a plane for them. */
> +     if (!output->cursor_plane)
> +             return 0;
> +
>       for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) {
>               struct gbm_bo *bo;
>  
> @@ -3631,11 +3839,15 @@ drm_output_enable(struct weston_output *base)
>           output->connector->connector_type == DRM_MODE_CONNECTOR_eDP)
>               output->base.connection_internal = true;
>  
> -     weston_plane_init(&output->cursor_plane, b->compositor,
> -                       INT32_MIN, INT32_MIN);
>       weston_plane_init(&output->scanout_plane, b->compositor, 0, 0);
>  
> -     weston_compositor_stack_plane(b->compositor, &output->cursor_plane, 
> NULL);
> +     if (output->cursor_plane)
> +             weston_compositor_stack_plane(b->compositor,
> +                                           &output->cursor_plane->base,
> +                                           NULL);
> +     else
> +             b->cursors_are_broken = 1;
> +
>       weston_compositor_stack_plane(b->compositor, &output->scanout_plane,
>                                     &b->compositor->primary_plane);
>  
> @@ -3678,10 +3890,11 @@ drm_output_deinit(struct weston_output *base)
>               drm_output_fini_egl(output);
>  
>       weston_plane_release(&output->scanout_plane);
> -     weston_plane_release(&output->cursor_plane);

This weston_plane_release() call is removed here.

To remain symmetric with drm_output_enable(), I think you should add

        wl_list_remove(&output->cursor_plane->base.link);
        wl_list_init(&output->cursor_plane->base.link);

here. It is theoretically possible to disable and then enable the same
output.

This is necessary also because the drm_plane for the cursor is not
destroyed when the respective drm_output is destroyed. The drm_plane
remains in the drm_backend::plane_list. When the same connector becomes
connected again and a new drm_output is created,
drm_output_find_special_plane() will find it again, and
drm_output_enable() will call
weston_compositor_stack_plane(cursor_plane) again, leading to list
corruption.

There is a catch. On shutdown, destroy_sprites() will destroy the
drm_planes, leaving drm_output::cursor_plane dangling. Then we destroy
the outputs, and the list removal hits a use-after-free.

Should destroy_sprites() be called after weston_compositor_shutdown()?

>  
> -     /* Turn off hardware cursor */
> -     drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
> +     if (output->cursor_plane) {
> +             /* Turn off hardware cursor */
> +             drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
> +     }
>  }
>  
>  static void
> @@ -3845,6 +4058,12 @@ create_output_for_connector(struct drm_backend *b,
>               }
>       }
>  
> +     /* Failing to find a cursor plane is not fatal, as we'll fall back
> +      * to software cursor. */
> +     output->cursor_plane =
> +             drm_output_find_special_plane(b, output,
> +                                           WDRM_PLANE_TYPE_CURSOR);
> +
>       weston_compositor_add_pending_output(&output->base, b->compositor);
>  
>       return 0;
> @@ -4083,7 +4302,9 @@ session_notify(struct wl_listener *listener, void *data)
>  
>               wl_list_for_each(output, &compositor->output_list, base.link) {
>                       output->base.repaint_needed = false;
> -                     drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
> +                     if (output->cursor_plane)
> +                             drmModeSetCursor(b->drm.fd, output->crtc_id,
> +                                              0, 0, 0);
>               }
>  
>               output = container_of(compositor->output_list.next,

Thanks,
pq

Attachment: pgpTiUYZLbWbR.pgp
Description: OpenPGP digital signature

_______________________________________________
wayland-devel mailing list
wayland-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/wayland-devel

Reply via email to