// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. */ #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) { *prop = drm_property_create_range(info->dev, flags, name, min, max); if (!*prop) 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_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) { /* '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) 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) { /* use 'create' for blob property place holder */ *prop = drm_property_create(info->dev, DRM_MODE_PROP_BLOB | flags, name, 0); if (!*prop) 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_blob_put( 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_blob_get( 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_blob_put( 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_blob_put(blob); goto exit; } /* update local reference */ if (*blob_reference) drm_property_blob_put(*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; }