| /* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include "msm_prop.h" |
| |
| void msm_property_init(struct msm_property_info *info, |
| struct drm_mode_object *base, |
| struct drm_device *dev, |
| struct drm_property **property_array, |
| struct msm_property_data *property_data, |
| uint32_t property_count, |
| uint32_t blob_count, |
| uint32_t state_size) |
| { |
| /* prevent access if any of these are NULL */ |
| if (!base || !dev || !property_array || !property_data) { |
| property_count = 0; |
| blob_count = 0; |
| |
| DRM_ERROR("invalid arguments, forcing zero properties\n"); |
| return; |
| } |
| |
| /* can't have more blob properties than total properties */ |
| if (blob_count > property_count) { |
| blob_count = property_count; |
| |
| DBG("Capping number of blob properties to %d", blob_count); |
| } |
| |
| if (!info) { |
| DRM_ERROR("info pointer is NULL\n"); |
| } else { |
| info->base = base; |
| info->dev = dev; |
| info->property_array = property_array; |
| info->property_data = property_data; |
| info->property_count = property_count; |
| info->blob_count = blob_count; |
| info->install_request = 0; |
| info->install_count = 0; |
| info->recent_idx = 0; |
| info->is_active = false; |
| info->state_size = state_size; |
| info->state_cache_size = 0; |
| mutex_init(&info->property_lock); |
| |
| memset(property_data, |
| 0, |
| sizeof(struct msm_property_data) * |
| property_count); |
| } |
| } |
| |
| void msm_property_destroy(struct msm_property_info *info) |
| { |
| if (!info) |
| return; |
| |
| /* free state cache */ |
| while (info->state_cache_size > 0) |
| kfree(info->state_cache[--(info->state_cache_size)]); |
| |
| mutex_destroy(&info->property_lock); |
| } |
| |
| int msm_property_pop_dirty(struct msm_property_info *info, |
| struct msm_property_state *property_state) |
| { |
| struct list_head *item; |
| int rc = 0; |
| |
| if (!info || !property_state || !property_state->values) { |
| DRM_ERROR("invalid argument(s)\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&info->property_lock); |
| if (list_empty(&property_state->dirty_list)) { |
| rc = -EAGAIN; |
| } else { |
| item = property_state->dirty_list.next; |
| list_del_init(item); |
| rc = container_of(item, struct msm_property_value, dirty_node) |
| - property_state->values; |
| DRM_DEBUG_KMS("property %d dirty\n", rc); |
| } |
| mutex_unlock(&info->property_lock); |
| |
| return rc; |
| } |
| |
| /** |
| * _msm_property_set_dirty_no_lock - flag given property as being dirty |
| * This function doesn't mutex protect the |
| * dirty linked list. |
| * @info: Pointer to property info container struct |
| * @property_state: Pointer to property state container struct |
| * @property_idx: Property index |
| */ |
| static void _msm_property_set_dirty_no_lock( |
| struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| uint32_t property_idx) |
| { |
| if (!info || !property_state || !property_state->values || |
| property_idx >= info->property_count) { |
| DRM_ERROR("invalid argument(s), idx %u\n", property_idx); |
| return; |
| } |
| |
| /* avoid re-inserting if already dirty */ |
| if (!list_empty(&property_state->values[property_idx].dirty_node)) { |
| DRM_DEBUG_KMS("property %u already dirty\n", property_idx); |
| return; |
| } |
| |
| list_add_tail(&property_state->values[property_idx].dirty_node, |
| &property_state->dirty_list); |
| } |
| |
| bool msm_property_is_dirty( |
| struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| uint32_t property_idx) |
| { |
| if (!info || !property_state || !property_state->values || |
| property_idx >= info->property_count) { |
| DRM_ERROR("invalid argument(s), idx %u\n", property_idx); |
| return false; |
| } |
| |
| return !list_empty(&property_state->values[property_idx].dirty_node); |
| } |
| |
| /** |
| * _msm_property_install_integer - install standard drm range property |
| * @info: Pointer to property info container struct |
| * @name: Property name |
| * @flags: Other property type flags, e.g. DRM_MODE_PROP_IMMUTABLE |
| * @min: Min property value |
| * @max: Max property value |
| * @init: Default Property value |
| * @property_idx: Property index |
| * @force_dirty: Whether or not to filter 'dirty' status on unchanged values |
| */ |
| static void _msm_property_install_integer(struct msm_property_info *info, |
| const char *name, int flags, uint64_t min, uint64_t max, |
| uint64_t init, uint32_t property_idx, bool force_dirty) |
| { |
| struct drm_property **prop; |
| |
| if (!info) |
| return; |
| |
| ++info->install_request; |
| |
| if (!name || (property_idx >= info->property_count)) { |
| DRM_ERROR("invalid argument(s), %s\n", name ? name : "null"); |
| } else { |
| prop = &info->property_array[property_idx]; |
| /* |
| * Properties need to be attached to each drm object that |
| * uses them, but only need to be created once |
| */ |
| if (*prop == 0) { |
| *prop = drm_property_create_range(info->dev, |
| flags, name, min, max); |
| if (*prop == 0) |
| DRM_ERROR("create %s property failed\n", name); |
| } |
| |
| /* save init value for later */ |
| info->property_data[property_idx].default_value = init; |
| info->property_data[property_idx].force_dirty = force_dirty; |
| |
| /* always attach property, if created */ |
| if (*prop) { |
| drm_object_attach_property(info->base, *prop, init); |
| ++info->install_count; |
| } |
| } |
| } |
| |
| void msm_property_install_range(struct msm_property_info *info, |
| const char *name, int flags, uint64_t min, uint64_t max, |
| uint64_t init, uint32_t property_idx) |
| { |
| _msm_property_install_integer(info, name, flags, |
| min, max, init, property_idx, false); |
| } |
| |
| void msm_property_install_volatile_range(struct msm_property_info *info, |
| const char *name, int flags, uint64_t min, uint64_t max, |
| uint64_t init, uint32_t property_idx) |
| { |
| _msm_property_install_integer(info, name, flags, |
| min, max, init, property_idx, true); |
| } |
| |
| void msm_property_install_rotation(struct msm_property_info *info, |
| unsigned int supported_rotations, uint32_t property_idx) |
| { |
| struct drm_property **prop; |
| |
| if (!info) |
| return; |
| |
| ++info->install_request; |
| |
| if (property_idx >= info->property_count) { |
| DRM_ERROR("invalid property index %d\n", property_idx); |
| } else { |
| prop = &info->property_array[property_idx]; |
| /* |
| * Properties need to be attached to each drm object that |
| * uses them, but only need to be created once |
| */ |
| if (*prop == 0) { |
| *prop = drm_mode_create_rotation_property(info->dev, |
| supported_rotations); |
| if (*prop == 0) |
| DRM_ERROR("create rotation property failed\n"); |
| } |
| |
| /* save init value for later */ |
| info->property_data[property_idx].default_value = 0; |
| info->property_data[property_idx].force_dirty = false; |
| |
| /* always attach property, if created */ |
| if (*prop) { |
| drm_object_attach_property(info->base, *prop, 0); |
| ++info->install_count; |
| } |
| } |
| } |
| |
| void msm_property_install_enum(struct msm_property_info *info, |
| const char *name, int flags, int is_bitmask, |
| const struct drm_prop_enum_list *values, int num_values, |
| uint32_t property_idx) |
| { |
| struct drm_property **prop; |
| |
| if (!info) |
| return; |
| |
| ++info->install_request; |
| |
| if (!name || !values || !num_values || |
| (property_idx >= info->property_count)) { |
| DRM_ERROR("invalid argument(s), %s\n", name ? name : "null"); |
| } else { |
| prop = &info->property_array[property_idx]; |
| /* |
| * Properties need to be attached to each drm object that |
| * uses them, but only need to be created once |
| */ |
| if (*prop == 0) { |
| /* 'bitmask' is a special type of 'enum' */ |
| if (is_bitmask) |
| *prop = drm_property_create_bitmask(info->dev, |
| DRM_MODE_PROP_BITMASK | flags, |
| name, values, num_values, -1); |
| else |
| *prop = drm_property_create_enum(info->dev, |
| DRM_MODE_PROP_ENUM | flags, |
| name, values, num_values); |
| if (*prop == 0) |
| DRM_ERROR("create %s property failed\n", name); |
| } |
| |
| /* save init value for later */ |
| info->property_data[property_idx].default_value = 0; |
| info->property_data[property_idx].force_dirty = false; |
| |
| /* select first defined value for enums */ |
| if (!is_bitmask) |
| info->property_data[property_idx].default_value = |
| values->type; |
| |
| /* always attach property, if created */ |
| if (*prop) { |
| drm_object_attach_property(info->base, *prop, |
| info->property_data |
| [property_idx].default_value); |
| ++info->install_count; |
| } |
| } |
| } |
| |
| void msm_property_install_blob(struct msm_property_info *info, |
| const char *name, int flags, uint32_t property_idx) |
| { |
| struct drm_property **prop; |
| |
| if (!info) |
| return; |
| |
| ++info->install_request; |
| |
| if (!name || (property_idx >= info->blob_count)) { |
| DRM_ERROR("invalid argument(s), %s\n", name ? name : "null"); |
| } else { |
| prop = &info->property_array[property_idx]; |
| /* |
| * Properties need to be attached to each drm object that |
| * uses them, but only need to be created once |
| */ |
| if (*prop == 0) { |
| /* use 'create' for blob property place holder */ |
| *prop = drm_property_create(info->dev, |
| DRM_MODE_PROP_BLOB | flags, name, 0); |
| if (*prop == 0) |
| DRM_ERROR("create %s property failed\n", name); |
| } |
| |
| /* save init value for later */ |
| info->property_data[property_idx].default_value = 0; |
| info->property_data[property_idx].force_dirty = true; |
| |
| /* always attach property, if created */ |
| if (*prop) { |
| drm_object_attach_property(info->base, *prop, -1); |
| ++info->install_count; |
| } |
| } |
| } |
| |
| int msm_property_install_get_status(struct msm_property_info *info) |
| { |
| int rc = -ENOMEM; |
| |
| if (info && (info->install_request == info->install_count)) |
| rc = 0; |
| |
| return rc; |
| } |
| |
| int msm_property_index(struct msm_property_info *info, |
| struct drm_property *property) |
| { |
| uint32_t count; |
| int32_t idx; |
| int rc = -EINVAL; |
| |
| if (!info || !property) { |
| DRM_ERROR("invalid argument(s)\n"); |
| } else { |
| /* |
| * Linear search, but start from last found index. This will |
| * help if any single property is accessed multiple times in a |
| * row. Ideally, we could keep a list of properties sorted in |
| * the order of most recent access, but that may be overkill |
| * for now. |
| */ |
| mutex_lock(&info->property_lock); |
| idx = info->recent_idx; |
| count = info->property_count; |
| while (count) { |
| --count; |
| |
| /* stop searching on match */ |
| if (info->property_array[idx] == property) { |
| info->recent_idx = idx; |
| rc = idx; |
| break; |
| } |
| |
| /* move to next valid index */ |
| if (--idx < 0) |
| idx = info->property_count - 1; |
| } |
| mutex_unlock(&info->property_lock); |
| } |
| |
| return rc; |
| } |
| |
| int msm_property_set_dirty(struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| int property_idx) |
| { |
| if (!info || !property_state || !property_state->values) { |
| DRM_ERROR("invalid argument(s)\n"); |
| return -EINVAL; |
| } |
| mutex_lock(&info->property_lock); |
| _msm_property_set_dirty_no_lock(info, property_state, property_idx); |
| mutex_unlock(&info->property_lock); |
| return 0; |
| } |
| |
| int msm_property_atomic_set(struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| struct drm_property *property, uint64_t val) |
| { |
| struct drm_property_blob *blob; |
| int property_idx, rc = -EINVAL; |
| |
| if (!info || !property_state) { |
| DRM_ERROR("invalid argument(s)\n"); |
| return -EINVAL; |
| } |
| |
| property_idx = msm_property_index(info, property); |
| if ((property_idx == -EINVAL) || !property_state->values) { |
| DRM_ERROR("invalid argument(s)\n"); |
| } else { |
| /* extra handling for incoming properties */ |
| mutex_lock(&info->property_lock); |
| if ((property->flags & DRM_MODE_PROP_BLOB) && |
| (property_idx < info->blob_count)) { |
| |
| /* need to clear previous ref */ |
| if (property_state->values[property_idx].blob) |
| drm_property_unreference_blob( |
| property_state->values[ |
| property_idx].blob); |
| |
| /* DRM lookup also takes a reference */ |
| blob = drm_property_lookup_blob(info->dev, |
| (uint32_t)val); |
| if (val && !blob) { |
| DRM_ERROR("prop %d blob id 0x%llx not found\n", |
| property_idx, val); |
| val = 0; |
| } else { |
| if (blob) { |
| DBG("Blob %u saved", blob->base.id); |
| val = blob->base.id; |
| } |
| |
| /* save the new blob */ |
| property_state->values[property_idx].blob = |
| blob; |
| } |
| } |
| |
| /* update value and flag as dirty */ |
| if (property_state->values[property_idx].value != val || |
| info->property_data[property_idx].force_dirty) { |
| property_state->values[property_idx].value = val; |
| _msm_property_set_dirty_no_lock(info, property_state, |
| property_idx); |
| |
| DBG("%s - %lld", property->name, val); |
| } |
| mutex_unlock(&info->property_lock); |
| rc = 0; |
| } |
| |
| return rc; |
| } |
| |
| int msm_property_atomic_get(struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| struct drm_property *property, uint64_t *val) |
| { |
| int property_idx, rc = -EINVAL; |
| |
| property_idx = msm_property_index(info, property); |
| if (!info || (property_idx == -EINVAL) || |
| !property_state->values || !val) { |
| DRM_DEBUG("Invalid argument(s)\n"); |
| } else { |
| mutex_lock(&info->property_lock); |
| *val = property_state->values[property_idx].value; |
| mutex_unlock(&info->property_lock); |
| rc = 0; |
| } |
| |
| return rc; |
| } |
| |
| void *msm_property_alloc_state(struct msm_property_info *info) |
| { |
| void *state = NULL; |
| |
| if (!info) { |
| DRM_ERROR("invalid property info\n"); |
| return NULL; |
| } |
| |
| mutex_lock(&info->property_lock); |
| if (info->state_cache_size) |
| state = info->state_cache[--(info->state_cache_size)]; |
| mutex_unlock(&info->property_lock); |
| |
| if (!state && info->state_size) |
| state = kmalloc(info->state_size, GFP_KERNEL); |
| |
| if (!state) |
| DRM_ERROR("failed to allocate state\n"); |
| |
| return state; |
| } |
| |
| /** |
| * _msm_property_free_state - helper function for freeing local state objects |
| * @info: Pointer to property info container struct |
| * @st: Pointer to state object |
| */ |
| static void _msm_property_free_state(struct msm_property_info *info, void *st) |
| { |
| if (!info || !st) |
| return; |
| |
| mutex_lock(&info->property_lock); |
| if (info->state_cache_size < MSM_PROP_STATE_CACHE_SIZE) |
| info->state_cache[(info->state_cache_size)++] = st; |
| else |
| kfree(st); |
| mutex_unlock(&info->property_lock); |
| } |
| |
| void msm_property_reset_state(struct msm_property_info *info, void *state, |
| struct msm_property_state *property_state, |
| struct msm_property_value *property_values) |
| { |
| uint32_t i; |
| |
| if (!info) { |
| DRM_ERROR("invalid property info\n"); |
| return; |
| } |
| |
| if (state) |
| memset(state, 0, info->state_size); |
| |
| if (property_state) { |
| property_state->property_count = info->property_count; |
| property_state->values = property_values; |
| INIT_LIST_HEAD(&property_state->dirty_list); |
| } |
| |
| /* |
| * Assign default property values. This helper is mostly used |
| * to initialize newly created state objects. |
| */ |
| if (property_values) |
| for (i = 0; i < info->property_count; ++i) { |
| property_values[i].value = |
| info->property_data[i].default_value; |
| property_values[i].blob = NULL; |
| INIT_LIST_HEAD(&property_values[i].dirty_node); |
| } |
| } |
| |
| void msm_property_duplicate_state(struct msm_property_info *info, |
| void *old_state, void *state, |
| struct msm_property_state *property_state, |
| struct msm_property_value *property_values) |
| { |
| uint32_t i; |
| |
| if (!info || !old_state || !state) { |
| DRM_ERROR("invalid argument(s)\n"); |
| return; |
| } |
| |
| memcpy(state, old_state, info->state_size); |
| |
| if (!property_state) |
| return; |
| |
| INIT_LIST_HEAD(&property_state->dirty_list); |
| property_state->values = property_values; |
| |
| if (property_state->values) |
| /* add ref count for blobs and initialize dirty nodes */ |
| for (i = 0; i < info->property_count; ++i) { |
| if (property_state->values[i].blob) |
| drm_property_reference_blob( |
| property_state->values[i].blob); |
| INIT_LIST_HEAD(&property_state->values[i].dirty_node); |
| } |
| } |
| |
| void msm_property_destroy_state(struct msm_property_info *info, void *state, |
| struct msm_property_state *property_state) |
| { |
| uint32_t i; |
| |
| if (!info || !state) { |
| DRM_ERROR("invalid argument(s)\n"); |
| return; |
| } |
| if (property_state && property_state->values) { |
| /* remove ref count for blobs */ |
| for (i = 0; i < info->property_count; ++i) |
| if (property_state->values[i].blob) { |
| drm_property_unreference_blob( |
| property_state->values[i].blob); |
| property_state->values[i].blob = NULL; |
| } |
| } |
| |
| _msm_property_free_state(info, state); |
| } |
| |
| void *msm_property_get_blob(struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| size_t *byte_len, |
| uint32_t property_idx) |
| { |
| struct drm_property_blob *blob; |
| size_t len = 0; |
| void *rc = 0; |
| |
| if (!info || !property_state || !property_state->values || |
| (property_idx >= info->blob_count)) { |
| DRM_ERROR("invalid argument(s)\n"); |
| } else { |
| blob = property_state->values[property_idx].blob; |
| if (blob) { |
| len = blob->length; |
| rc = &blob->data; |
| } |
| } |
| |
| if (byte_len) |
| *byte_len = len; |
| |
| return rc; |
| } |
| |
| int msm_property_set_blob(struct msm_property_info *info, |
| struct drm_property_blob **blob_reference, |
| void *blob_data, |
| size_t byte_len, |
| uint32_t property_idx) |
| { |
| struct drm_property_blob *blob = NULL; |
| int rc = -EINVAL; |
| |
| if (!info || !blob_reference || (property_idx >= info->blob_count)) { |
| DRM_ERROR("invalid argument(s)\n"); |
| } else { |
| /* create blob */ |
| if (blob_data && byte_len) { |
| blob = drm_property_create_blob(info->dev, |
| byte_len, |
| blob_data); |
| if (IS_ERR_OR_NULL(blob)) { |
| rc = PTR_ERR(blob); |
| DRM_ERROR("failed to create blob, %d\n", rc); |
| goto exit; |
| } |
| } |
| |
| /* update drm object */ |
| rc = drm_object_property_set_value(info->base, |
| info->property_array[property_idx], |
| blob ? blob->base.id : 0); |
| if (rc) { |
| DRM_ERROR("failed to set blob to property\n"); |
| if (blob) |
| drm_property_unreference_blob(blob); |
| goto exit; |
| } |
| |
| /* update local reference */ |
| if (*blob_reference) |
| drm_property_unreference_blob(*blob_reference); |
| *blob_reference = blob; |
| } |
| |
| exit: |
| return rc; |
| } |
| |
| int msm_property_set_property(struct msm_property_info *info, |
| struct msm_property_state *property_state, |
| uint32_t property_idx, |
| uint64_t val) |
| { |
| int rc = -EINVAL; |
| |
| if (!info || (property_idx >= info->property_count) || |
| property_idx < info->blob_count || |
| !property_state || !property_state->values) { |
| DRM_ERROR("invalid argument(s)\n"); |
| } else { |
| struct drm_property *drm_prop; |
| |
| mutex_lock(&info->property_lock); |
| |
| /* update cached value */ |
| property_state->values[property_idx].value = val; |
| |
| /* update the new default value for immutables */ |
| drm_prop = info->property_array[property_idx]; |
| if (drm_prop->flags & DRM_MODE_PROP_IMMUTABLE) |
| info->property_data[property_idx].default_value = val; |
| |
| mutex_unlock(&info->property_lock); |
| |
| /* update drm object */ |
| rc = drm_object_property_set_value(info->base, drm_prop, val); |
| if (rc) |
| DRM_ERROR("failed set property value, idx %d rc %d\n", |
| property_idx, rc); |
| |
| } |
| |
| return rc; |
| } |
| |