blob: 801e7615c5e2e906ea0024d51e0665516d48a94f [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/dma-fence.h>
#include <linux/uaccess.h>
#include <linux/sync_file.h>
#include <linux/file.h>
#include <linux/types.h>
#include "meson_async_atomic.h"
#include "meson_plane.h"
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_mode.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_property.h>
#include "meson_drv.h"
#include "meson_async_atomic.h"
#include "meson_plane.h"
struct drm_property *meson_mode_obj_find_prop_id(struct drm_mode_object *obj,
uint32_t prop_id)
{
int i;
for (i = 0; i < obj->properties->count; i++)
if (obj->properties->properties[i]->base.id == prop_id)
return obj->properties->properties[i];
return NULL;
}
static int
meson_atomic_replace_property_blob_from_id(struct drm_device *dev,
struct drm_property_blob **blob,
u64 blob_id,
ssize_t expected_size,
ssize_t expected_elem_size,
bool *replaced)
{
struct drm_property_blob *new_blob = NULL;
if (blob_id != 0) {
new_blob = drm_property_lookup_blob(dev, blob_id);
if (!new_blob)
return -EINVAL;
if (expected_size > 0 &&
new_blob->length != expected_size) {
drm_property_blob_put(new_blob);
return -EINVAL;
}
if (expected_elem_size > 0 &&
new_blob->length % expected_elem_size != 0) {
drm_property_blob_put(new_blob);
return -EINVAL;
}
}
*replaced |= drm_property_replace_blob(blob, new_blob);
drm_property_blob_put(new_blob);
return 0;
}
static int meson_atomic_plane_set_property(struct drm_plane *plane,
struct drm_plane_state *state, struct drm_file *file_priv,
struct drm_property *property, u64 val)
{
struct drm_device *dev = plane->dev;
struct drm_mode_config *config = &dev->mode_config;
bool replaced = false;
int ret;
if (property == config->prop_fb_id) {
struct drm_framebuffer *fb;
fb = drm_framebuffer_lookup(dev, file_priv, val);
drm_atomic_set_fb_for_plane(state, fb);
if (fb)
drm_framebuffer_put(fb);
} else if (property == config->prop_in_fence_fd) {
if (state->fence)
return -EINVAL;
if (U642I64(val) == -1)
return 0;
state->fence = sync_file_get_fence(val);
if (!state->fence)
return -EINVAL;
} else if (property == config->prop_crtc_id) {
struct drm_crtc *crtc = drm_crtc_find(dev, file_priv, val);
if (val && !crtc)
return -EACCES;
return drm_atomic_set_crtc_for_plane(state, crtc);
} else if (property == config->prop_crtc_x) {
state->crtc_x = U642I64(val);
} else if (property == config->prop_crtc_y) {
state->crtc_y = U642I64(val);
} else if (property == config->prop_crtc_w) {
state->crtc_w = val;
} else if (property == config->prop_crtc_h) {
state->crtc_h = val;
} else if (property == config->prop_src_x) {
state->src_x = val;
} else if (property == config->prop_src_y) {
state->src_y = val;
} else if (property == config->prop_src_w) {
state->src_w = val;
} else if (property == config->prop_src_h) {
state->src_h = val;
} else if (property == plane->alpha_property) {
state->alpha = val;
} else if (property == plane->blend_mode_property) {
state->pixel_blend_mode = val;
} else if (property == plane->rotation_property) {
if (!is_power_of_2(val & DRM_MODE_ROTATE_MASK)) {
DRM_DEBUG_ATOMIC("[PLANE:%d:%s] bad rotation bitmask: 0x%llx\n",
plane->base.id, plane->name, val);
return -EINVAL;
}
state->rotation = val;
} else if (property == plane->zpos_property) {
state->zpos = val;
} else if (property == plane->color_encoding_property) {
state->color_encoding = val;
} else if (property == plane->color_range_property) {
state->color_range = val;
} else if (property == config->prop_fb_damage_clips) {
ret = meson_atomic_replace_property_blob_from_id(dev,
&state->fb_damage_clips,
val,
-1,
sizeof(struct drm_rect),
&replaced);
return ret;
} else if (property == plane->scaling_filter_property) {
state->scaling_filter = val;
} else if (plane->funcs->atomic_set_property) {
return plane->funcs->atomic_set_property(plane, state,
property, val);
} else {
DRM_DEBUG_ATOMIC("[PLANE:%d:%s] unknown property [PROP:%d:%s]]\n",
plane->base.id, plane->name,
property->base.id, property->name);
return -EINVAL;
}
return 0;
}
static bool meson_property_change_valid_get(struct drm_property *property,
u64 value, struct drm_mode_object **ref)
{
int i;
if (property->flags & DRM_MODE_PROP_IMMUTABLE)
return false;
*ref = NULL;
if (drm_property_type_is(property, DRM_MODE_PROP_RANGE)) {
if (value < property->values[0] || value > property->values[1])
return false;
return true;
} else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE)) {
s64 svalue = U642I64(value);
if (svalue < U642I64(property->values[0]) ||
svalue > U642I64(property->values[1]))
return false;
return true;
} else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK)) {
u64 valid_mask = 0;
for (i = 0; i < property->num_values; i++)
valid_mask |= (1ULL << property->values[i]);
return !(value & ~valid_mask);
} else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB)) {
struct drm_property_blob *blob;
if (value == 0)
return true;
blob = drm_property_lookup_blob(property->dev, value);
if (blob) {
*ref = &blob->base;
return true;
} else {
return false;
}
} else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT)) {
/* a zero value for an object property translates to null: */
if (value == 0)
return true;
*ref = drm_mode_object_find(property->dev, NULL, value,
property->values[0]);
if (*ref)
return true;
else
return false;
}
for (i = 0; i < property->num_values; i++)
if (property->values[i] == value)
return true;
return false;
}
static void meson_property_change_valid_put(struct drm_property *property,
struct drm_mode_object *ref)
{
if (!ref)
return;
if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT))
drm_mode_object_put(ref);
else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB))
drm_property_blob_put(obj_to_blob(ref));
}
static int atomic_set_prop(struct drm_atomic_state *state,
struct drm_mode_object *obj, struct drm_file *file_priv,
struct drm_property *prop, u64 prop_value)
{
struct drm_mode_object *ref;
int ret;
if (!meson_property_change_valid_get(prop, prop_value, &ref))
return -EINVAL;
switch (obj->type) {
case DRM_MODE_OBJECT_PLANE: {
struct drm_plane *plane = obj_to_plane(obj);
struct drm_plane_state *plane_state;
plane_state = drm_atomic_get_plane_state(state, plane);
if (IS_ERR(plane_state)) {
ret = PTR_ERR(plane_state);
break;
}
ret = meson_atomic_plane_set_property(plane,
plane_state, file_priv, prop, prop_value);
break;
}
default:
ret = -EINVAL;
break;
}
meson_property_change_valid_put(prop, ref);
return ret;
}
/*modified from drm_mode_atomic_ioctl() */
int meson_async_atomic_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
struct drm_mode_atomic *arg = data;
u32 __user *objs_ptr = (u32 __user *)(unsigned long)(arg->objs_ptr);
u32 __user *count_props_ptr = (u32 __user *)(unsigned long)(arg->count_props_ptr);
u32 __user *props_ptr = (u32 __user *)(unsigned long)(arg->props_ptr);
u64 __user *prop_values_ptr = (u64 __user *)(unsigned long)(arg->prop_values_ptr);
unsigned int copied_objs, copied_props;
struct drm_atomic_state *state;
struct drm_modeset_acquire_ctx ctx;
struct drm_out_fence_state *fence_state;
int ret = 0;
unsigned int i, j, num_fences;
drm_modeset_acquire_init(&ctx, 0);
state = drm_atomic_state_alloc(dev);
if (!state)
return -ENOMEM;
state->allow_modeset = false;
state->acquire_ctx = &ctx;
retry:
copied_objs = 0;
copied_props = 0;
fence_state = NULL;
num_fences = 0;
if (arg->count_objs > 1) {
DRM_ERROR("only accept plane object update(%d).\n", arg->count_objs);
ret = -EINVAL;
goto out;
}
for (i = 0; i < arg->count_objs; i++) {
u32 obj_id, count_props;
struct drm_mode_object *obj;
if (get_user(obj_id, objs_ptr + copied_objs)) {
ret = -EFAULT;
goto out;
}
obj = drm_mode_object_find(dev, file_priv, obj_id, DRM_MODE_OBJECT_ANY);
if (!obj) {
ret = -ENOENT;
goto out;
}
if (obj->type != DRM_MODE_OBJECT_PLANE) {
DRM_ERROR("only accept plane object update, can not set (%d)\n", obj->type);
ret = -EINVAL;
goto out;
}
if (!obj->properties) {
drm_mode_object_put(obj);
ret = -ENOENT;
goto out;
}
if (get_user(count_props, count_props_ptr + copied_objs)) {
drm_mode_object_put(obj);
ret = -EFAULT;
goto out;
}
copied_objs++;
for (j = 0; j < count_props; j++) {
u32 prop_id;
u64 prop_value;
struct drm_property *prop;
if (get_user(prop_id, props_ptr + copied_props)) {
drm_mode_object_put(obj);
ret = -EFAULT;
goto out;
}
prop = meson_mode_obj_find_prop_id(obj, prop_id);
if (!prop) {
drm_mode_object_put(obj);
ret = -ENOENT;
goto out;
}
if (copy_from_user(&prop_value,
prop_values_ptr + copied_props,
sizeof(prop_value))) {
drm_mode_object_put(obj);
ret = -EFAULT;
goto out;
}
ret = atomic_set_prop(state, obj, file_priv, prop, prop_value);
if (ret) {
drm_mode_object_put(obj);
goto out;
}
copied_props++;
}
drm_mode_object_put(obj);
}
state->legacy_cursor_update = true;
ret = drm_atomic_commit(state);
if (ret)
DRM_ERROR("%s failed .\n", __func__);
out:
if (ret == -EDEADLK) {
drm_atomic_state_clear(state);
drm_modeset_backoff(&ctx);
goto retry;
}
drm_atomic_state_put(state);
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
return ret;
}