/* * Copyright (c) 2016 Intel Corporation * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #include #include #include "drm_crtc_internal.h" /** * DOC: overview * * A plane represents an image source that can be blended with or overlayed on * top of a CRTC during the scanout process. Planes take their input data from a * &drm_framebuffer object. The plane itself specifies the cropping and scaling * of that image, and where it is placed on the visible are of a display * pipeline, represented by &drm_crtc. A plane can also have additional * properties that specify how the pixels are positioned and blended, like * rotation or Z-position. All these properties are stored in &drm_plane_state. * * To create a plane, a KMS drivers allocates and zeroes an instances of * struct &drm_plane (possibly as part of a larger structure) and registers it * with a call to drm_universal_plane_init(). * * Cursor and overlay planes are optional. All drivers should provide one * primary plane per CRTC to avoid surprising userspace too much. See enum * &drm_plane_type for a more in-depth discussion of these special uapi-relevant * plane types. Special planes are associated with their CRTC by calling * drm_crtc_init_with_planes(). * * The type of a plane is exposed in the immutable "type" enumeration property, * which has one of the following values: "Overlay", "Primary", "Cursor". */ static unsigned int drm_num_planes(struct drm_device *dev) { unsigned int num = 0; struct drm_plane *tmp; drm_for_each_plane(tmp, dev) { num++; } return num; } /** * drm_universal_plane_init - Initialize a new universal plane object * @dev: DRM device * @plane: plane object to init * @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) * @name: printf style format string for the plane name, or NULL for default name * * Initializes a plane object of type @type. * * Returns: * Zero on success, error code on failure. */ int drm_universal_plane_init(struct drm_device *dev, struct drm_plane *plane, unsigned long possible_crtcs, const struct drm_plane_funcs *funcs, const uint32_t *formats, unsigned int format_count, enum drm_plane_type type, const char *name, ...) { struct drm_mode_config *config = &dev->mode_config; int ret; ret = drm_mode_object_get(dev, &plane->base, DRM_MODE_OBJECT_PLANE); if (ret) return ret; drm_modeset_lock_init(&plane->mutex); plane->base.properties = &plane->properties; plane->dev = dev; plane->funcs = funcs; plane->format_types = kmalloc_array(format_count, sizeof(uint32_t), GFP_KERNEL); if (!plane->format_types) { DRM_DEBUG_KMS("out of memory when allocating plane\n"); drm_mode_object_unregister(dev, &plane->base); return -ENOMEM; } if (name) { va_list ap; va_start(ap, name); plane->name = kvasprintf(GFP_KERNEL, name, ap); va_end(ap); } else { plane->name = kasprintf(GFP_KERNEL, "plane-%d", drm_num_planes(dev)); } if (!plane->name) { kfree(plane->format_types); drm_mode_object_unregister(dev, &plane->base); return -ENOMEM; } memcpy(plane->format_types, formats, format_count * sizeof(uint32_t)); plane->format_count = format_count; plane->possible_crtcs = possible_crtcs; plane->type = type; list_add_tail(&plane->head, &config->plane_list); plane->index = config->num_total_plane++; if (plane->type == DRM_PLANE_TYPE_OVERLAY) config->num_overlay_plane++; drm_object_attach_property(&plane->base, config->plane_type_property, plane->type); if (drm_core_check_feature(dev, DRIVER_ATOMIC)) { drm_object_attach_property(&plane->base, config->prop_fb_id, 0); drm_object_attach_property(&plane->base, config->prop_crtc_id, 0); drm_object_attach_property(&plane->base, config->prop_crtc_x, 0); drm_object_attach_property(&plane->base, config->prop_crtc_y, 0); drm_object_attach_property(&plane->base, config->prop_crtc_w, 0); drm_object_attach_property(&plane->base, config->prop_crtc_h, 0); drm_object_attach_property(&plane->base, config->prop_src_x, 0); drm_object_attach_property(&plane->base, config->prop_src_y, 0); drm_object_attach_property(&plane->base, config->prop_src_w, 0); drm_object_attach_property(&plane->base, config->prop_src_h, 0); } return 0; } EXPORT_SYMBOL(drm_universal_plane_init); int drm_plane_register_all(struct drm_device *dev) { struct drm_plane *plane; int ret = 0; drm_for_each_plane(plane, dev) { if (plane->funcs->late_register) ret = plane->funcs->late_register(plane); if (ret) return ret; } return 0; } void drm_plane_unregister_all(struct drm_device *dev) { struct drm_plane *plane; drm_for_each_plane(plane, dev) { if (plane->funcs->early_unregister) plane->funcs->early_unregister(plane); } } /** * drm_plane_init - Initialize a legacy plane * @dev: DRM device * @plane: plane object to init * @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 * @is_primary: plane type (primary vs overlay) * * Legacy API to initialize a DRM plane. * * New drivers should call drm_universal_plane_init() instead. * * Returns: * Zero on success, error code on failure. */ int drm_plane_init(struct drm_device *dev, struct drm_plane *plane, unsigned long possible_crtcs, const struct drm_plane_funcs *funcs, const uint32_t *formats, unsigned int format_count, bool is_primary) { enum drm_plane_type type; type = is_primary ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY; return drm_universal_plane_init(dev, plane, possible_crtcs, funcs, formats, format_count, type, NULL); } EXPORT_SYMBOL(drm_plane_init); /** * drm_plane_cleanup - Clean up the core plane usage * @plane: plane to cleanup * * This function cleans up @plane and removes it from the DRM mode setting * core. Note that the function does *not* free the plane structure itself, * this is the responsibility of the caller. */ void drm_plane_cleanup(struct drm_plane *plane) { struct drm_device *dev = plane->dev; drm_modeset_lock_all(dev); kfree(plane->format_types); drm_mode_object_unregister(dev, &plane->base); BUG_ON(list_empty(&plane->head)); /* Note that the plane_list is considered to be static; should we * remove the drm_plane at runtime we would have to decrement all * the indices on the drm_plane after us in the plane_list. */ list_del(&plane->head); dev->mode_config.num_total_plane--; if (plane->type == DRM_PLANE_TYPE_OVERLAY) dev->mode_config.num_overlay_plane--; drm_modeset_unlock_all(dev); WARN_ON(plane->state && !plane->funcs->atomic_destroy_state); if (plane->state && plane->funcs->atomic_destroy_state) plane->funcs->atomic_destroy_state(plane, plane->state); kfree(plane->name); memset(plane, 0, sizeof(*plane)); } EXPORT_SYMBOL(drm_plane_cleanup); /** * drm_plane_from_index - find the registered plane at an index * @dev: DRM device * @idx: index of registered plane to find for * * Given a plane index, return the registered plane from DRM device's * list of planes with matching index. */ struct drm_plane * drm_plane_from_index(struct drm_device *dev, int idx) { struct drm_plane *plane; drm_for_each_plane(plane, dev) if (idx == plane->index) return plane; return NULL; } EXPORT_SYMBOL(drm_plane_from_index); /** * drm_plane_force_disable - Forcibly disable a plane * @plane: plane to disable * * Forces the plane to be disabled. * * Used when the plane's current framebuffer is destroyed, * and when restoring fbdev mode. */ void drm_plane_force_disable(struct drm_plane *plane) { int ret; if (!plane->fb) return; plane->old_fb = plane->fb; ret = plane->funcs->disable_plane(plane); if (ret) { DRM_ERROR("failed to disable plane with busy fb\n"); plane->old_fb = NULL; return; } /* disconnect the plane from the fb and crtc: */ drm_framebuffer_unreference(plane->old_fb); plane->old_fb = NULL; plane->fb = NULL; plane->crtc = NULL; } EXPORT_SYMBOL(drm_plane_force_disable); /** * drm_mode_plane_set_obj_prop - set the value of a property * @plane: drm plane object to set property value for * @property: property to set * @value: value the property should be set to * * This functions sets a given property on a given plane object. This function * calls the driver's ->set_property callback and changes the software state of * the property if the callback succeeds. * * Returns: * Zero on success, error code on failure. */ int drm_mode_plane_set_obj_prop(struct drm_plane *plane, struct drm_property *property, uint64_t value) { int ret = -EINVAL; struct drm_mode_object *obj = &plane->base; if (plane->funcs->set_property) ret = plane->funcs->set_property(plane, property, value); if (!ret) drm_object_property_set_value(obj, property, value); return ret; } EXPORT_SYMBOL(drm_mode_plane_set_obj_prop); int drm_mode_getplane_res(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_get_plane_res *plane_resp = data; struct drm_mode_config *config; struct drm_plane *plane; uint32_t __user *plane_ptr; int copied = 0; unsigned num_planes; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; config = &dev->mode_config; if (file_priv->universal_planes) num_planes = config->num_total_plane; else num_planes = config->num_overlay_plane; /* * This ioctl is called twice, once to determine how much space is * needed, and the 2nd time to fill it. */ if (num_planes && (plane_resp->count_planes >= num_planes)) { plane_ptr = (uint32_t __user *)(unsigned long)plane_resp->plane_id_ptr; /* Plane lists are invariant, no locking needed. */ drm_for_each_plane(plane, dev) { /* * Unless userspace set the 'universal planes' * capability bit, only advertise overlays. */ if (plane->type != DRM_PLANE_TYPE_OVERLAY && !file_priv->universal_planes) continue; if (put_user(plane->base.id, plane_ptr + copied)) return -EFAULT; copied++; } } plane_resp->count_planes = num_planes; return 0; } int drm_mode_getplane(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_get_plane *plane_resp = data; struct drm_plane *plane; uint32_t __user *format_ptr; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; plane = drm_plane_find(dev, plane_resp->plane_id); if (!plane) return -ENOENT; drm_modeset_lock(&plane->mutex, NULL); if (plane->crtc) plane_resp->crtc_id = plane->crtc->base.id; else plane_resp->crtc_id = 0; if (plane->fb) plane_resp->fb_id = plane->fb->base.id; else plane_resp->fb_id = 0; drm_modeset_unlock(&plane->mutex); plane_resp->plane_id = plane->base.id; plane_resp->possible_crtcs = plane->possible_crtcs; plane_resp->gamma_size = 0; /* * This ioctl is called twice, once to determine how much space is * needed, and the 2nd time to fill it. */ if (plane->format_count && (plane_resp->count_format_types >= plane->format_count)) { format_ptr = (uint32_t __user *)(unsigned long)plane_resp->format_type_ptr; if (copy_to_user(format_ptr, plane->format_types, sizeof(uint32_t) * plane->format_count)) { return -EFAULT; } } plane_resp->count_format_types = plane->format_count; return 0; } int drm_plane_check_pixel_format(const struct drm_plane *plane, u32 format) { unsigned int i; for (i = 0; i < plane->format_count; i++) { if (format == plane->format_types[i]) return 0; } return -EINVAL; } /* * setplane_internal - setplane handler for internal callers * * Note that we assume an extra reference has already been taken on fb. If the * update fails, this reference will be dropped before return; if it succeeds, * the previous framebuffer (if any) will be unreferenced instead. * * src_{x,y,w,h} are provided in 16.16 fixed point format */ static int __setplane_internal(struct drm_plane *plane, struct drm_crtc *crtc, struct drm_framebuffer *fb, int32_t crtc_x, int32_t crtc_y, uint32_t crtc_w, uint32_t crtc_h, /* src_{x,y,w,h} values are 16.16 fixed point */ uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h) { int ret = 0; /* No fb means shut it down */ if (!fb) { plane->old_fb = plane->fb; ret = plane->funcs->disable_plane(plane); if (!ret) { plane->crtc = NULL; plane->fb = NULL; } else { plane->old_fb = NULL; } goto out; } /* Check whether this plane is usable on this CRTC */ if (!(plane->possible_crtcs & drm_crtc_mask(crtc))) { DRM_DEBUG_KMS("Invalid crtc for plane\n"); ret = -EINVAL; goto out; } /* Check whether this plane supports the fb pixel format. */ ret = drm_plane_check_pixel_format(plane, fb->pixel_format); if (ret) { char *format_name = drm_get_format_name(fb->pixel_format); DRM_DEBUG_KMS("Invalid pixel format %s\n", format_name); kfree(format_name); goto out; } /* Give drivers some help against integer overflows */ if (crtc_w > INT_MAX || crtc_x > INT_MAX - (int32_t) crtc_w || crtc_h > INT_MAX || crtc_y > INT_MAX - (int32_t) crtc_h) { DRM_DEBUG_KMS("Invalid CRTC coordinates %ux%u+%d+%d\n", crtc_w, crtc_h, crtc_x, crtc_y); ret = -ERANGE; goto out; } ret = drm_framebuffer_check_src_coords(src_x, src_y, src_w, src_h, fb); if (ret) goto out; plane->old_fb = plane->fb; ret = plane->funcs->update_plane(plane, crtc, fb, crtc_x, crtc_y, crtc_w, crtc_h, src_x, src_y, src_w, src_h); if (!ret) { plane->crtc = crtc; plane->fb = fb; fb = NULL; } else { plane->old_fb = NULL; } out: if (fb) drm_framebuffer_unreference(fb); if (plane->old_fb) drm_framebuffer_unreference(plane->old_fb); plane->old_fb = NULL; return ret; } static int setplane_internal(struct drm_plane *plane, struct drm_crtc *crtc, struct drm_framebuffer *fb, int32_t crtc_x, int32_t crtc_y, uint32_t crtc_w, uint32_t crtc_h, /* src_{x,y,w,h} values are 16.16 fixed point */ uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h) { int ret; drm_modeset_lock_all(plane->dev); ret = __setplane_internal(plane, crtc, fb, crtc_x, crtc_y, crtc_w, crtc_h, src_x, src_y, src_w, src_h); drm_modeset_unlock_all(plane->dev); return ret; } int drm_mode_setplane(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_set_plane *plane_req = data; struct drm_plane *plane; struct drm_crtc *crtc = NULL; struct drm_framebuffer *fb = NULL; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; /* * First, find the plane, crtc, and fb objects. If not available, * we don't bother to call the driver. */ plane = drm_plane_find(dev, plane_req->plane_id); if (!plane) { DRM_DEBUG_KMS("Unknown plane ID %d\n", plane_req->plane_id); return -ENOENT; } if (plane_req->fb_id) { fb = drm_framebuffer_lookup(dev, plane_req->fb_id); if (!fb) { DRM_DEBUG_KMS("Unknown framebuffer ID %d\n", plane_req->fb_id); return -ENOENT; } crtc = drm_crtc_find(dev, plane_req->crtc_id); if (!crtc) { DRM_DEBUG_KMS("Unknown crtc ID %d\n", plane_req->crtc_id); return -ENOENT; } } /* * setplane_internal will take care of deref'ing either the old or new * framebuffer depending on success. */ return setplane_internal(plane, crtc, fb, plane_req->crtc_x, plane_req->crtc_y, plane_req->crtc_w, plane_req->crtc_h, plane_req->src_x, plane_req->src_y, plane_req->src_w, plane_req->src_h); } static int drm_mode_cursor_universal(struct drm_crtc *crtc, struct drm_mode_cursor2 *req, struct drm_file *file_priv) { struct drm_device *dev = crtc->dev; struct drm_framebuffer *fb = NULL; struct drm_mode_fb_cmd2 fbreq = { .width = req->width, .height = req->height, .pixel_format = DRM_FORMAT_ARGB8888, .pitches = { req->width * 4 }, .handles = { req->handle }, }; int32_t crtc_x, crtc_y; uint32_t crtc_w = 0, crtc_h = 0; uint32_t src_w = 0, src_h = 0; int ret = 0; BUG_ON(!crtc->cursor); WARN_ON(crtc->cursor->crtc != crtc && crtc->cursor->crtc != NULL); /* * Obtain fb we'll be using (either new or existing) and take an extra * reference to it if fb != null. setplane will take care of dropping * the reference if the plane update fails. */ if (req->flags & DRM_MODE_CURSOR_BO) { if (req->handle) { fb = drm_internal_framebuffer_create(dev, &fbreq, file_priv); if (IS_ERR(fb)) { DRM_DEBUG_KMS("failed to wrap cursor buffer in drm framebuffer\n"); return PTR_ERR(fb); } fb->hot_x = req->hot_x; fb->hot_y = req->hot_y; } else { fb = NULL; } } else { fb = crtc->cursor->fb; if (fb) drm_framebuffer_reference(fb); } if (req->flags & DRM_MODE_CURSOR_MOVE) { crtc_x = req->x; crtc_y = req->y; } else { crtc_x = crtc->cursor_x; crtc_y = crtc->cursor_y; } if (fb) { crtc_w = fb->width; crtc_h = fb->height; src_w = fb->width << 16; src_h = fb->height << 16; } /* * setplane_internal will take care of deref'ing either the old or new * framebuffer depending on success. */ ret = __setplane_internal(crtc->cursor, crtc, fb, crtc_x, crtc_y, crtc_w, crtc_h, 0, 0, src_w, src_h); /* Update successful; save new cursor position, if necessary */ if (ret == 0 && req->flags & DRM_MODE_CURSOR_MOVE) { crtc->cursor_x = req->x; crtc->cursor_y = req->y; } return ret; } static int drm_mode_cursor_common(struct drm_device *dev, struct drm_mode_cursor2 *req, struct drm_file *file_priv) { struct drm_crtc *crtc; int ret = 0; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; if (!req->flags || (~DRM_MODE_CURSOR_FLAGS & req->flags)) return -EINVAL; crtc = drm_crtc_find(dev, req->crtc_id); if (!crtc) { DRM_DEBUG_KMS("Unknown CRTC ID %d\n", req->crtc_id); return -ENOENT; } /* * If this crtc has a universal cursor plane, call that plane's update * handler rather than using legacy cursor handlers. */ drm_modeset_lock_crtc(crtc, crtc->cursor); if (crtc->cursor) { ret = drm_mode_cursor_universal(crtc, req, file_priv); goto out; } if (req->flags & DRM_MODE_CURSOR_BO) { if (!crtc->funcs->cursor_set && !crtc->funcs->cursor_set2) { ret = -ENXIO; goto out; } /* Turns off the cursor if handle is 0 */ if (crtc->funcs->cursor_set2) ret = crtc->funcs->cursor_set2(crtc, file_priv, req->handle, req->width, req->height, req->hot_x, req->hot_y); else ret = crtc->funcs->cursor_set(crtc, file_priv, req->handle, req->width, req->height); } if (req->flags & DRM_MODE_CURSOR_MOVE) { if (crtc->funcs->cursor_move) { ret = crtc->funcs->cursor_move(crtc, req->x, req->y); } else { ret = -EFAULT; goto out; } } out: drm_modeset_unlock_crtc(crtc); return ret; } int drm_mode_cursor_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_cursor *req = data; struct drm_mode_cursor2 new_req; memcpy(&new_req, req, sizeof(struct drm_mode_cursor)); new_req.hot_x = new_req.hot_y = 0; return drm_mode_cursor_common(dev, &new_req, file_priv); } /* * Set the cursor configuration based on user request. This implements the 2nd * version of the cursor ioctl, which allows userspace to additionally specify * the hotspot of the pointer. */ int drm_mode_cursor2_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_cursor2 *req = data; return drm_mode_cursor_common(dev, req, file_priv); } int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_mode_crtc_page_flip_target *page_flip = data; struct drm_crtc *crtc; struct drm_framebuffer *fb = NULL; struct drm_pending_vblank_event *e = NULL; u32 target_vblank = page_flip->sequence; int ret = -EINVAL; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS) return -EINVAL; if (page_flip->sequence != 0 && !(page_flip->flags & DRM_MODE_PAGE_FLIP_TARGET)) return -EINVAL; /* Only one of the DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE/RELATIVE flags * can be specified */ if ((page_flip->flags & DRM_MODE_PAGE_FLIP_TARGET) == DRM_MODE_PAGE_FLIP_TARGET) return -EINVAL; if ((page_flip->flags & DRM_MODE_PAGE_FLIP_ASYNC) && !dev->mode_config.async_page_flip) return -EINVAL; crtc = drm_crtc_find(dev, page_flip->crtc_id); if (!crtc) return -ENOENT; drm_modeset_lock_crtc(crtc, crtc->primary); if (crtc->primary->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; } if (crtc->state) { const struct drm_plane_state *state = crtc->primary->state; ret = drm_framebuffer_check_src_coords(state->src_x, state->src_y, state->src_w, state->src_h, fb); } else { ret = drm_crtc_check_viewport(crtc, crtc->x, crtc->y, &crtc->mode, fb); } if (ret) goto out; if (crtc->primary->fb->pixel_format != fb->pixel_format) { DRM_DEBUG_KMS("Page flip is not allowed to change frame buffer format.\n"); ret = -EINVAL; goto out; } if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) { e = kzalloc(sizeof *e, GFP_KERNEL); if (!e) { ret = -ENOMEM; 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; ret = drm_event_reserve_init(dev, file_priv, &e->base, &e->event.base); if (ret) { kfree(e); goto out; } } crtc->primary->old_fb = crtc->primary->fb; if (crtc->funcs->page_flip_target) { u32 current_vblank; int r; r = drm_crtc_vblank_get(crtc); if (r) return r; current_vblank = drm_crtc_vblank_count(crtc); switch (page_flip->flags & DRM_MODE_PAGE_FLIP_TARGET) { case DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE: if ((int)(target_vblank - current_vblank) > 1) { DRM_DEBUG("Invalid absolute flip target %u, " "must be <= %u\n", target_vblank, current_vblank + 1); drm_crtc_vblank_put(crtc); return -EINVAL; } break; case DRM_MODE_PAGE_FLIP_TARGET_RELATIVE: if (target_vblank != 0 && target_vblank != 1) { DRM_DEBUG("Invalid relative flip target %u, " "must be 0 or 1\n", target_vblank); drm_crtc_vblank_put(crtc); return -EINVAL; } target_vblank += current_vblank; break; default: target_vblank = current_vblank + !(page_flip->flags & DRM_MODE_PAGE_FLIP_ASYNC); break; } } else if (crtc->funcs->page_flip == NULL || (page_flip->flags & DRM_MODE_PAGE_FLIP_TARGET)) { return -EINVAL; } if (crtc->funcs->page_flip_target) ret = crtc->funcs->page_flip_target(crtc, fb, e, page_flip->flags, target_vblank); else ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags); if (ret) { if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) drm_event_cancel_free(dev, &e->base); /* Keep the old fb, don't unref it. */ crtc->primary->old_fb = NULL; } else { crtc->primary->fb = fb; /* Unref only the old framebuffer. */ fb = NULL; } out: if (ret && crtc->funcs->page_flip_target) drm_crtc_vblank_put(crtc); if (fb) drm_framebuffer_unreference(fb); if (crtc->primary->old_fb) drm_framebuffer_unreference(crtc->primary->old_fb); crtc->primary->old_fb = NULL; drm_modeset_unlock_crtc(crtc); return ret; }