diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index f1f61419fd29..ca3cb4fb3900 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -686,3 +686,5 @@ config VIDEO_RCAR_DRIF will be called rcar_drif. endif # SDR_PLATFORM_DRIVERS + +source "drivers/media/platform/msm/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 6ee7eb0d36f4..5204445615a3 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -101,3 +101,5 @@ obj-y += meson/ obj-y += cros-ec-cec/ obj-y += sunxi/ + +obj-y += msm/ diff --git a/drivers/media/platform/msm/Kconfig b/drivers/media/platform/msm/Kconfig new file mode 100644 index 000000000000..423d7f1d7e59 --- /dev/null +++ b/drivers/media/platform/msm/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +source "drivers/media/platform/msm/synx/Kconfig" diff --git a/drivers/media/platform/msm/Makefile b/drivers/media/platform/msm/Makefile new file mode 100644 index 000000000000..b0ef6078267e --- /dev/null +++ b/drivers/media/platform/msm/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MSM_GLOBAL_SYNX) += synx/ diff --git a/drivers/media/platform/msm/synx/Kconfig b/drivers/media/platform/msm/synx/Kconfig new file mode 100644 index 000000000000..c6cb58fa2ac0 --- /dev/null +++ b/drivers/media/platform/msm/synx/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig MSM_GLOBAL_SYNX + tristate "Qualcomm Technologies, Inc. Global Synchronization Framework" + depends on ARCH_QCOM + help + Say M here to enable global synchronization module for + Qualcomm Technologies, Inc. chipsets. + Enabling this adds support for global synchronization across + heterogeneous cores. + diff --git a/drivers/media/platform/msm/synx/Makefile b/drivers/media/platform/msm/synx/Makefile new file mode 100644 index 000000000000..0435b083a21f --- /dev/null +++ b/drivers/media/platform/msm/synx/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_MSM_GLOBAL_SYNX) += synx-driver.o + +synx-driver-y := synx.o synx_util.o synx_debugfs.o diff --git a/drivers/media/platform/msm/synx/synx.c b/drivers/media/platform/msm/synx/synx.c new file mode 100644 index 000000000000..19e49c91de75 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx.c @@ -0,0 +1,1771 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ +#define pr_fmt(fmt) "synx: " fmt + +#include +#include +#include +#include +#include + +#include "synx_api.h" +#include "synx_util.h" +#include "synx_debugfs.h" + +struct synx_device *synx_dev; + +void synx_external_callback(s32 sync_obj, int status, void *data) +{ + struct synx_coredata *synx_obj; + struct synx_client *client = NULL; + struct synx_external_data *bind_data = data; + + if (!bind_data) { + pr_err("invalid payload from sync external obj %d\n", + sync_obj); + return; + } + + client = synx_get_client(bind_data->session_id); + if (!client) + goto free; + + synx_obj = synx_util_acquire_object(client, bind_data->h_synx); + if (!synx_obj) { + pr_err("[sess: %u] invalid callback from external obj %d handle %d\n", + client->id, sync_obj, bind_data->h_synx); + goto fail; + } + + pr_debug("[sess: %u] external callback from %d on handle %d\n", + client->id, sync_obj, bind_data->h_synx); + if (synx_signal_core(synx_obj, status, true, sync_obj)) + pr_err("[sess: %u] signal callback failed for handle %d\n", + client->id, bind_data->h_synx); + + synx_util_release_object(client, bind_data->h_synx); +fail: + synx_put_client(client); +free: + kfree(bind_data); +} +EXPORT_SYMBOL(synx_external_callback); + +bool synx_fence_enable_signaling(struct dma_fence *fence) +{ + return true; +} + +const char *synx_fence_driver_name(struct dma_fence *fence) +{ + return "Global Synx driver"; +} + +void synx_fence_release(struct dma_fence *fence) +{ + struct synx_coredata *synx_obj = + container_of(fence, struct synx_coredata, fence); + + synx_util_object_destroy(synx_obj); +} +EXPORT_SYMBOL(synx_fence_release); + +static struct dma_fence_ops synx_fence_ops = { + .wait = dma_fence_default_wait, + .enable_signaling = synx_fence_enable_signaling, + .get_driver_name = synx_fence_driver_name, + .get_timeline_name = synx_fence_driver_name, + .release = synx_fence_release, +}; + +int synx_create(struct synx_session session_id, + struct synx_create_params *params) +{ + int rc; + long h_synx; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter create from pid %d\n", + session_id.client_id, current->pid); + + if (!params || !params->h_synx) { + pr_err("[sess: %u] invalid create arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (!synx_obj) { + rc = -ENOMEM; + goto fail; + } + + rc = synx_util_init_coredata(synx_obj, params, &synx_fence_ops); + if (rc) { + pr_err("[sess: %u] error initializing synx obj\n", + client->id); + goto clean_up; + } + + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc < 0) { + pr_err("[sess: %u] unable to init new handle\n", + client->id); + goto clean_up; + } + + rc = synx_util_activate(synx_obj); + if (rc) { + pr_err("[sess: %u] unable to activate handle %ld\n", + client->id, h_synx); + goto clear_bit; + return -EINVAL; + } + + *params->h_synx = h_synx; + pr_debug("[sess: %u] new synx obj with handle %ld\n", + client->id, h_synx); + synx_put_client(client); + return 0; + +clear_bit: + clear_bit(h_synx, client->bitmap); +clean_up: + kfree(synx_obj); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_create); + +int synx_signal_core(struct synx_coredata *synx_obj, + u32 status, + bool cb_signal, + s32 ext_sync_id) +{ + int rc, ret; + u32 i = 0; + u32 idx = 0; + s32 sync_id; + u32 type; + struct dma_fence *fence; + struct synx_external_data *data = NULL; + struct synx_bind_desc bind_descs[SYNX_MAX_NUM_BINDINGS]; + struct bind_operations *bind_ops = NULL; + + if (!synx_obj) + return -EINVAL; + + if (status < SYNX_STATE_SIGNALED_SUCCESS) { + pr_err("signaling with undefined status = %d\n", status); + return -EINVAL; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("signaling a composite synx object\n"); + return -EINVAL; + } + + spin_lock_bh(&synx_obj->lock); + + if (synx_util_get_object_status_locked(synx_obj) != SYNX_STATE_ACTIVE) { + spin_unlock_bh(&synx_obj->lock); + return -EALREADY; + } + + fence = synx_util_get_fence(synx_obj); + /* set fence error to model {signal w/ error} */ + if (status != SYNX_STATE_SIGNALED_SUCCESS) + dma_fence_set_error(fence, -status); + + rc = dma_fence_signal_locked(fence); + if (rc) { + pr_err("signaling object failed with err: %d\n", rc); + if (status == SYNX_STATE_SIGNALED_SUCCESS) { + status = SYNX_STATE_SIGNALED_ERROR; + dma_fence_set_error(fence, -status); + } + } + + synx_util_callback_dispatch(synx_obj, status); + + /* + * signal the external bound sync obj/s even if fence signal fails, + * w/ error signal state (set above) to prevent deadlock + */ + if (synx_obj->num_bound_synxs > 0) { + memset(bind_descs, 0, + sizeof(struct synx_bind_desc) * SYNX_MAX_NUM_BINDINGS); + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + /* signal invoked by external sync obj */ + if (cb_signal && + (ext_sync_id == + synx_obj->bound_synxs[i].external_desc.id[0])) { + pr_debug("skipping signaling inbound sync: %d\n", + ext_sync_id); + memset(&synx_obj->bound_synxs[i], 0, + sizeof(struct synx_bind_desc)); + continue; + } + memcpy(&bind_descs[idx++], + &synx_obj->bound_synxs[i], + sizeof(struct synx_bind_desc)); + /* clear the memory, its been backed up above */ + memset(&synx_obj->bound_synxs[i], 0, + sizeof(struct synx_bind_desc)); + } + synx_obj->num_bound_synxs = 0; + } + spin_unlock_bh(&synx_obj->lock); + + for (i = 0; i < idx; i++) { + sync_id = bind_descs[i].external_desc.id[0]; + data = bind_descs[i].external_data; + type = bind_descs[i].external_desc.type; + bind_ops = synx_util_get_bind_ops(type); + if (!bind_ops) { + pr_err("invalid bind ops for type: %u\n", type); + kfree(data); + continue; + } + /* + * we are already signaled, so don't want to + * recursively be signaled + */ + ret = bind_ops->deregister_callback( + synx_external_callback, data, sync_id); + if (ret < 0) + pr_err("deregistration fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + pr_debug("signal external sync: %d, type: %u, status: %u\n", + sync_id, type, status); + /* optional function to enable external signaling */ + if (bind_ops->enable_signaling) { + ret = bind_ops->enable_signaling(sync_id); + if (ret < 0) { + pr_err("enabling fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + continue; + } + } + ret = bind_ops->signal(sync_id, status); + if (ret < 0) + pr_err("signaling fail on %d, type: %u, err: %d\n", + sync_id, type, ret); + /* + * release the memory allocated for external data. + * It is safe to release this memory as external cb + * has been already deregistered before this. + */ + kfree(data); + } + + return rc; +} + +int synx_signal(struct synx_session session_id, s32 h_synx, u32 status) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter signal from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + rc = synx_signal_core(synx_obj, status, false, 0); + if (rc) + pr_err("[sess: %u] signaling failed for handle %d with err: %d\n", + client->id, h_synx, rc); + synx_util_release_object(client, h_synx); + +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit signal with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_signal); + +static int synx_match_payload(struct synx_client_cb *cb, + struct synx_kernel_payload *payload, + bool is_kernel_cb) +{ + int rc = 0; + + if (!cb || !payload) + return -EINVAL; + + if (is_kernel_cb) { + struct synx_kernel_payload *cb_payload = &cb->kernel_cb; + + if ((cb_payload->cb_func == payload->cb_func) && + (cb_payload->data == payload->data)) { + if (payload->cancel_cb_func) { + cb_payload->cb_func = + payload->cancel_cb_func; + rc = 1; + } else { + rc = 2; + pr_debug("kernel cb deregistration success\n"); + } + } + } else { + struct synx_user_payload *cb_payload = &cb->user_cb; + struct synx_user_payload *user_payload = payload->data; + + if ((cb_payload->data[0] == user_payload->data[0]) && + (cb_payload->data[1] == user_payload->data[1])) { + if (user_payload->data[2]) { + memcpy(cb_payload->data, + user_payload->data, + SYNX_PAYLOAD_WORDS * sizeof(__u64)); + rc = 1; + } else { + rc = 2; + pr_debug("user cb deregistration success\n"); + } + } + } + + return rc; +} + +void synx_default_user_callback(s32 h_synx, + int status, void *data) +{ + struct synx_user_payload *payload = data; + struct synx_client *client = payload->client; + struct synx_client_cb *cb = + container_of(payload, struct synx_client_cb, user_cb); + + if (client) { + payload->status = status; + pr_debug("user cb queued for handle %d\n", h_synx); + mutex_lock(&client->event_q_lock); + list_add_tail(&cb->node, &client->event_q); + mutex_unlock(&client->event_q_lock); + wake_up_all(&client->event_wq); + } else { + pr_err("%s: invalid client session\n", __func__); + } +} + +int synx_register_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata) +{ + int rc = 0; + u32 idx; + u32 status; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_cb_data *synx_cb; + struct synx_kernel_payload payload; + + pr_debug("[sess: %u] Enter register cb from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("cannot register cb with composite synx object\n"); + goto clear; + } + + synx_cb = kzalloc(sizeof(*synx_cb), GFP_ATOMIC); + if (!synx_cb) { + rc = -ENOMEM; + goto clear; + } + + if (cb_func == synx_default_user_callback) { + /* userspace callback index allocated already */ + idx = *((u32 *)userdata); + if (idx < SYNX_MAX_OBJS) { + /* data used in default callback func */ + userdata = &client->cb_table[idx].user_cb; + } else { + pr_err("%s: [sess: %u] invalid cb index\n", + __func__, client->id); + kfree(synx_cb); + rc = -EINVAL; + goto clear; + } + } else { + /* obtain a free index from client cb table */ + rc = synx_util_alloc_cb_entry(client, &idx); + if (rc) { + pr_err("[sess :%u] error allocating cb entry\n", + client->id); + kfree(synx_cb); + goto clear; + } + } + + payload.h_synx = h_synx; + payload.cb_func = cb_func; + payload.data = userdata; + /* update the cb entry with kernel cb data */ + rc = synx_util_update_cb_entry(client, &payload, idx); + if (rc) { + pr_err("[sess :%u] error allocating cb entry\n", + client->id); + kfree(synx_cb); + clear_bit(idx, client->cb_bitmap); + goto clear; + } + + spin_lock_bh(&synx_obj->lock); + status = synx_util_get_object_status_locked(synx_obj); + synx_cb->session_id = session_id; + synx_cb->idx = idx; + INIT_WORK(&synx_cb->cb_dispatch, synx_util_cb_dispatch); + + /* add callback if object still ACTIVE, dispatch if SIGNALED */ + if (status == SYNX_STATE_ACTIVE) { + pr_debug("[sess: %u] callback added\n", client->id); + list_add_tail(&synx_cb->node, &synx_obj->reg_cbs_list); + } else { + synx_cb->status = status; + pr_debug("[sess: %u] callback queued\n", client->id); + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + } + spin_unlock_bh(&synx_obj->lock); + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit register cb with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_register_callback); + +int synx_deregister_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata, + synx_callback cancel_cb_func) +{ + int rc = 0, ret = 0; + u32 status; + bool is_kernel_cb = true; + bool match_found = false; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_kernel_payload payload; + struct synx_cb_data *synx_cb, *synx_cb_temp; + struct synx_client_cb *cb_payload; + + pr_debug("[sess: %u] Enter deregister cb from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("cannot deregister cb with composite synx object\n"); + goto clear; + } + + if (cb_func == synx_default_user_callback) + is_kernel_cb = false; + + payload.h_synx = h_synx; + payload.cb_func = cb_func; + payload.data = userdata; + payload.cancel_cb_func = cancel_cb_func; + + spin_lock_bh(&synx_obj->lock); + status = synx_util_get_object_status_locked(synx_obj); + if (status != SYNX_STATE_ACTIVE) { + pr_err("handle %d already signaled. cannot deregister cb/s\n", + h_synx); + rc = -EINVAL; + spin_unlock_bh(&synx_obj->lock); + goto clear; + } + + status = SYNX_CALLBACK_RESULT_CANCELED; + /* remove all cb payloads mayching the deregister call */ + list_for_each_entry_safe(synx_cb, synx_cb_temp, + &synx_obj->reg_cbs_list, node) { + if (synx_cb->session_id.client_id != client->id) { + continue; + } else if (synx_cb->idx >= SYNX_MAX_OBJS) { + /* + * this should not happen. Even if it does, + * the allocated memory will be cleaned up + * when object is destroyed, preventing any + * memory leaks. + */ + pr_err("[sess: %u] invalid callback data\n", + session_id.client_id); + continue; + } + + cb_payload = &client->cb_table[synx_cb->idx]; + ret = synx_match_payload(cb_payload, &payload, is_kernel_cb); + switch (ret) { + case 1: + /* queue the cancel cb work */ + list_del_init(&synx_cb->node); + synx_cb->status = status; + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + match_found = true; + break; + case 2: + /* no cancellation cb */ + list_del_init(&synx_cb->node); + kfree(synx_cb); + match_found = true; + break; + default: + break; + } + } + spin_unlock_bh(&synx_obj->lock); + + if (!match_found) + rc = -EINVAL; + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + return rc; + + pr_debug("[sess: %u] exit deregister cb with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_deregister_callback); + +int synx_merge(struct synx_session session_id, + s32 *h_synxs, + u32 num_objs, + s32 *h_synx_merged) +{ + int rc; + long h_synx; + u32 count = 0; + struct synx_client *client; + struct dma_fence **fences = NULL; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter merge from pid %d\n", + session_id.client_id, current->pid); + + if (!h_synxs || !h_synx_merged) { + pr_err("%s: [sess: %u] invalid arguments\n", + __func__, session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + rc = synx_util_validate_merge(client, h_synxs, + num_objs, &fences, &count); + if (rc < 0) { + pr_err("[sess: %u] merge validation failed\n", + client->id); + rc = -EINVAL; + goto fail; + } + + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (!synx_obj) { + rc = -ENOMEM; + goto fail; + } + + rc = synx_util_init_group_coredata(synx_obj, fences, count); + if (rc) { + pr_err("[sess: %u] error initializing merge synx obj\n", + client->id); + goto clean_up; + } + + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc) { + pr_err("[sess: %u] unable to init merge handle %ld\n", + client->id, h_synx); + goto clean_up; + } + + rc = synx_util_activate(synx_obj); + if (rc) { + pr_err("[sess: %u] unable to activate merge handle %ld\n", + client->id, h_synx); + goto clear_bit; + } + + *h_synx_merged = h_synx; + synx_put_client(client); + pr_debug("[sess: %u] exit merge with status %d\n", + session_id.client_id, rc); + return 0; + +clear_bit: + clear_bit(h_synx, client->bitmap); +clean_up: + kfree(synx_obj); +fail: + synx_util_merge_error(client, h_synxs, count); + if (num_objs <= count) + kfree(fences); + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_merge); + +int synx_release(struct synx_session session_id, s32 h_synx) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter release from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + /* release the reference obtained at synx creation */ + synx_util_release_object(client, h_synx); + synx_util_release_object(client, h_synx); + +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit release with status %d\n", + session_id.client_id, rc); + + return rc; +} +EXPORT_SYMBOL(synx_release); + +int synx_wait(struct synx_session session_id, s32 h_synx, u64 timeout_ms) +{ + int rc = 0; + unsigned long timeleft; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + pr_debug("[sess: %u] Enter wait from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + fence = synx_util_get_fence(synx_obj); + timeleft = dma_fence_wait_timeout(fence, (bool) 0, + msecs_to_jiffies(timeout_ms)); + if (timeleft <= 0) { + pr_err("[sess: %u] wait timeout for handle %d\n", + client->id, h_synx); + rc = -ETIMEDOUT; + goto clear; + } + + rc = synx_util_get_object_status(synx_obj); + /* remap the state if signaled successfully */ + if (rc == SYNX_STATE_SIGNALED_SUCCESS) + rc = 0; + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit wait with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_wait); + +int synx_bind(struct synx_session session_id, + s32 h_synx, + struct synx_external_desc external_sync) +{ + int rc = 0; + u32 i; + struct synx_client *client; + struct synx_coredata *synx_obj; + struct synx_external_data *data = NULL; + struct bind_operations *bind_ops = NULL; + + pr_debug("[sess: %u] Enter bind from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if (synx_util_is_merged_object(synx_obj)) { + pr_err("[sess: %u] cannot bind to merged handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto clear; + } + + bind_ops = synx_util_get_bind_ops(external_sync.type); + if (!bind_ops) { + pr_err("[sess: %u] invalid bind ops for %u\n", + client->id, external_sync.type); + rc = -EINVAL; + goto clear; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + rc = -ENOMEM; + goto clear; + } + + spin_lock_bh(&synx_obj->lock); + if (synx_util_get_object_status_locked(synx_obj) != SYNX_STATE_ACTIVE) { + pr_err("[sess: %u] bind prohibited to inactive handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto free; + } + + if (synx_obj->num_bound_synxs >= SYNX_MAX_NUM_BINDINGS) { + pr_err("[sess: %u] max bindings reached for handle %d\n", + client->id, h_synx); + rc = -ENOMEM; + goto free; + } + + /* don't bind external sync obj is already done */ + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + if (external_sync.id[0] == + synx_obj->bound_synxs[i].external_desc.id[0]) { + pr_err("[sess: %u] duplicate binding for external sync %d\n", + client->id, external_sync.id[0]); + rc = -EALREADY; + goto free; + } + } + + /* data passed to external callback */ + data->h_synx = h_synx; + data->session_id = session_id; + rc = bind_ops->register_callback(synx_external_callback, + data, external_sync.id[0]); + if (rc) { + pr_err("[sess: %u] callback registration failed for %d\n", + client->id, external_sync.id[0]); + goto free; + } + + memcpy(&synx_obj->bound_synxs[synx_obj->num_bound_synxs], + &external_sync, sizeof(struct synx_external_desc)); + synx_obj->bound_synxs[synx_obj->num_bound_synxs].external_data = data; + synx_obj->num_bound_synxs++; + spin_unlock_bh(&synx_obj->lock); + + synx_util_release_object(client, h_synx); + synx_put_client(client); + pr_debug("[sess: %u] exit bind\n", + session_id.client_id); + return 0; + +free: + spin_unlock_bh(&synx_obj->lock); + kfree(data); +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_bind); + +int synx_get_status(struct synx_session session_id, s32 h_synx) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter get_status from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = SYNX_STATE_INVALID; + goto fail; + } + + rc = synx_util_get_object_status(synx_obj); + pr_debug("[sess: %u] synx object handle %d status %d\n", + client->id, h_synx, rc); + + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit get_status with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_get_status); + +int synx_addrefcount(struct synx_session session_id, s32 h_synx, s32 count) +{ + int rc = 0; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter addrefcount from pid %d\n", + session_id.client_id, current->pid); + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_acquire_object(client, h_synx); + if (!synx_obj) { + pr_err("%s: [sess: %u] invalid handle access %d\n", + __func__, client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + if ((count < 0) || (count > SYNX_MAX_REF_COUNTS)) { + pr_err("[sess: %u] invalid addrefcount for handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto clear; + } + + while (count--) + synx_util_acquire_object(client, h_synx); + +clear: + synx_util_release_object(client, h_synx); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit addrefcount with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_addrefcount); + +int synx_import(struct synx_session session_id, + struct synx_import_params *params) +{ + int rc = 0; + long h_synx; + struct synx_client *client; + struct synx_coredata *synx_obj; + + pr_debug("[sess: %u] Enter import from pid %d\n", + session_id.client_id, current->pid); + + if (!params || !params->new_h_synx) { + pr_err("[sess: %u] invalid import arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + synx_obj = synx_util_import_object(params); + if (!synx_obj) { + pr_err("[sess: %u] invalid import handle %d with key %u\n", + client->id, params->h_synx, params->secure_key); + rc = -EINVAL; + goto fail; + } + + /* + * just need to init handle for importing client, the reference + * in synx object has already been accounted for during export + */ + rc = synx_util_init_handle(client, synx_obj, &h_synx); + if (rc) { + pr_err("[sess: %u] unable to init handle\n", + client->id); + goto clean_up; + } + + *params->new_h_synx = h_synx; + synx_put_client(client); + + pr_debug("[sess: %u] exit import\n", session_id.client_id); + return 0; + +clean_up: + synx_util_put_object(synx_obj); +fail: + synx_put_client(client); + return rc; +} +EXPORT_SYMBOL(synx_import); + +int synx_export(struct synx_session session_id, + struct synx_export_params *params) +{ + int rc = 0; + struct synx_client *client; + + if (!params || !params->secure_key) { + pr_err("[sess: %u] invalid export arguments\n", + session_id.client_id); + return -EINVAL; + } + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + rc = synx_util_export_object(client, params); + if (rc) + pr_err("[sess: %u] handle export failed %d\n", + client->id, params->h_synx); + + synx_put_client(client); + pr_debug("[sess: %u] exit export with status %d\n", + session_id.client_id, rc); + return rc; +} +EXPORT_SYMBOL(synx_export); + +static int synx_handle_create(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int result; + struct synx_info synx_create_info; + struct synx_create_params params; + + if (k_ioctl->size != sizeof(synx_create_info)) + return -EINVAL; + + if (copy_from_user(&synx_create_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = &synx_create_info.synx_obj; + params.name = synx_create_info.name; + result = synx_create(session_id, ¶ms); + + if (!result) + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_create_info, + k_ioctl->size)) + return -EFAULT; + + return result; +} + +static int synx_handle_getstatus(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_signal synx_status; + + if (k_ioctl->size != sizeof(synx_status)) + return -EINVAL; + + if (copy_from_user(&synx_status, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + synx_status.synx_state = + synx_get_status(session_id, synx_status.synx_obj); + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_status, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_import(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_id_info id_info; + struct synx_import_params params; + + if (k_ioctl->size != sizeof(id_info)) + return -EINVAL; + + if (copy_from_user(&id_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = id_info.synx_obj; + params.secure_key = id_info.secure_key; + params.new_h_synx = &id_info.new_synx_obj; + if (synx_import(session_id, ¶ms)) + return -EINVAL; + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &id_info, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_export(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_id_info id_info; + struct synx_export_params params; + + if (k_ioctl->size != sizeof(id_info)) + return -EINVAL; + + if (copy_from_user(&id_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + params.h_synx = id_info.synx_obj; + params.secure_key = &id_info.secure_key; + if (synx_export(session_id, ¶ms)) + return -EINVAL; + + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &id_info, + k_ioctl->size)) + return -EFAULT; + + return 0; +} + +static int synx_handle_signal(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_signal synx_signal_info; + + if (k_ioctl->size != sizeof(synx_signal_info)) + return -EINVAL; + + if (copy_from_user(&synx_signal_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + return synx_signal(session_id, synx_signal_info.synx_obj, + synx_signal_info.synx_state); +} + +static int synx_handle_merge(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + s32 *h_synxs; + u32 num_objs; + u32 size; + int result; + struct synx_merge synx_merge_info; + + if (k_ioctl->size != sizeof(synx_merge_info)) + return -EINVAL; + + if (copy_from_user(&synx_merge_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + if (synx_merge_info.num_objs >= SYNX_MAX_OBJS) + return -EINVAL; + + num_objs = synx_merge_info.num_objs; + size = sizeof(u32) * num_objs; + h_synxs = kcalloc(synx_merge_info.num_objs, + sizeof(*h_synxs), GFP_KERNEL); + if (!h_synxs) + return -ENOMEM; + + if (copy_from_user(h_synxs, + u64_to_user_ptr(synx_merge_info.synx_objs), + sizeof(u32) * synx_merge_info.num_objs)) { + kfree(h_synxs); + return -EFAULT; + } + + result = synx_merge(session_id, h_synxs, + num_objs, &synx_merge_info.merged); + + if (!result) + if (copy_to_user(u64_to_user_ptr(k_ioctl->ioctl_ptr), + &synx_merge_info, + k_ioctl->size)) { + kfree(h_synxs); + return -EFAULT; + } + + kfree(h_synxs); + return result; +} + +static int synx_handle_wait(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_wait synx_wait_info; + + if (k_ioctl->size != sizeof(synx_wait_info)) + return -EINVAL; + + if (copy_from_user(&synx_wait_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_wait(session_id, synx_wait_info.synx_obj, + synx_wait_info.timeout_ms); + + return 0; +} + +static int synx_handle_register_user_payload( + struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int rc = 0; + u32 idx; + struct synx_userpayload_info user_data; + struct synx_client *client; + struct synx_client_cb *cb_data; + + if (k_ioctl->size != sizeof(user_data)) + return -EINVAL; + + if (copy_from_user(&user_data, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + client = synx_get_client(session_id); + if (!client) + return -EINVAL; + + /* obtain a free index from the cb table */ + rc = synx_util_alloc_cb_entry(client, &idx); + if (rc) { + pr_err("%s: [sess :%u] error allocating cb entry\n", + __func__, client->id); + goto fail; + } + + cb_data = &client->cb_table[idx]; + cb_data->user_cb.client = client; + cb_data->user_cb.h_synx = user_data.synx_obj; + memcpy(&cb_data->user_cb.data, user_data.payload, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + + rc = synx_register_callback(session_id, user_data.synx_obj, + synx_default_user_callback, &idx); + if (rc) { + pr_err("[sess: %u] user cb registration failed for handle %d\n", + session_id.client_id, user_data.synx_obj); + clear_bit(idx, client->cb_bitmap); + } + +fail: + synx_put_client(client); + return rc; +} + +static int synx_handle_deregister_user_payload( + struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + int rc = 0; + struct synx_userpayload_info user_data; + struct synx_user_payload payload; + + if (k_ioctl->size != sizeof(user_data)) + return -EINVAL; + + if (copy_from_user(&user_data, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + payload.h_synx = user_data.synx_obj; + memcpy(&payload.data, user_data.payload, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + + rc = synx_deregister_callback(session_id, + payload.h_synx, synx_default_user_callback, + &payload, NULL); + if (rc) + pr_err("[sess: %u] callback deregistration failed for handle %d\n", + session_id.client_id, payload.h_synx); + + return rc; +} + +static int synx_handle_bind(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_bind synx_bind_info; + + if (k_ioctl->size != sizeof(synx_bind_info)) + return -EINVAL; + + if (copy_from_user(&synx_bind_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_bind(session_id, synx_bind_info.synx_obj, + synx_bind_info.ext_sync_desc); + + return k_ioctl->result; +} + +static int synx_handle_addrefcount(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_addrefcount addrefcount_info; + + if (k_ioctl->size != sizeof(addrefcount_info)) + return -EINVAL; + + if (copy_from_user(&addrefcount_info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + k_ioctl->result = synx_addrefcount(session_id, + addrefcount_info.synx_obj, + addrefcount_info.count); + + return k_ioctl->result; +} + +static int synx_handle_release(struct synx_private_ioctl_arg *k_ioctl, + struct synx_session session_id) +{ + struct synx_info info; + + if (k_ioctl->size != sizeof(info)) + return -EINVAL; + + if (copy_from_user(&info, + u64_to_user_ptr(k_ioctl->ioctl_ptr), + k_ioctl->size)) + return -EFAULT; + + return synx_release(session_id, info.synx_obj); +} + +static long synx_ioctl(struct file *filep, + unsigned int cmd, + unsigned long arg) +{ + s32 rc = 0; + struct synx_private_ioctl_arg k_ioctl; + struct synx_session *session = filep->private_data; + + if (cmd != SYNX_PRIVATE_IOCTL_CMD) { + pr_err("invalid ioctl cmd\n"); + return -ENOIOCTLCMD; + } + + if (copy_from_user(&k_ioctl, + (struct synx_private_ioctl_arg *)arg, + sizeof(k_ioctl))) { + pr_err("invalid ioctl args\n"); + return -EFAULT; + } + + if (!k_ioctl.ioctl_ptr) + return -EINVAL; + + pr_debug("[sess: %u] Enter cmd %u from pid %d\n", + session->client_id, k_ioctl.id, current->pid); + + switch (k_ioctl.id) { + case SYNX_CREATE: + rc = synx_handle_create(&k_ioctl, *session); + break; + case SYNX_RELEASE: + rc = synx_handle_release(&k_ioctl, *session); + break; + case SYNX_REGISTER_PAYLOAD: + rc = synx_handle_register_user_payload(&k_ioctl, + *session); + break; + case SYNX_DEREGISTER_PAYLOAD: + rc = synx_handle_deregister_user_payload(&k_ioctl, + *session); + break; + case SYNX_SIGNAL: + rc = synx_handle_signal(&k_ioctl, *session); + break; + case SYNX_MERGE: + rc = synx_handle_merge(&k_ioctl, *session); + break; + case SYNX_WAIT: + rc = synx_handle_wait(&k_ioctl, *session); + if (copy_to_user((void *)arg, + &k_ioctl, + sizeof(k_ioctl))) { + pr_err("invalid ioctl args\n"); + rc = -EFAULT; + } + break; + case SYNX_BIND: + rc = synx_handle_bind(&k_ioctl, *session); + break; + case SYNX_ADDREFCOUNT: + rc = synx_handle_addrefcount(&k_ioctl, *session); + break; + case SYNX_GETSTATUS: + rc = synx_handle_getstatus(&k_ioctl, *session); + break; + case SYNX_IMPORT: + rc = synx_handle_import(&k_ioctl, *session); + break; + case SYNX_EXPORT: + rc = synx_handle_export(&k_ioctl, *session); + break; + default: + rc = -EINVAL; + } + + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + + return rc; +} + +static ssize_t synx_read(struct file *filep, + char __user *buf, size_t size, loff_t *f_pos) +{ + ssize_t rc = 0; + u32 idx; + struct synx_client *client = NULL; + struct synx_client_cb *cb; + struct synx_session *session = filep->private_data; + struct synx_userpayload_info data; + + pr_debug("[sess: %u] Enter from pid %d\n", + session->client_id, current->pid); + + if (size != sizeof(struct synx_userpayload_info)) { + pr_err("invalid read size\n"); + return -EINVAL; + } + + client = synx_get_client(*session); + if (!client) + return -EINVAL; + + mutex_lock(&client->event_q_lock); + cb = list_first_entry_or_null(&client->event_q, + struct synx_client_cb, node); + if (!cb) { + mutex_unlock(&client->event_q_lock); + rc = -EINVAL; + goto fail; + } + list_del_init(&cb->node); + mutex_unlock(&client->event_q_lock); + + rc = size; + data.synx_obj = cb->user_cb.h_synx; + data.reserved = cb->user_cb.status; + memcpy(data.payload, &cb->user_cb.data, + SYNX_USER_PAYLOAD_SIZE * sizeof(__u64)); + if (copy_to_user(buf, + &data, + sizeof(struct synx_userpayload_info))) { + pr_err("couldn't copy user callback data\n"); + rc = -EFAULT; + } + + idx = cb->idx; + memset(cb, 0, sizeof(*cb)); + clear_bit(idx, client->cb_bitmap); +fail: + synx_put_client(client); + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + + return rc; +} + +static unsigned int synx_poll(struct file *filep, + struct poll_table_struct *poll_table) +{ + int rc = 0; + struct synx_client *client; + struct synx_session *session = filep->private_data; + + pr_debug("[sess: %u] Enter from pid %d\n", + session->client_id, current->pid); + + client = synx_get_client(*session); + if (!client) { + pr_err("invalid session in poll\n"); + return 0; + } + + poll_wait(filep, &client->event_wq, poll_table); + mutex_lock(&client->event_q_lock); + if (!list_empty(&client->event_q)) + rc = POLLPRI; + mutex_unlock(&client->event_q_lock); + + pr_debug("[sess: %u] exit with status %d\n", + session->client_id, rc); + synx_put_client(client); + + return rc; +} + +int synx_initialize(struct synx_session *session_id, + struct synx_initialization_params *params) +{ + u32 i; + long idx; + bool bit; + struct synx_client *client; + struct synx_client_metadata *client_metadata; + + if (!session_id || !params) + return -EINVAL; + + do { + idx = find_first_zero_bit(synx_dev->bitmap, SYNX_MAX_CLIENTS); + if (idx >= SYNX_MAX_CLIENTS) { + pr_err("maximum client limit reached\n"); + return -ENOMEM; + } + bit = test_and_set_bit(idx, synx_dev->bitmap); + } while (bit); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + clear_bit(idx, synx_dev->bitmap); + return -ENOMEM; + } + + if (params->name) + strlcpy(client->name, params->name, sizeof(client->name)); + client->device = synx_dev; + client->id = idx; + mutex_init(&client->event_q_lock); + for (i = 0; i < SYNX_MAX_OBJS; i++) + mutex_init(&client->synx_table_lock[i]); + INIT_LIST_HEAD(&client->event_q); + init_waitqueue_head(&client->event_wq); + /* zero handle not allowed */ + set_bit(0, client->bitmap); + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[idx]; + client_metadata->client = client; + kref_init(&client_metadata->refcount); + mutex_unlock(&synx_dev->dev_table_lock); + + session_id->client_id = idx; + pr_debug("index location %ld allocated for client %s\n", + idx, params->name); + + return 0; +} +EXPORT_SYMBOL(synx_initialize); + +int synx_uninitialize(struct synx_session session_id) +{ + struct synx_client *client; + + client = synx_get_client(session_id); + if (!client) { + pr_err("%s: invalid session id %u\n", + __func__, session_id.client_id); + return -EINVAL; + } + + /* release reference obtained in current function */ + synx_put_client(client); + /* release reference obtained during initialize */ + synx_put_client(client); + return 0; +} +EXPORT_SYMBOL(synx_uninitialize); + +static int synx_open(struct inode *inode, struct file *filep) +{ + int rc = 0; + struct synx_session *session; + char name[SYNX_OBJ_NAME_LEN]; + struct synx_initialization_params params; + + pr_debug("Enter pid: %d\n", current->pid); + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return -ENOMEM; + + scnprintf(name, SYNX_OBJ_NAME_LEN, "umd-client-%d", current->pid); + params.name = name; + rc = synx_initialize(session, ¶ms); + if (rc) { + kfree(session); + } else { + filep->private_data = session; + pr_debug("[sess: %u] allocated new session for pid: %d\n", + session->client_id, current->pid); + } + + return rc; +} + +static int synx_close(struct inode *inode, struct file *filep) +{ + int rc = 0; + struct synx_session *session = filep->private_data; + + pr_debug("[sess: %u] Enter pid: %d\n", + session->client_id, current->pid); + + rc = synx_uninitialize(*session); + kfree(session); + + pr_debug("exit with status %d\n", rc); + + return rc; +} + +static const struct file_operations synx_fops = { + .owner = THIS_MODULE, + .open = synx_open, + .read = synx_read, + .release = synx_close, + .poll = synx_poll, + .unlocked_ioctl = synx_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = synx_ioctl, +#endif +}; + +int synx_register_ops(const struct synx_register_params *params) +{ + s32 rc = 0; + struct synx_registered_ops *client_ops; + + if (!params || !params->name || + !synx_util_is_valid_bind_type(params->type) || + !params->ops.register_callback || + !params->ops.deregister_callback || + !params->ops.signal) { + pr_err("invalid register params\n"); + return -EINVAL; + } + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[params->type]; + if (!client_ops->valid) { + client_ops->valid = true; + memcpy(&client_ops->ops, ¶ms->ops, + sizeof(client_ops->ops)); + strlcpy(client_ops->name, params->name, + sizeof(client_ops->name)); + client_ops->type = params->type; + pr_info("registered bind ops type %u for %s\n", + params->type, params->name); + } else { + pr_info("client already registered for type %u by %s\n", + client_ops->type, client_ops->name); + rc = -EALREADY; + } + mutex_unlock(&synx_dev->vtbl_lock); + + return rc; +} +EXPORT_SYMBOL(synx_register_ops); + +int synx_deregister_ops(const struct synx_register_params *params) +{ + struct synx_registered_ops *client_ops; + + if (!params || !params->name || + !synx_util_is_valid_bind_type(params->type)) { + pr_err("invalid params\n"); + return -EINVAL; + } + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[params->type]; + memset(client_ops, 0, sizeof(*client_ops)); + pr_info("deregistered bind ops for %s\n", + params->name); + mutex_unlock(&synx_dev->vtbl_lock); + + return 0; +} +EXPORT_SYMBOL(synx_deregister_ops); + +static int __init synx_init(void) +{ + int rc; + + pr_info("synx device initialization start\n"); + + synx_dev = kzalloc(sizeof(*synx_dev), GFP_KERNEL); + if (!synx_dev) + return -ENOMEM; + + rc = alloc_chrdev_region(&synx_dev->dev, 0, 1, SYNX_DEVICE_NAME); + if (rc < 0) { + pr_err("region allocation failed\n"); + goto alloc_fail; + } + + cdev_init(&synx_dev->cdev, &synx_fops); + synx_dev->cdev.owner = THIS_MODULE; + rc = cdev_add(&synx_dev->cdev, synx_dev->dev, 1); + if (rc < 0) { + pr_err("device registation failed\n"); + goto reg_fail; + } + + synx_dev->class = class_create(THIS_MODULE, SYNX_DEVICE_NAME); + device_create(synx_dev->class, NULL, synx_dev->dev, + NULL, SYNX_DEVICE_NAME); + + synx_dev->work_queue = alloc_workqueue(SYNX_WORKQUEUE_NAME, + WQ_HIGHPRI | WQ_UNBOUND, SYNX_WORKQUEUE_THREADS); + if (!synx_dev->work_queue) { + pr_err("high priority work queue creation failed\n"); + rc = -EINVAL; + goto fail; + } + + mutex_init(&synx_dev->dev_table_lock); + mutex_init(&synx_dev->vtbl_lock); + synx_dev->dma_context = dma_fence_context_alloc(1); + /* zero session id not allowed */ + set_bit(0, synx_dev->bitmap); + synx_dev->debugfs_root = synx_init_debugfs_dir(synx_dev); + + pr_info("synx device initialization success\n"); + + return 0; + +fail: + device_destroy(synx_dev->class, synx_dev->dev); + class_destroy(synx_dev->class); +reg_fail: + unregister_chrdev_region(synx_dev->dev, 1); +alloc_fail: + kfree(synx_dev); + return rc; +} + +static void __exit synx_exit(void) +{ + struct error_node *err_node, *err_node_tmp; + + device_destroy(synx_dev->class, synx_dev->dev); + class_destroy(synx_dev->class); + cdev_del(&synx_dev->cdev); + unregister_chrdev_region(synx_dev->dev, 1); + synx_remove_debugfs_dir(synx_dev); + /* release uncleared error nodes */ + list_for_each_entry_safe( + err_node, err_node_tmp, + &synx_dev->error_list, + node) { + list_del(&err_node->node); + kfree(err_node); + } + mutex_destroy(&synx_dev->dev_table_lock); + mutex_destroy(&synx_dev->error_lock); + kfree(synx_dev); +} + +module_init(synx_init); +module_exit(synx_exit); + +MODULE_DESCRIPTION("Global Synx Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/msm/synx/synx_api.h b/drivers/media/platform/msm/synx/synx_api.h new file mode 100644 index 000000000000..4a27da0b932e --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_api.h @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_API_H__ +#define __SYNX_API_H__ + +#include +#include + +typedef void (*synx_callback)(s32 sync_obj, int status, void *data); + +/** + * struct bind_operations - Function pointers that need to be defined + * to achieve bind functionality for external fence with synx obj + * + * @register_callback : Function to register with external sync object + * @deregister_callback : Function to deregister with external sync object + * @enable_signaling : Function to enable the signaling on the external + * sync object (optional) + * @signal : Function to signal the external sync object + */ +struct bind_operations { + int (*register_callback)(synx_callback cb_func, + void *userdata, s32 sync_obj); + int (*deregister_callback)(synx_callback cb_func, + void *userdata, s32 sync_obj); + int (*enable_signaling)(s32 sync_obj); + int (*signal)(s32 sync_obj, u32 status); +}; + +/** + * struct synx_register_params - External registration parameters + * + * @ops : Bind operations struct + * @name : External client name + * Only first 64 bytes are accepted, rest will be ignored + * @type : Synx external client type + */ +struct synx_register_params { + struct bind_operations ops; + char *name; + u32 type; +}; + +/** + * struct synx_session - Client session handle + * + * @client_id : Client id + */ +struct synx_session { + u32 client_id; +}; + +/** + * struct synx_initialization_params - Session params + * + * @name : Client session name + * Only first 64 bytes are accepted, rest will be ignored + */ +struct synx_initialization_params { + const char *name; +}; + +/** + * struct synx_create_params - Synx creation parameters + * + * @h_synx : Pointer to synx object handle (filled by function) + * @name : Optional parameter associating a name with the synx + * object for debug purposes + * Only first 64 bytes are accepted, + * rest will be ignored + */ +struct synx_create_params { + s32 *h_synx; + const char *name; +}; + +/** + * struct synx_export_params - Synx export parameters + * + * @h_synx : Synx object handle to export + * @secure_key : Pointer to Key generated for authentication + * (filled by the function) + */ +struct synx_export_params { + s32 h_synx; + u32 *secure_key; +}; + +/** + * struct synx_import_params - Synx import parameters + * + * @h_synx : Synx object handle to import + * @secure_key : Key to verify authenticity + * @new_h_synx : Pointer to newly created synx object + * (filled by the function) + * This handle should be used by importing + * process for all synx api operations + */ +struct synx_import_params { + s32 h_synx; + u32 secure_key; + s32 *new_h_synx; +}; + +/* Kernel APIs */ + +/* @brief: Register operations for external synchronization + * + * Register with synx for enabling external synchronization through bind + * + * @param params : Pointer to register params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params are invalid. + * -ENOMEM will be returned if bind ops cannot be registered due to + * insufficient memory. + * -EALREADY will be returned if type already in use + */ +int synx_register_ops(const struct synx_register_params *params); + +/** + * @brief: De-register external synchronization operations + * + * @param params : Pointer to register params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if record not found. + */ +int synx_deregister_ops(const struct synx_register_params *params); + +/** + * @brief: Initializes a new client session + * + * Create a new client and provides access to the synx framework + * + * @param session_id : Pointer to client session id (filled by function) + * @param params : Pointer to session init params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params is not valid. + * -ENOMEM will be returned if the kernel can't allocate space for + * new client + */ +int synx_initialize(struct synx_session *session_id, + struct synx_initialization_params *params); + +/** + * @brief: Destroys the client session + * + * The client session is destroyed and all synx objects owned + * by the client are cleaned up. + * + * @param session_id : Client session id + * + * @return Status of operation. Zero in case of success. + */ +int synx_uninitialize(struct synx_session session_id); + +/** + * @brief: Creates a synx object + * + * Creates a new synx obj and returns the handle to client + * + * @param session_id : Client session id + * @param params : Pointer to create params + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if params were invalid. + * -ENOMEM will be returned if the kernel can't allocate space for + * synx object. + */ +int synx_create(struct synx_session session_id, + struct synx_create_params *params); + +/** + * @brief: Registers a callback with a synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param cb_func : Pointer to callback to be registered + * @param userdata : Opaque pointer passed back with callback + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if userdata is invalid. + * -ENOMEM will be returned if cb_func is invalid. + * + */ +int synx_register_callback(struct synx_session session_id, + s32 h_synx, synx_callback cb_func, void *userdata); + +/** + * @brief: De-registers a callback with a synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param cb_func : Pointer to callback to be de-registered + * @param userdata : Opaque pointer passed back with callback + * @param cancel_cb_func : Pointer to callback to ack de-registration + * + * @return Status of operation. Zero in case of success. + * -EINVAL will be returned if userdata is invalid. + * -ENOMEM will be returned if cb_func is invalid. + */ +int synx_deregister_callback(struct synx_session session_id, + s32 h_synx, + synx_callback cb_func, + void *userdata, + synx_callback cancel_cb_func); + +/** + * @brief: Signals a synx object with the status argument. + * + * This function will signal the synx object referenced by h_synx + * and invoke any external binding synx objs. + * The status parameter will indicate whether the entity + * performing the signaling wants to convey an error case or a success case. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param status : Status of signaling. + * Value : SYNX_STATE_SIGNALED_SUCCESS or + * SYNX_STATE_SIGNALED_ERROR. + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_signal(struct synx_session session_id, s32 h_synx, u32 status); + +/** + * @brief: Merges multiple synx objects + * + * This function will merge multiple synx objects into a synx group. + * + * @param session_id : Client session id + * @param h_synxs : Pointer to a block of synx handles to be merged + * @param num_objs : Number of synx objs in the block + * @param h_merged_obj : Merged synx object handle (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_merge(struct synx_session session_id, + s32 *h_synxs, u32 num_objs, s32 *h_merged_obj); + +/** + * @brief: Waits for a synx object synchronously + * + * Does a wait on the synx object identified by h_synx for a maximum + * of timeout_ms milliseconds. Must not be called from interrupt context as + * this API can sleep. Should be called from process context only. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle to be waited upon + * @param timeout_ms : Timeout in ms + * + * @return 0 upon success, -EINVAL if synx object is in bad state or arguments + * are invalid, -ETIMEDOUT if wait times out. + */ +int synx_wait(struct synx_session session_id, s32 h_synx, u64 timeout_ms); + +/** + * @brief: Binds synx object with external sync + * + * Binding synx objects with supported external sync object will unify them + * in a way that if one of them signals, the other ones is signaled as well + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param external_sync : External synx descriptor to bind to object + * + * @return 0 upon success, -EINVAL if synx object is in bad state or arguments + * are invalid. + */ +int synx_bind(struct synx_session session_id, s32 h_synx, + struct synx_external_desc external_sync); + +/** + * @brief: Returns the status of the synx object + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * + * @return Status of the synx object + */ +int synx_get_status(struct synx_session session_id, s32 h_synx); + +/** + * @brief: Adds to the reference count of a synx object + * + * When a synx object is created, the refcount will be 1. Every + * additional refcount requires separate release of object handle. + * + * @param session_id : Client session id + * @param h_synx : Synx object handle + * @param count : Count to add to the refcount + * + * @return 0 upon success, -EINVAL if synx object is in bad state + */ +int synx_addrefcount(struct synx_session session_id, + s32 h_synx, s32 count); + +/** + * @brief: Imports (looks up) synx object from given handle and key + * + * The given ID should have been exported by another client + * and provided with key. + * + * @param session_id : Client session id + * @param params : Pointer to import params + * + * @return 0 upon success, -EINVAL if synx object is bad state + */ +int synx_import(struct synx_session session_id, + struct synx_import_params *params); + +/** + * @brief: Exports a synx object and returns a key + * + * The synx object handle and given key may be passed to other + * clients to be imported. + * + * @param session_id : Client session id + * @param params : Pointer to export params + * + * @return 0 upon success, -EINVAL if the ID is bad + */ +int synx_export(struct synx_session session_id, + struct synx_export_params *params); + +/** + * @brief: Release the synx object + + * Decrements refcount of a synx object by 1, and destroys it + * if becomes 0 + * + * @param session_id : Client session id + * @param h_synx : Synx object handle to be destroyed + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_release(struct synx_session session_id, s32 h_synx); + +#endif /* __SYNX_API_H__ */ diff --git a/drivers/media/platform/msm/synx/synx_debugfs.c b/drivers/media/platform/msm/synx/synx_debugfs.c new file mode 100644 index 000000000000..c4cc3a96c719 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_debugfs.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "synx_api.h" +#include "synx_debugfs.h" +#include "synx_util.h" + +#define MAX_DBG_BUF_SIZE (36 * SYNX_MAX_OBJS) + +struct dentry *my_direc; +static const char delim[] = ","; +int columns = NAME_COLUMN | ID_COLUMN | + BOUND_COLUMN | STATE_COLUMN | ERROR_CODES; + +void populate_bound_rows( + struct synx_coredata *row, + char *cur, + char *end) +{ + int j; + + for (j = 0; j < row->num_bound_synxs; + j++) { + cur += scnprintf(cur, end - cur, + "\n\tID: %d", + row->bound_synxs[j].external_desc.id[0]); + } +} +static ssize_t synx_table_read(struct file *file, + char *buf, + size_t count, + loff_t *ppos) +{ + + struct synx_device *dev = file->private_data; + struct error_node *err_node, *err_node_tmp; + struct synx_client *client; + struct synx_coredata *row; + char *dbuf, *cur, *end; + + int i = 0; + int j = 0; + int status = SYNX_STATE_INVALID; + ssize_t len = 0; + + dbuf = kzalloc(MAX_DBG_BUF_SIZE, GFP_KERNEL); + if (!dbuf) + return -ENOMEM; + + cur = dbuf; + end = cur + MAX_DBG_BUF_SIZE; + if (columns & NAME_COLUMN) + cur += scnprintf(cur, end - cur, "| Name |"); + if (columns & ID_COLUMN) + cur += scnprintf(cur, end - cur, "| ID |"); + if (columns & BOUND_COLUMN) + cur += scnprintf(cur, end - cur, "| Bound |"); + if (columns & STATE_COLUMN) + cur += scnprintf(cur, end - cur, "| Status |"); + cur += scnprintf(cur, end - cur, "\n"); + + for (j = 0; j < SYNX_MAX_CLIENTS; j++) { + struct synx_session session; + + session.client_id = j; + client = synx_get_client(session); + if (!client) + continue; + + cur += scnprintf(cur, end - cur, + "=============== session %08u ===============\n", + client->id); + for (i = 0; i < SYNX_MAX_OBJS; i++) { + row = synx_util_acquire_object(client, i); + if (!row) + continue; + + spin_lock_bh(&row->lock); + if (columns & NAME_COLUMN) + cur += scnprintf(cur, end - cur, + "|%10s|", row->name); + if (columns & ID_COLUMN) + cur += scnprintf(cur, end - cur, + "|%10d|", i); + if (columns & BOUND_COLUMN) + cur += scnprintf(cur, end - cur, + "|%11d|", row->num_bound_synxs); + if (columns & STATE_COLUMN) { + status = + synx_util_get_object_status_locked(row); + cur += scnprintf(cur, end - cur, + "|%10d|", status); + } + if ((columns & BOUND_COLUMN) && + (row->num_bound_synxs > 0)) { + cur += scnprintf( + cur, end - cur, "\nBound synx: "); + populate_bound_rows(row, + cur, + end); + } + spin_unlock_bh(&row->lock); + cur += scnprintf(cur, end - cur, "\n"); + synx_util_release_object(client, i); + } + cur += scnprintf(cur, end - cur, + "\n================================================\n\n"); + synx_put_client(client); + } + + if (columns & ERROR_CODES && !list_empty( + &dev->error_list)) { + cur += scnprintf( + cur, end - cur, "\nError(s): "); + + mutex_lock(&dev->error_lock); + list_for_each_entry_safe( + err_node, err_node_tmp, + &dev->error_list, + node) { + if (err_node->timestamp != NULL) { + cur += scnprintf(cur, end - cur, + "\n\tTime: %s - ID: %d - Code: %d", + err_node->timestamp, + err_node->h_synx, + err_node->error_code); + } + list_del(&err_node->node); + kfree(err_node); + } + mutex_unlock(&dev->error_lock); + } + + len = simple_read_from_buffer(buf, count, ppos, + dbuf, cur - dbuf); + kfree(dbuf); + return len; +} + +static ssize_t synx_table_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + char *ptr; + char *kbuffer = kzalloc(48, GFP_KERNEL); + int stat = -1; + + if (!kbuffer) + return -ENOMEM; + stat = copy_from_user(kbuffer, buf, 48); + if (stat != 0) { + kfree(kbuffer); + return -EFAULT; + } + while ((ptr = strsep(&kbuffer, delim)) != NULL) { + ptr += '\0'; + if (strcmp(ptr, "bound\n") == 0) + columns = columns ^ BOUND_COLUMN; + else if (strcmp(ptr, "name\n") == 0) + columns = columns ^ NAME_COLUMN; + else if (strcmp(ptr, "synxid\n") == 0) + columns = columns ^ ID_COLUMN; + else if (strcmp(ptr, "status\n") == 0) + columns = columns ^ STATE_COLUMN; + else if (strcmp(ptr, "errors\n") == 0) + columns = columns ^ ERROR_CODES; + } + kfree(kbuffer); + return count; +} + +static const struct file_operations synx_table_fops = { + .owner = THIS_MODULE, + .read = synx_table_read, + .write = synx_table_write, + .open = simple_open, +}; + +struct dentry *synx_init_debugfs_dir(struct synx_device *dev) +{ + struct dentry *dir = NULL; + + dir = debugfs_create_dir("synx_debug", NULL); + + if (!dir) { + pr_debug("Failed to create debugfs for synx\n"); + return NULL; + } + if (!debugfs_create_file("synx_table", + 0644, dir, dev, &synx_table_fops)) { + pr_debug("Failed to create debugfs file for synx\n"); + return NULL; + } + mutex_init(&dev->error_lock); + INIT_LIST_HEAD(&dev->error_list); + return dir; +} + +void synx_remove_debugfs_dir(struct synx_device *dev) +{ + debugfs_remove_recursive(dev->debugfs_root); +} diff --git a/drivers/media/platform/msm/synx/synx_debugfs.h b/drivers/media/platform/msm/synx/synx_debugfs.h new file mode 100644 index 000000000000..4d5091ddf8f1 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_debugfs.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#include +#include + +#include "synx_private.h" + +/** + * @brief: Initializes debugfs + * + * @param dev : Pointer to synx device structure + */ +struct dentry *synx_init_debugfs_dir(struct synx_device *dev); + +/** + * @brief: Removes debugfs + * + * @param dev : Pointer to synx device structure + */ +void synx_remove_debugfs_dir(struct synx_device *dev); + +#define NAME_COLUMN 0x0001 +#define ID_COLUMN 0x0002 +#define BOUND_COLUMN 0x0004 +#define STATE_COLUMN 0x0008 +#define ERROR_CODES 0x8000 diff --git a/drivers/media/platform/msm/synx/synx_private.h b/drivers/media/platform/msm/synx/synx_private.h new file mode 100644 index 000000000000..283227016e06 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_private.h @@ -0,0 +1,303 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_PRIVATE_H__ +#define __SYNX_PRIVATE_H__ + +#include +#include +#include +#include +#include + +#define SYNX_NAME "synx" +#define SYNX_WORKQUEUE_NAME "hiprio_synx_work_queue" +#define SYNX_WORKQUEUE_THREADS 1 +#define SYNX_MAX_NUM_BINDINGS 8 +#define SYNX_DEVICE_NAME "synx_device" + +#define SYNX_CLIENT_HANDLE_SHIFT 8 +#define SYNX_SECURE_KEY_SHIFT 16 + +#define MAX_TIMESTAMP_SIZE 32 +#define SYNX_OBJ_NAME_LEN 64 +#define SYNX_MAX_CLIENTS (1UL< +#include + +#include "synx_api.h" +#include "synx_util.h" + +extern void synx_external_callback(s32 sync_obj, int status, void *data); + +bool synx_util_is_valid_bind_type(u32 type) +{ + if (type < SYNX_MAX_BIND_TYPES) + return true; + + return false; +} + +u32 synx_util_get_object_type(struct synx_coredata *synx_obj) +{ + if (!synx_obj) + return 0; + + return synx_obj->type; +} + +bool synx_util_is_merged_object(struct synx_coredata *synx_obj) +{ + if (synx_util_get_object_type(synx_obj) + & SYNX_OBJ_TYPE_MERGED) + return true; + return false; +} + +struct dma_fence *synx_util_get_fence(struct synx_coredata *synx_obj) +{ + if (!synx_obj) + return NULL; + + if (synx_util_is_merged_object(synx_obj)) + return synx_obj->merged_fence; + + return &synx_obj->fence; +} + +void synx_util_get_object(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return; + + fence = synx_util_get_fence(synx_obj); + dma_fence_get(fence); +} + +void synx_util_put_object(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return; + + fence = synx_util_get_fence(synx_obj); + dma_fence_put(fence); +} + +int synx_util_init_coredata(struct synx_coredata *synx_obj, + struct synx_create_params *params, + struct dma_fence_ops *ops) +{ + if (!synx_obj || !params || !ops) { + pr_err("invalid arguments\n"); + return -EINVAL; + } + + spin_lock_init(&synx_obj->lock); + dma_fence_init(&synx_obj->fence, ops, + &synx_obj->lock, synx_dev->dma_context, 1); + + synx_obj->type = SYNX_OBJ_TYPE_LOCAL; + INIT_LIST_HEAD(&synx_obj->reg_cbs_list); + if (params->name) + strlcpy(synx_obj->name, params->name, sizeof(synx_obj->name)); + + return 0; +} + +int synx_util_init_group_coredata(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 num_objs) +{ + struct dma_fence_array *array; + + if (!synx_obj) + return -EINVAL; + + array = dma_fence_array_create(num_objs, fences, + synx_dev->dma_context, 1, false); + if (!array) + return -EINVAL; + + synx_obj->merged_fence = &array->base; + synx_obj->type = SYNX_OBJ_TYPE_LOCAL | SYNX_OBJ_TYPE_MERGED; + INIT_LIST_HEAD(&synx_obj->reg_cbs_list); + + return 0; +} + +void synx_util_object_destroy(struct synx_coredata *synx_obj) +{ + int rc; + u32 i; + s32 sync_id; + u32 type; + struct synx_cb_data *synx_cb, *synx_cb_temp; + struct synx_bind_desc *bind_desc; + struct bind_operations *bind_ops; + struct synx_external_data *data; + + /* clear all the undispatched callbacks */ + list_for_each_entry_safe(synx_cb, + synx_cb_temp, &synx_obj->reg_cbs_list, node) { + pr_err("[sess: %u] cleaning up callback\n", + synx_cb->session_id.client_id); + list_del_init(&synx_cb->node); + kfree(synx_cb); + } + + for (i = 0; i < synx_obj->num_bound_synxs; i++) { + bind_desc = &synx_obj->bound_synxs[i]; + sync_id = bind_desc->external_desc.id[0]; + type = bind_desc->external_desc.type; + data = bind_desc->external_data; + bind_ops = synx_util_get_bind_ops(type); + rc = bind_ops->deregister_callback( + synx_external_callback, data, sync_id); + if (rc < 0) + pr_err("de-registration fail id: %d, type: %u, err: %d\n", + sync_id, type, rc); + /* + * release the memory allocated for external data. + * It is safe to release this memory as external cb + * has been already deregistered before this. + * even if deregistration fails, it implies the + * external fence might have been cleaned up already + * or in some bad state. Have to release cb data + * at this point or else will lead to memory leak. + */ + kfree(data); + } + + kfree(synx_obj); + pr_debug("cleaned up synx object fence\n"); +} + +int synx_util_init_handle(struct synx_client *client, + struct synx_coredata *synx_obj, + long *h_synx) +{ + bool bit; + long idx; + struct synx_handle_coredata *synx_data; + + if (!client || !synx_obj) + return -EINVAL; + + do { + idx = find_first_zero_bit(client->bitmap, SYNX_MAX_OBJS); + if (idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] free index not available\n", + client->id); + return -ENOMEM; + } + bit = test_and_set_bit(idx, client->bitmap); + } while (bit); + + synx_data = &client->synx_table[idx]; + synx_data->client = client; + synx_data->id = idx; + synx_data->synx_obj = synx_obj; + kref_init(&synx_data->internal_refcount); + + *h_synx = idx; + return 0; +} + +int synx_util_activate(struct synx_coredata *synx_obj) +{ + struct dma_fence *fence; + + if (!synx_obj) + return -EINVAL; + + fence = synx_util_get_fence(synx_obj); + /* move synx to ACTIVE state and register cb for merged object */ + dma_fence_enable_sw_signaling(fence); + return 0; +} + +static u32 synx_util_get_references(struct synx_coredata *synx_obj) +{ + u32 count = 0; + u32 i = 0; + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + + fence = synx_util_get_fence(synx_obj); + /* obtain dma fence reference */ + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return 0; + + for (i = 0; i < array->num_fences; i++) + dma_fence_get(array->fences[i]); + + count = array->num_fences; + } else { + dma_fence_get(fence); + count = 1; + } + + return count; +} + +static void synx_util_put_references(struct synx_coredata *synx_obj) +{ + u32 i = 0; + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + + fence = synx_util_get_fence(synx_obj); + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return; + + for (i = 0; i < array->num_fences; i++) + dma_fence_put(array->fences[i]); + } else { + dma_fence_put(fence); + } +} + +static u32 synx_util_add_fence(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 idx) +{ + struct dma_fence *fence; + struct dma_fence_array *array = NULL; + u32 i = 0; + + fence = synx_util_get_fence(synx_obj); + if (dma_fence_is_array(fence)) { + array = to_dma_fence_array(fence); + if (!array) + return 0; + + for (i = 0; i < array->num_fences; i++) + fences[idx+i] = array->fences[i]; + + return array->num_fences; + } + + fences[idx] = fence; + return 1; +} + +static u32 synx_util_remove_duplicates(struct dma_fence **arr, u32 num) +{ + int i, j; + u32 wr_idx = 1; + + if (!arr) { + pr_err("invalid input array\n"); + return 0; + } + + for (i = 1; i < num; i++) { + for (j = 0; j < wr_idx ; j++) { + if (arr[i] == arr[j]) { + /* release reference obtained for duplicate */ + pr_debug("releasing duplicate reference\n"); + dma_fence_put(arr[i]); + break; + } + } + if (j == wr_idx) + arr[wr_idx++] = arr[i]; + } + + return wr_idx; +} + +s32 synx_util_merge_error(struct synx_client *client, + s32 *h_synxs, + u32 num_objs) +{ + u32 i = 0; + struct synx_coredata *synx_obj; + + if (!client || !h_synxs) + return -EINVAL; + + for (i = 0; i < num_objs; i++) { + synx_obj = synx_util_acquire_object(client, h_synxs[i]); + if (!synx_obj) { + pr_err("[sess: %u] invalid handle %d in merge cleanup\n", + client->id, h_synxs[i]); + continue; + } + /* release all references obtained during merge validatation */ + synx_util_put_references(synx_obj); + synx_util_release_object(client, h_synxs[i]); + } + + return 0; +} + +int synx_util_validate_merge(struct synx_client *client, + s32 *h_synxs, + u32 num_objs, + struct dma_fence ***fence_list, + u32 *fence_cnt) +{ + u32 count = 0; + u32 i = 0; + struct synx_coredata **synx_objs = NULL; + struct dma_fence **fences = NULL; + + if (num_objs <= 1) { + pr_err("single object merge is not allowed\n"); + return -EINVAL; + } + + synx_objs = kcalloc(num_objs, sizeof(*synx_objs), GFP_KERNEL); + if (!synx_objs) + return -ENOMEM; + + for (i = 0; i < num_objs; i++) { + synx_objs[i] = synx_util_acquire_object(client, h_synxs[i]); + if (!synx_objs) { + pr_err("[sess: %u] invalid handle %d in merge list\n", + client->id, h_synxs[i]); + *fence_cnt = i; + return -EINVAL; + } + count += synx_util_get_references(synx_objs[i]); + } + + fences = kcalloc(count, sizeof(*fences), GFP_KERNEL); + if (!fences) { + *fence_cnt = num_objs; + return -ENOMEM; + } + + /* memory will be released later in the invoking function */ + *fence_list = fences; + count = 0; + + for (i = 0; i < num_objs; i++) { + count += synx_util_add_fence(synx_objs[i], fences, count); + /* release the reference obtained earlier in the function */ + synx_util_release_object(client, h_synxs[i]); + } + + *fence_cnt = synx_util_remove_duplicates(fences, count); + kfree(synx_objs); + return 0; +} + +static u32 __fence_state(struct dma_fence *fence, bool locked) +{ + s32 status; + u32 state = SYNX_STATE_INVALID; + + if (!fence) { + pr_err("invalid dma fence addr\n"); + return SYNX_STATE_INVALID; + } + + if (locked) + status = dma_fence_get_status_locked(fence); + else + status = dma_fence_get_status(fence); + + /* convert fence status to synx state */ + switch (status) { + case 0: + state = SYNX_STATE_ACTIVE; + break; + case 1: + state = SYNX_STATE_SIGNALED_SUCCESS; + break; + case -SYNX_STATE_SIGNALED_CANCEL: + state = SYNX_STATE_SIGNALED_CANCEL; + break; + case -SYNX_STATE_FORCED_RELEASE: + state = SYNX_STATE_FORCED_RELEASE; + break; + case -SYNX_STATE_SIGNALED_ERROR: + default: + state = SYNX_STATE_SIGNALED_ERROR; + } + + return state; +} + +static u32 __fence_group_state(struct dma_fence *fence, bool locked) +{ + u32 i = 0; + u32 state = SYNX_STATE_INVALID; + struct dma_fence_array *array = NULL; + u32 intr, actv_cnt, sig_cnt, err_cnt; + + if (!fence) { + pr_err("invalid dma fence addr\n"); + return SYNX_STATE_INVALID; + } + + actv_cnt = sig_cnt = err_cnt = 0; + array = to_dma_fence_array(fence); + if (!array) + return SYNX_STATE_INVALID; + + for (i = 0; i < array->num_fences; i++) { + intr = __fence_state(array->fences[i], locked); + switch (intr) { + case SYNX_STATE_ACTIVE: + actv_cnt++; + break; + case SYNX_STATE_SIGNALED_SUCCESS: + sig_cnt++; + break; + default: + err_cnt++; + } + } + + pr_debug("group cnt stats act:%u, sig: %u, err: %u\n", + actv_cnt, sig_cnt, err_cnt); + + if (err_cnt) + state = SYNX_STATE_SIGNALED_ERROR; + else if (actv_cnt) + state = SYNX_STATE_ACTIVE; + else if (sig_cnt == array->num_fences) + state = SYNX_STATE_SIGNALED_SUCCESS; + + return state; +} + +/* + * WARN: Should not hold the synx spinlock when invoking + * this function. Use synx_fence_state_locked instead + */ +u32 synx_util_get_object_status(struct synx_coredata *synx_obj) +{ + u32 state; + struct dma_fence *fence; + + if (!synx_obj) + return SYNX_STATE_INVALID; + + fence = synx_util_get_fence(synx_obj); + if (synx_util_is_merged_object(synx_obj)) + state = __fence_group_state(fence, false); + else + state = __fence_state(fence, false); + + return state; +} + +/* use this for status check when holding on to metadata spinlock */ +u32 synx_util_get_object_status_locked(struct synx_coredata *synx_obj) +{ + u32 state; + struct dma_fence *fence; + + if (!synx_obj) + return SYNX_STATE_INVALID; + + fence = synx_util_get_fence(synx_obj); + if (synx_util_is_merged_object(synx_obj)) + state = __fence_group_state(fence, true); + else + state = __fence_state(fence, true); + + return state; +} + +struct synx_handle_coredata *synx_util_obtain_handle( + struct synx_client *client, + s32 h_synx) +{ + if (!client) { + pr_err("invalid session argument\n"); + return NULL; + } + + if (h_synx < 0 || h_synx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] invalid handle %d access\n", + client->id, h_synx); + return NULL; + } + + return &client->synx_table[h_synx]; +} + +struct synx_coredata *synx_util_acquire_object( + struct synx_client *client, s32 h_synx) +{ + struct synx_coredata *synx_obj = NULL; + struct synx_handle_coredata *synx_data = + synx_util_obtain_handle(client, h_synx); + + if (!synx_data) + return NULL; + + mutex_lock(&client->synx_table_lock[h_synx]); + synx_obj = synx_data->synx_obj; + if (synx_obj) { + synx_util_get_object(synx_obj); + kref_get(&synx_data->internal_refcount); + pr_debug("[sess: %u] acquired synx object for handle %d\n", + client->id, h_synx); + } + mutex_unlock(&client->synx_table_lock[h_synx]); + + return synx_obj; +} + +static void synx_util_destroy_handle(struct synx_handle_coredata *synx_data) +{ + long idx = synx_data->id; + struct synx_client *client = synx_data->client; + + if (synx_util_is_merged_object(synx_data->synx_obj)) { + pr_debug("cleaned up synx object fence\n"); + kfree(synx_data->synx_obj); + } + + memset(synx_data, 0, sizeof(*synx_data)); + clear_bit(idx, client->bitmap); + pr_debug("[sess: %u] handle %d destroyed\n", + client->id, idx); +} + +void synx_util_destroy_import_handle(struct kref *kref) +{ + struct synx_handle_coredata *synx_data = + container_of(kref, struct synx_handle_coredata, + import_refcount); + + pr_debug("[sess: %u] import handle cleanup for %d\n", + synx_data->client->id, synx_data->id); + + /* in case of pending internal references, abort clean up */ + if (kref_read(&synx_data->internal_refcount)) + return; + + synx_util_destroy_handle(synx_data); +} + +void synx_util_destroy_internal_handle(struct kref *kref) +{ + struct synx_handle_coredata *synx_data = + container_of(kref, struct synx_handle_coredata, + internal_refcount); + + pr_debug("[sess: %u] internal handle cleanup for %d\n", + synx_data->client->id, synx_data->id); + + /* in case of pending imports, abort clean up */ + if (kref_read(&synx_data->import_refcount)) + return; + + synx_util_destroy_handle(synx_data); +} + +void synx_util_release_object(struct synx_client *client, s32 h_synx) +{ + struct synx_handle_coredata *synx_data = + synx_util_obtain_handle(client, h_synx); + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + if (!synx_data) + return; + + mutex_lock(&client->synx_table_lock[h_synx]); + synx_obj = synx_data->synx_obj; + if (synx_obj) { + fence = synx_util_get_fence(synx_obj); + kref_put(&synx_data->internal_refcount, + synx_util_destroy_internal_handle); + dma_fence_put(fence); + } + mutex_unlock(&client->synx_table_lock[h_synx]); +} + +struct bind_operations *synx_util_get_bind_ops(u32 type) +{ + struct synx_registered_ops *client_ops; + + if (!synx_util_is_valid_bind_type(type)) + return NULL; + + mutex_lock(&synx_dev->vtbl_lock); + client_ops = &synx_dev->bind_vtbl[type]; + if (!client_ops->valid) { + mutex_unlock(&synx_dev->vtbl_lock); + return NULL; + } + mutex_unlock(&synx_dev->vtbl_lock); + + return &client_ops->ops; +} + +int synx_util_alloc_cb_entry(struct synx_client *client, + u32 *cb_idx) +{ + bool bit; + long idx; + struct synx_client_cb *cb; + + if (!client || !cb_idx) + return -EINVAL; + + do { + idx = find_first_zero_bit(client->cb_bitmap, SYNX_MAX_OBJS); + if (idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] free cb index not available\n", + client->id); + return -ENOMEM; + } + bit = test_and_set_bit(idx, client->cb_bitmap); + } while (bit); + + cb = &client->cb_table[idx]; + memset(cb, 0, sizeof(*cb)); + cb->idx = idx; + *cb_idx = idx; + pr_debug("[sess: %u] allocated cb index %u\n", client->id, idx); + return 0; +} + +int synx_util_update_cb_entry(struct synx_client *client, + void *data, + u32 cb_idx) +{ + struct synx_client_cb *cb; + + if (!client || !data || (cb_idx >= SYNX_MAX_OBJS)) + return -EINVAL; + + cb = &client->cb_table[cb_idx]; + cb->is_valid = true; + memcpy(&cb->kernel_cb, data, + sizeof(cb->kernel_cb)); + + pr_debug("[sess: %u] updated cb index %u\n", client->id, cb_idx); + return 0; +} + +void synx_util_callback_dispatch(struct synx_coredata *synx_obj, u32 status) +{ + struct synx_cb_data *synx_cb, *synx_cb_temp; + + if (!synx_obj) { + pr_err("invalid arguments\n"); + return; + } + + list_for_each_entry_safe(synx_cb, + synx_cb_temp, &synx_obj->reg_cbs_list, node) { + synx_cb->status = status; + list_del_init(&synx_cb->node); + queue_work(synx_dev->work_queue, + &synx_cb->cb_dispatch); + pr_debug("dispatched callback\n"); + } +} + +void synx_util_cb_dispatch(struct work_struct *cb_dispatch) +{ + struct synx_cb_data *synx_cb = + container_of(cb_dispatch, struct synx_cb_data, cb_dispatch); + struct synx_client *client; + struct synx_client_cb *cb; + struct synx_kernel_payload *payload; + + client = synx_get_client(synx_cb->session_id); + if (!client) { + pr_err("invalid session data %u in cb payload\n", + synx_cb->session_id); + goto free; + } + + if (synx_cb->idx >= SYNX_MAX_OBJS) { + pr_err("[sess: %u] invalid cb index\n", + client->id); + goto fail; + } + + cb = &client->cb_table[synx_cb->idx]; + if (!cb->is_valid) { + pr_err("invalid cb payload\n"); + goto fail; + } + + payload = &cb->kernel_cb; + pr_debug("[sess: %u] kernel cb dispatch for handle %d\n", + client->id, payload->h_synx); + + /* dispatch kernel callback */ + payload->cb_func(payload->h_synx, + synx_cb->status, + payload->data); + +fail: + synx_put_client(client); +free: + pr_debug("released synx_cb_data memory\n"); + kfree(synx_cb); +} + +struct synx_coredata *synx_util_import_object(struct synx_import_params *params) +{ + u16 key; + s32 h_synx; + u32 secure_key; + struct synx_session ex_session_id; + struct synx_client *ex_client; + struct synx_handle_coredata *synx_data; + struct synx_coredata *synx_obj = NULL; + + if (!params) + return NULL; + + h_synx = params->h_synx; + secure_key = params->secure_key; + + ex_session_id.client_id = secure_key & SYNX_CLIENT_HANDLE_MASK; + key = (secure_key >> SYNX_CLIENT_HANDLE_SHIFT) & SYNX_SECURE_KEY_MASK; + + /* get the client exporting the synx handle */ + ex_client = synx_get_client(ex_session_id); + if (!ex_client) { + pr_err("sess: %u invalid import handle %d and/or key %u\n", + ex_session_id.client_id, h_synx, secure_key); + return NULL; + } + + synx_data = synx_util_obtain_handle(ex_client, h_synx); + if (!synx_data) { + pr_err("[sess: %u] invalid import handle %d\n", + ex_client->id, h_synx); + goto fail; + } + + mutex_lock(&ex_client->synx_table_lock[h_synx]); + /* need to check whether import is accounted for in import_refcount */ + if (synx_data->synx_obj && + key && (key == synx_data->key) && + kref_read(&synx_data->import_refcount)) { + if (synx_util_is_merged_object(synx_data->synx_obj)) { + /* + * need to copy the synx coredata for merged object + * as we need to clean up the coredata on release + * since fence array cannot be registered with + * synx_fence_release function. + */ + synx_obj = kzalloc(sizeof(*synx_obj), GFP_KERNEL); + if (synx_obj) + memcpy(synx_obj, synx_data->synx_obj, + sizeof(*synx_obj)); + } else { + synx_obj = synx_data->synx_obj; + } + /* release the reference obtained during export */ + kref_put(&synx_data->import_refcount, + synx_util_destroy_import_handle); + pr_debug("sess: %u handle %d import successful\n", + ex_client->id, h_synx); + } + mutex_unlock(&ex_client->synx_table_lock[h_synx]); + +fail: + synx_put_client(ex_client); + return synx_obj; +} + +int synx_util_export_object(struct synx_client *client, + struct synx_export_params *params) +{ + int rc = 0; + u16 key = 0; + s32 h_synx; + struct synx_handle_coredata *synx_data; + + if (!params || !params->secure_key) + return -EINVAL; + + h_synx = params->h_synx; + synx_data = synx_util_obtain_handle(client, h_synx); + if (!synx_data) { + pr_err("[sess: %u] invalid export handle %d\n", + client->id, h_synx); + rc = -EINVAL; + goto fail; + } + + mutex_lock(&client->synx_table_lock[h_synx]); + /* need to check whether import is accounted for in import_refcount */ + if (synx_data->synx_obj) { + /* generate the key on the first export of the handle */ + if (!synx_data->key) { + kref_init(&synx_data->import_refcount); + while (!key) + get_random_bytes(&key, sizeof(key)); + synx_data->key = key; + pr_debug("[sess: %u] import refcount initialized\n", + client->id); + } else { + kref_get(&synx_data->import_refcount); + key = synx_data->key; + } + /* + * to make sure the synx is not lost if the process + * dies or synx is released before any other process + * gets a chance to import it. + * The assumption is that an import will match this + * and account for the extra reference. Otherwise, + * this will be released upon client uninit. + */ + synx_util_get_object(synx_data->synx_obj); + + /* encode the key and the client id to user provided variable */ + *params->secure_key = key; + *params->secure_key <<= SYNX_CLIENT_HANDLE_SHIFT; + *params->secure_key |= client->id; + + pr_debug("[sess: %u] handle %d export successful with key %u\n", + client->id, params->h_synx, *params->secure_key); + } else { + pr_err("[sess: %u] invalid export object %d\n", + client->id, h_synx); + rc = -EINVAL; + } + mutex_unlock(&client->synx_table_lock[h_synx]); + +fail: + return rc; +} + +struct synx_client *synx_get_client(struct synx_session session_id) +{ + struct synx_client_metadata *client_metadata; + struct synx_client *client; + u32 id = session_id.client_id; + + if (id >= SYNX_MAX_CLIENTS) { + pr_err("%s: invalid session handle %u from pid: %d\n", + __func__, id, current->pid); + return NULL; + } + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[id]; + client = client_metadata->client; + if (client) + kref_get(&client_metadata->refcount); + else + pr_err("session %u not available, pid: %d\n", + id, current->pid); + mutex_unlock(&synx_dev->dev_table_lock); + + return client; +} + +static void synx_client_destroy(struct kref *kref) +{ + u32 i; + struct synx_client_metadata *client_metadata = + container_of(kref, struct synx_client_metadata, refcount); + struct synx_client *client = client_metadata->client; + struct synx_handle_coredata *synx_data; + struct synx_coredata *synx_obj; + struct dma_fence *fence; + + /* TODO:can create a thread and handle it */ + + /* go over all the remaining synx obj handles and clear them */ + for (i = 0; i < SYNX_MAX_OBJS; i++) { + synx_data = &client->synx_table[i]; + synx_obj = synx_data->synx_obj; + /* + * cleanup unreleased references by the client + * Note: it is only safe to access synx_obj if + * there are refcounts (internal, import) + * remaining in the current handle, as it + * gurantees corresponding reference to fence. + */ + if (synx_obj) { + fence = synx_util_get_fence(synx_obj); + while (kref_read(&synx_data->internal_refcount)) { + kref_put(&synx_data->internal_refcount, + synx_util_destroy_internal_handle); + dma_fence_put(fence); + } + while (kref_read(&synx_data->import_refcount)) { + kref_put(&synx_data->import_refcount, + synx_util_destroy_import_handle); + dma_fence_put(fence); + } + } + mutex_destroy(&client->synx_table_lock[i]); + } + mutex_destroy(&client->event_q_lock); + memset(client_metadata, 0, sizeof(*client_metadata)); + clear_bit(client->id, synx_dev->bitmap); + + pr_debug("[sess: %u] session destroyed %s\n", + client->id, client->name); + kfree(client); +} + +void synx_put_client(struct synx_client *client) +{ + struct synx_client_metadata *client_metadata; + + if (!client) { + pr_err("%s: invalid client ptr\n", __func__); + return; + } + + if (client->id >= SYNX_MAX_CLIENTS) { + pr_err("%s: session id %u invalid from pid: %d\n", + __func__, client->id, current->pid); + return; + } + + mutex_lock(&synx_dev->dev_table_lock); + client_metadata = &synx_dev->client_table[client->id]; + if (client_metadata->client == client) + /* should not reference client after this call */ + kref_put(&client_metadata->refcount, synx_client_destroy); + else + pr_err("%s: invalid session %u from pid: %d\n", + __func__, client->id, current->pid); + mutex_unlock(&synx_dev->dev_table_lock); +} + +void synx_util_generate_timestamp(char *timestamp, size_t size) +{ + struct timespec64 tv; + struct tm tm; + + ktime_get_real_ts64(&tv); + time64_to_tm(tv.tv_sec, 0, &tm); + snprintf(timestamp, size, "%02d-%02d %02d:%02d:%02d", + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec); +} + +void synx_util_log_error(u32 client_id, s32 h_synx, s32 err) +{ + struct error_node *err_node; + + if (!synx_dev->debugfs_root) + return; + + err_node = kzalloc(sizeof(*err_node), GFP_KERNEL); + if (!err_node) + return; + + err_node->client_id = client_id; + err_node->error_code = err; + err_node->h_synx = h_synx; + synx_util_generate_timestamp(err_node->timestamp, + sizeof(err_node->timestamp)); + mutex_lock(&synx_dev->error_lock); + list_add(&err_node->node, + &synx_dev->error_list); + mutex_unlock(&synx_dev->error_lock); +} diff --git a/drivers/media/platform/msm/synx/synx_util.h b/drivers/media/platform/msm/synx/synx_util.h new file mode 100644 index 000000000000..39706dd5de98 --- /dev/null +++ b/drivers/media/platform/msm/synx/synx_util.h @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SYNX_UTIL_H__ +#define __SYNX_UTIL_H__ + +#include "synx_private.h" + +extern struct synx_device *synx_dev; + +/** + * @brief: Function to check if the external obj type is valid + * + * @param type : External sync object type + * + * @return True if valid. False otherwise + */ +bool synx_util_is_valid_bind_type(u32 type); + +/** + * @brief: Function to get the synx object type + * + * @param synx_obj : Pointer to synx object + * + * @return Synx object type. Zero if invalid + */ +u32 synx_util_get_object_type(struct synx_coredata *synx_obj); + +/** + * @brief: Function to check if the synx object is a composite (merged) + * + * @param synx_obj : Pointer to synx object + * + * @return True if merged object. False otherwise + */ +bool synx_util_is_merged_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to get the dma fence backing the synx object + * + * @param synx_obj : Pointer to synx object + * + * @return Dma fence. NULL if synx_obj is invalid + */ +struct dma_fence *synx_util_get_fence( + struct synx_coredata *synx_obj); + +/** + * @brief: Function to acquire reference to synx object + * + * This acquires additional reference to the synx object and should be + * released using synx_util_put_object + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_get_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to release reference to synx object + * + * This releases reference to the synx object and should be + * matched with synx_util_get_object + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_put_object(struct synx_coredata *synx_obj); + +/** + * @brief: Function to initialize synx object + * + * It also initializes the dma fence. This should be called only + * for individual objects. + * + * @param synx_obj : Pointer to synx object + * @param params : Pointer to create params + * @param ops : Dma fence ops required for fence initialization + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_coredata(struct synx_coredata *synx_obj, + struct synx_create_params *params, + struct dma_fence_ops *ops); + +/** + * @brief: Function to initialize a merged synx object. + * + * It also initializes dma fence array. + * + * @param synx_obj : Pointer to synx object + * @param fences : Array of fence objects which will merged + * or grouped together to a fence array + * @param num_objs : Number of fence objects in the array + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_group_coredata(struct synx_coredata *synx_obj, + struct dma_fence **fences, + u32 num_objs); + +/** + * @brief: Function to cleanup the synx object + * + * This function will be invoked when the dma fence ref count reaches 0. + * + * @param synx_obj : Pointer to synx object + */ +void synx_util_object_destroy(struct synx_coredata *synx_obj); + +/** + * @brief: Function to allocate synx_client_cb entry from cb table + * + * @param client : Pointer to client session info + * @param cb_idx : Allocated index (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_alloc_cb_entry(struct synx_client *client, + u32 *cb_idx); + +/** + * @brief: Function to allocate synx_client_cb entry from cb table + * + * @param client : Pointer to client session info + * @param data : Opaque payload data + * @param cb_idx : Callback table index + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_update_cb_entry(struct synx_client *client, + void *data, + u32 cb_idx); + +/** + * @brief: Function to initialize synx object handle for the client + * + * @param client : Pointer to client session info + * @param synx_obj : Pointer to synx object + * @param h_synx : Pointer to synx object handle (filled by function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_init_handle(struct synx_client *client, + struct synx_coredata *synx_obj, + long *h_synx); + +/** + * @brief: Function to activate the synx object. Moves the synx from INVALID + * state to ACTIVE state. + * + * @param synx_obj : Pointer to synx object + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_activate(struct synx_coredata *synx_obj); + +/** + * @brief: Function to queue all the registered callbacks by clients for + * the synx object + * + * @param synx_obj : Pointer to synx object + * @param state : State of the object + */ +void synx_util_callback_dispatch(struct synx_coredata *synx_obj, u32 state); + +/** + * @brief: Function to dispatch client registered callback + * + * @param cb_dispatch : Pointer to work_struct that needs to be dispatched + */ +void synx_util_cb_dispatch(struct work_struct *cb_dispatch); + +/** + * @brief: Function to handle error during merging of synx objects + * + * Removes the references added on the dma fence objects + * + * @param client : Pointer to client session + * @param h_synxs : Array of synx object handles to merge + * @param num_objs : Number of synx obects in the array + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +s32 synx_util_merge_error(struct synx_client *client, + s32 *h_synxs, + u32 num_objs); + +/** + * @brief: Function to validate synx merge arguments. + * + * It obtains the necessary references to the fence objects + * and also removes duplicates (if any). + * + * @param client : Pointer to client session + * @param h_synxs : Array of synx object handles to merge + * @param num_objs : Number of synx objects in the array + * @param fences : Address to a list of dma fence* array + * @param fence_cnt : Number of fence objects in the fences array + * (filled by the function) + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_validate_merge(struct synx_client *client, + s32 *h_synxs, + u32 num_objs, + struct dma_fence ***fences, + u32 *fence_cnt); + +/** + * @brief: Function to obtain the state of synx object + * + * The lock associated with the synx obj should not be + * held when invoking this function. + * Use synx_util_get_object_status_locked for state enquiry + * when holding lock + * + * @param synx_obj : Pointer to synx object + * + * @return Status of the synx object. + */ +u32 synx_util_get_object_status(struct synx_coredata *synx_obj); + +/** + * @brief: Function to obtain the state of synx object + * + * The lock associated with the synx obj should be held + * when invoking this function. + * Use synx_util_get_object_status for state enquiry + * when not holding lock + * + * @param synx_obj : Pointer to synx object + * + * @return Status of the synx object. + */ +u32 synx_util_get_object_status_locked(struct synx_coredata *synx_obj); + +/** + * @brief: Function to obtain the synx object metadata for the client + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + * + * @return Pointer to synx object metadata for the client. + * NULL if invalid synx handle. + */ +struct synx_handle_coredata *synx_util_obtain_handle( + struct synx_client *client, s32 h_synx); + +/** + * @brief: Function to acquire the synx object + * + * This function increments the reference count of the dma fence. + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + * + * @return Pointer to synx object. NULL if invalid synx handle. + */ +struct synx_coredata *synx_util_acquire_object( + struct synx_client *client, s32 h_synx); + +/** + * @brief: Function to release the synx object + * + * This function decrements the reference count of the dma fence and + * implicitly calls up the synx object cleanup if count reaches 0. + * Synx object should not be derefrenced in the function after + * calling this. + * + * @param client : Pointer to client session + * @param h_synx : Synx object handle + */ +void synx_util_release_object(struct synx_client *client, s32 h_synx); + +/** + * @brief: Function called implicitly when import refcount reaches zero + * + * @param kref : &synx_handle_coredata.import_refcount + */ +void synx_util_destroy_import_handle(struct kref *kref); + +/** + * @brief: Function called implicitly when internal refcount reaches zero + * + * @param kref : &synx_handle_coredata.internal_refcount + */ +void synx_util_destroy_internal_handle(struct kref *kref); + +/** + * @brief: Function to get the bind operations registered by external driver + * + * @param type : External sync type + * + * @return Bind operations. NULL if type is invalid + */ +struct bind_operations *synx_util_get_bind_ops(u32 type); + +/** + * @brief: Function to share the synx object to use by other clients + * + * Shares the synx object by acquiring additional reference to dma fence + * + * @param client : Pointer to client session + * @param params : Pointer to export params struct + * + * @return Status of operation. Negative in case of error. Zero otherwise. + */ +int synx_util_export_object(struct synx_client *client, + struct synx_export_params *params); + +/** + * @brief: Function to import the synx object for use by the client + * + * Obtains the synx object corresponding to the importing synx handle + * + * @param params : Pointer to import params struct + * + * @return Synx object pointer on success. NULL if import failed or invalid + */ +struct synx_coredata *synx_util_import_object( + struct synx_import_params *params); + +/** + * @brief: Function to acquire the synx client session + * + * This increments the reference count of the client in device client_table + * + * @param session_id : Client session id + * + * @return Pointer to synx client. NULL if invalid session id. + */ +struct synx_client *synx_get_client(struct synx_session session_id); + +/** + * @brief: Function to release the synx client session + * + * This decrements the reference count of the client in device client_table and + * invokes the client session cleanup if reference reaches zero. + * + * @param client : Pointer to client session structure + */ +void synx_put_client(struct synx_client *client); + +/** + * @brief: Function to generate timestamp + * + * @param timestamp : Timestamp string (filled by function) + * @param size : Timestamp string size + */ +void synx_util_generate_timestamp(char *timestamp, size_t size); + +/** + * @brief: Function to log framework errors + * + * This saves the error in a global list and can be accessed using debugfs. + * Needs to be added where we wish to trace the error. + * + * @param id : Client session id + * @param h_synx : Synx object handle + * @param err : Error code + */ +void synx_util_log_error(u32 id, s32 h_synx, s32 err); + +#endif /* __SYNX_UTIL_H__ */